diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 60868e82..20b7a3eb 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,51 +1,11 @@ import type { Metadata } from "next"; -import type * as React from "react"; -import { cn } from "@/components/lib/utils"; -import SelectCoachingRelationship from "@/components/ui/dashboard/select-coaching-relationship"; -import CoachingSessionList from "@/components/ui/dashboard/coaching-session-list"; -import AddEntities from "@/components/ui/dashboard/add-entities"; +import { DashboardContainer } from "../../components/ui/dashboard/dashboard-container"; export const metadata: Metadata = { title: "Dashboard", description: "Coaching dashboard", }; -function DashboardContainer({ - className, - ...props -}: React.HTMLAttributes) { - return ( -
*]:w-full", - className - )} - {...props} - /> - ); -} - export default function DashboardPage() { - return ( - <> -
-
- -
-
- - - - - - ); + return ; } diff --git a/src/components/ui/coaching-relationship-selector.tsx b/src/components/ui/coaching-relationship-selector.tsx index cb75ddab..1ca7f24f 100644 --- a/src/components/ui/coaching-relationship-selector.tsx +++ b/src/components/ui/coaching-relationship-selector.tsx @@ -14,8 +14,10 @@ import { useEffect } from "react"; import { useAuthStore } from "@/lib/providers/auth-store-provider"; import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"; import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider"; +import { cn } from "../lib/utils"; interface CoachingRelationshipsSelectorProps extends PopoverProps { + className?: string; /// The Organization's Id for which to get a list of associated CoachingRelationships organizationId: Id; /// Disable the component from interaction with the user @@ -38,9 +40,6 @@ function CoachingRelationshipsSelectItems({ // Be sure to cache the list of current coaching relationships in the CoachingRelationshipStateStore useEffect(() => { if (!relationships.length) return; - console.debug( - `relationships (useEffect): ${JSON.stringify(relationships)}` - ); setCurrentCoachingRelationships(relationships); }, [relationships, setCurrentCoachingRelationships]); @@ -48,8 +47,6 @@ function CoachingRelationshipsSelectItems({ if (isError) return
Error loading coaching relationships
; if (!relationships?.length) return
No coaching relationships found
; - console.debug(`relationships: ${JSON.stringify(relationships)}`); - return ( <> {relationships.map((rel) => ( @@ -63,6 +60,7 @@ function CoachingRelationshipsSelectItems({ } export default function CoachingRelationshipSelector({ + className, organizationId, disabled, onSelect, @@ -101,29 +99,32 @@ export default function CoachingRelationshipSelector({ ? getCurrentCoachingRelationship(currentCoachingRelationshipId) : null; - const displayValue = currentRelationship ? ( - <> - {currentRelationship.coach_first_name}{" "} - {currentRelationship.coach_last_name} ->{" "} - {currentRelationship.coachee_first_name}{" "} - {currentRelationship.coachee_last_name} - - ) : undefined; + const displayValue = + currentRelationship && currentRelationship.id ? ( + <> + {currentRelationship.coach_first_name}{" "} + {currentRelationship.coach_last_name} ->{" "} + {currentRelationship.coachee_first_name}{" "} + {currentRelationship.coachee_last_name} + + ) : undefined; return ( - +
+ +
); } diff --git a/src/components/ui/coaching-session.tsx b/src/components/ui/coaching-session.tsx index d0e77880..0b381531 100644 --- a/src/components/ui/coaching-session.tsx +++ b/src/components/ui/coaching-session.tsx @@ -8,20 +8,31 @@ import Link from "next/link"; import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider"; import { useOverarchingGoalBySession } from "@/lib/api/overarching-goals"; import { Id } from "@/types/general"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { MoreHorizontal } from "lucide-react"; +import { CoachingSession as CoachingSessionType } from "@/types/coaching-session"; +import { useAuthStore } from "@/lib/providers/auth-store-provider"; interface CoachingSessionProps { - coachingSession: { - id: Id; - date: string; - }; + coachingSession: CoachingSessionType; + onUpdate: () => void; + onDelete: () => void; } const CoachingSession: React.FC = ({ coachingSession, + onUpdate, + onDelete, }) => { const { setCurrentCoachingSessionId } = useCoachingSessionStateStore( (state) => state ); + const { isCoach } = useAuthStore((state) => state); return ( @@ -33,15 +44,34 @@ const CoachingSession: React.FC = ({ {format(new Date(coachingSession.date), "MMMM d, yyyy h:mm a")}
- - - +
+ + + + + + + + + + Edit + + {isCoach && ( + + Delete + + )} + + +
diff --git a/src/components/ui/dashboard/add-coaching-session-dialog.tsx b/src/components/ui/dashboard/add-coaching-session-dialog.tsx deleted file mode 100644 index 3cbcad19..00000000 --- a/src/components/ui/dashboard/add-coaching-session-dialog.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client"; - -import React from "react"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Label } from "@/components/ui/label"; -import { Calendar } from "@/components/ui/calendar"; -import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"; -import { getDateTimeFromString } from "@/types/general"; -import { - CoachingSession, - defaultCoachingSession, -} from "@/types/coaching-session"; -import { - useCoachingSessionList, - useCoachingSessionMutation, -} from "@/lib/api/coaching-sessions"; -import { DateTime } from "ts-luxon"; -import { useAuthStore } from "@/lib/providers/auth-store-provider"; -import { cn } from "@/components/lib/utils"; - -interface AddCoachingSessionDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onCoachingSessionAdded: () => void; - dialogTrigger: React.ReactElement>; -} - -export function AddCoachingSessionDialog({ - open, - onOpenChange, - onCoachingSessionAdded, - dialogTrigger, -}: AddCoachingSessionDialogProps) { - const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore( - (state) => state - ); - const fromDate = DateTime.now().minus({ month: 1 }); - const toDate = DateTime.now().plus({ month: 1 }); - const { refresh } = useCoachingSessionList( - currentCoachingRelationshipId, - fromDate, - toDate - ); - const { create: createCoachingSession } = useCoachingSessionMutation(); - const [newSessionDate, setNewSessionDate] = useState( - undefined - ); - const [newSessionTime, setNewSessionTime] = useState(""); - const { isCoach } = useAuthStore((state) => state); - - const handleCreateSession = (e: React.FormEvent) => { - e.preventDefault(); - if (!newSessionDate || !newSessionTime) return; - - const [hours, minutes] = newSessionTime.split(":").map(Number); - const dateTime = getDateTimeFromString(newSessionDate.toISOString()) - .set({ hour: hours, minute: minutes }) - .toFormat("yyyy-MM-dd'T'HH:mm:ss"); - - const newCoachingSession: CoachingSession = { - ...defaultCoachingSession(), - coaching_relationship_id: currentCoachingRelationshipId, - date: dateTime, - }; - - createCoachingSession(newCoachingSession) - .then(() => { - refresh(); - onCoachingSessionAdded(); - setNewSessionDate(undefined); - setNewSessionTime(""); - }) - .catch((err: Error) => { - console.error("Failed to create new Coaching Session: " + err); - throw err; - }); - }; - - return ( - - - {React.cloneElement(dialogTrigger, { - ...(dialogTrigger.props as React.HTMLAttributes), - className: cn( - (dialogTrigger.props as React.HTMLAttributes) - .className - ), - })} - - - - - Create New Coaching Session - -
-
- - setNewSessionDate(date)} - /> -
-
- - setNewSessionTime(e.target.value)} - className="w-full border rounded p-2" - required - /> -
- -
-
-
- ); -} diff --git a/src/components/ui/dashboard/add-entities.tsx b/src/components/ui/dashboard/add-entities.tsx index 76c83147..38ccc504 100644 --- a/src/components/ui/dashboard/add-entities.tsx +++ b/src/components/ui/dashboard/add-entities.tsx @@ -1,48 +1,40 @@ "use client"; -import { useState } from "react"; -import { AddCoachingSessionDialog } from "./add-coaching-session-dialog"; import { AddCoachingSessionButton } from "./add-coaching-session-button"; import { AddMemberButton } from "./add-member-button"; import { useRouter } from "next/navigation"; import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider"; import { useAuthStore } from "@/lib/providers/auth-store-provider"; +import { cn } from "@/components/lib/utils"; -export default function AddEntities() { +interface AddEntitiesProps { + className?: string; + onCreateSession: () => void; +} + +export default function AddEntities({ + className, + onCreateSession, +}: AddEntitiesProps) { const router = useRouter(); - const [open, setOpen] = useState(false); const { currentOrganizationId } = useOrganizationStateStore((state) => state); const { isCoach } = useAuthStore((state) => state); - const onCoachingSessionAdded = () => { - setOpen(false); - }; - const onMemberButtonClicked = () => { router.push(`/organizations/${currentOrganizationId}/members`); }; return ( -
+

Add New

- - } + - {/* TODO: Refactor the AddMemberButton and AddMemberDialog to work just like - AddCoachingSessionDialog does above, where the dialog is the parent container - and it accepts a AddMemberButton as the dialogTrigger parameter. - */} void; + coachingSessionToEdit?: CoachingSession; +} + +export function CoachingSessionDialog({ + open, + onOpenChange, + coachingSessionToEdit, +}: CoachingSessionDialogProps) { + const mode: CoachingSessionFormMode = coachingSessionToEdit ? "update" : "create"; + + return ( + + + + + {mode === "create" ? "Create New Coaching Session" : "Update Coaching Session"} + + + + + + ); +} diff --git a/src/components/ui/dashboard/coaching-session-form.tsx b/src/components/ui/dashboard/coaching-session-form.tsx new file mode 100644 index 00000000..672a7c93 --- /dev/null +++ b/src/components/ui/dashboard/coaching-session-form.tsx @@ -0,0 +1,121 @@ +import { CoachingSession } from "@/types/coaching-session"; +import { Label } from "@/components/ui/label"; +import { Calendar } from "@/components/ui/calendar"; +import { Button } from "@/components/ui/button"; +import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"; +import { useCoachingSessionList, useCoachingSessionMutation } from "@/lib/api/coaching-sessions"; +import { DateTime } from "ts-luxon"; +import { useAuthStore } from "@/lib/providers/auth-store-provider"; +import { format } from "date-fns"; +import { getDateTimeFromString } from "@/types/general"; +import { useState } from "react"; +import { defaultCoachingSession } from "@/types/coaching-session"; + +export type CoachingSessionFormMode = "create" | "update"; + +interface CoachingSessionFormProps { + existingSession?: CoachingSession; + mode: CoachingSessionFormMode; + onOpenChange: (open: boolean) => void; +} + +export default function CoachingSessionForm({ + existingSession, + mode, + onOpenChange +}: CoachingSessionFormProps) { + + const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore( + (state) => state + ); + const fromDate = DateTime.now().minus({ month: 1 }); + const toDate = DateTime.now().plus({ month: 1 }); + const { refresh } = useCoachingSessionList( + currentCoachingRelationshipId, + fromDate, + toDate + ); + const { create: createCoachingSession, update } = + useCoachingSessionMutation(); + const [sessionDate, setSessionDate] = useState( + existingSession ? new Date(existingSession.date) : undefined + ); + const [sessionTime, setSessionTime] = useState( + existingSession ? format(new Date(existingSession.date), "HH:mm") : "" + ); + + const resetForm = () => { + setSessionDate(undefined); + setSessionTime(""); + onOpenChange(false); + }; + + const handleCreateSession = async (dateTime: string) => { + const newCoachingSession: CoachingSession = { + ...defaultCoachingSession(), + coaching_relationship_id: currentCoachingRelationshipId, + date: dateTime, + }; + await createCoachingSession(newCoachingSession); + }; + + const handleUpdateSession = async (dateTime: string) => { + if (!existingSession) return; + await update(existingSession.id, { + ...existingSession, + date: dateTime, + updated_at: DateTime.now(), + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!sessionDate || !sessionTime) return; + + const [hours, minutes] = sessionTime.split(":").map(Number); + const dateTime = getDateTimeFromString(sessionDate.toISOString()) + .set({ hour: hours, minute: minutes }) + .toFormat("yyyy-MM-dd'T'HH:mm:ss"); + + const handler = mode === "create" ? handleCreateSession : handleUpdateSession; + + try { + await handler(dateTime); + refresh(); + } catch (error) { + // TODO: We might want to show a toast here if/when we get that infrastructure in place + console.error(`Failed to ${mode} coaching session:`, error); + } finally { + resetForm(); + } + }; + + return ( +
+
+
+ + setSessionDate(date)} + /> +
+
+ + setSessionTime(e.target.value)} + className="w-full border rounded p-2" + required + /> +
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/components/ui/dashboard/coaching-session-list.tsx b/src/components/ui/dashboard/coaching-session-list.tsx index 2b366939..31c697d8 100644 --- a/src/components/ui/dashboard/coaching-session-list.tsx +++ b/src/components/ui/dashboard/coaching-session-list.tsx @@ -1,17 +1,31 @@ "use client"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { ArrowUpDown } from "lucide-react"; -import { useAuthStore } from "@/lib/providers/auth-store-provider"; import { useCoachingRelationshipStateStore } from "@/lib/providers/coaching-relationship-state-store-provider"; import { useCoachingSessionList } from "@/lib/api/coaching-sessions"; +import { useCoachingSessionMutation } from "@/lib/api/coaching-sessions"; import { CoachingSession as CoachingSessionComponent } from "@/components/ui/coaching-session"; import { DateTime } from "ts-luxon"; -import { AddCoachingSessionDialog } from "./add-coaching-session-dialog"; +import { + filterAndSortCoachingSessions, + type CoachingSession, +} from "@/types/coaching-session"; +import { Id, SortOrder } from "@/types/general"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import CoachingRelationshipSelector from "../coaching-relationship-selector"; +import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider"; +import { cn } from "@/components/lib/utils"; -export default function CoachingSessionList() { +interface CoachingSessionListProps { + className?: string; + onUpdateSession: (session: CoachingSession) => void; +} + +export default function CoachingSessionList({ + className, + onUpdateSession, +}: CoachingSessionListProps) { + const { currentOrganizationId } = useOrganizationStateStore((state) => state); const { currentCoachingRelationshipId } = useCoachingRelationshipStateStore( (state) => state ); @@ -24,90 +38,125 @@ export default function CoachingSessionList() { coachingSessions, isLoading: isLoadingCoachingSessions, isError: isErrorCoachingSessions, - refresh, + refresh: refreshCoachingSessions, } = useCoachingSessionList(currentCoachingRelationshipId, fromDate, toDate); - const [sortByDate, setSortByDate] = useState(true); - const [open, setOpen] = useState(false); - const { isCoach } = useAuthStore((state) => state); + const { delete: deleteCoachingSession } = useCoachingSessionMutation(); - const sortedSessions = coachingSessions - ? [...coachingSessions].sort((a, b) => { - return new Date(b.date).getTime() - new Date(a.date).getTime(); - }) - : []; + const handleDeleteCoachingSession = async (id: Id) => { + if (!confirm("Are you sure you want to delete this session?")) { + return; + } - const onCoachingSessionAdded = () => { - // SWR refresh - refresh(); - setOpen(false); + try { + await deleteCoachingSession(id).then(() => refreshCoachingSessions()); + } catch (error) { + console.error("Error deleting coaching session:", error); + // TODO: Show an error toast here once we start using toasts for showing operation results. + } }; - if (isLoadingCoachingSessions) return
Loading coaching sessions...
; - if (isErrorCoachingSessions) - return
Error loading coaching sessions
; + const upcomingSessions = coachingSessions + ? filterAndSortCoachingSessions(coachingSessions, SortOrder.Ascending, true) + : []; + + const previousSessions = coachingSessions + ? filterAndSortCoachingSessions( + coachingSessions, + SortOrder.Descending, + false + ) + : []; + + let loadingCoachingSessions = ( +
+

+ Loading your coaching sessions... +

+
+ ); + + let noCoachingSessions = ( +
+

+ Select a coaching relationship to view your coaching sessions. +

+
+ ); + + let errorLoadingCoachingSessions = ( +
+

+ There was an error trying to load your coaching sessions. +

+
+ ); return ( - - -
- - Coaching Sessions - - - Create New Coaching Session - - } - /> -
-
- - -
+ + + +
+
Coaching Sessions
+ +
+
- {!currentCoachingRelationshipId ? ( -
-

- Choose a Relationship to view Coaching Sessions -

-
- ) : ( -
- {sortedSessions.map((coachingSession) => ( - - ))} -
- )} + + + Upcoming + Previous + + + {isLoadingCoachingSessions ? ( + loadingCoachingSessions + ) : isErrorCoachingSessions ? ( + errorLoadingCoachingSessions + ) : !currentCoachingRelationshipId ? ( + noCoachingSessions + ) : ( +
+ {upcomingSessions.map((coachingSession) => ( + onUpdateSession(coachingSession)} + onDelete={() => + handleDeleteCoachingSession(coachingSession.id) + } + /> + ))} +
+ )} +
+ + {isLoadingCoachingSessions ? ( + loadingCoachingSessions + ) : isErrorCoachingSessions ? ( + errorLoadingCoachingSessions + ) : !currentCoachingRelationshipId ? ( + noCoachingSessions + ) : ( +
+ {previousSessions.map((coachingSession) => ( + onUpdateSession(coachingSession)} + onDelete={() => + handleDeleteCoachingSession(coachingSession.id) + } + /> + ))} +
+ )} +
+
); diff --git a/src/components/ui/dashboard/dashboard-container.tsx b/src/components/ui/dashboard/dashboard-container.tsx new file mode 100644 index 00000000..5295b245 --- /dev/null +++ b/src/components/ui/dashboard/dashboard-container.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useState } from "react"; +import type * as React from "react"; +import { cn } from "@/components/lib/utils"; +import CoachingSessionList from "@/components/ui/dashboard/coaching-session-list"; +import AddEntities from "@/components/ui/dashboard/add-entities"; +import { CoachingSessionDialog } from "@/components/ui/dashboard/coaching-session-dialog"; +import type { CoachingSession } from "@/types/coaching-session"; + +export function DashboardContainer() { + const [dialogOpen, setDialogOpen] = useState(false); + const [sessionToEdit, setSessionToEdit] = useState< + CoachingSession | undefined + >(); + + const handleOpenDialog = (session?: CoachingSession) => { + setSessionToEdit(session); + setDialogOpen(true); + }; + + const handleCloseDialog = () => { + setDialogOpen(false); + setSessionToEdit(undefined); + }; + + return ( +
*]:w-full" + )} + > + handleOpenDialog()} + /> + + +
+ ); +} diff --git a/src/components/ui/main-nav-menu.tsx b/src/components/ui/main-nav-menu.tsx deleted file mode 100644 index 3c8fecb9..00000000 --- a/src/components/ui/main-nav-menu.tsx +++ /dev/null @@ -1,278 +0,0 @@ -"use client"; - -import { cn } from "@/components/lib/utils"; - -import { Dialog } from "@radix-ui/react-dialog"; - -import { Button } from "@/components/ui/button"; -import { - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Icons } from "@/components/ui/icons"; -import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, -} from "@/components/ui/navigation-menu"; -import { PresetSelector } from "@/components/ui/preset-selector"; - -import { current, future, past } from "../../data/presets"; - -import { ChevronDownIcon } from "@radix-ui/react-icons"; - -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { siteConfig } from "@/site.config"; -import { forwardRef, useState } from "react"; -import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider"; - -export function MainNavMenu() { - const [open, setIsOpen] = useState(false); - const { currentOrganizationId, getCurrentOrganization } = - useOrganizationStateStore((state) => state); - - const organization = getCurrentOrganization(currentOrganizationId); - - return ( - <> - - - - - - {organization.name.length > 0 - ? organization.name - : siteConfig.name} - - - -
    -
  • - - - -
    - {organization.name.length > 0 - ? organization.name - : siteConfig.name} -
    -

    - {siteConfig.description} -

    -
    -
    -
  • - - {/* Hidden for MVP */} - - - Organization & Relationship - - Set your current organization and coaching relationship. - - - -
    -
    -
    -

    - Organization -

    -
    -
    - - - - - - - - - No roles found. - - -

    Viewer

    -

    - Can view and comment. -

    -
    - -

    Developer

    -

    - Can view, comment and edit. -

    -
    - -

    Billing

    -

    - Can view, comment and manage billing. -

    -
    - -

    Owner

    -

    - Admin-level access to all resources. -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    - Relationship -

    -
    -
    - - - - - - - - - No roles found. - - -

    Viewer

    -

    - Can view and comment. -

    -
    - -

    Developer

    -

    - Can view, comment and edit. -

    -
    - -

    Billing

    -

    - Can view, comment and manage billing. -

    -
    - -

    Owner

    -

    - Admin-level access to all resources. -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
-
-
-
-
- - - - - Current Organization & Coaching Relationship - - - Select your current organization and coaching relationship to - access specific coaching sessions across multiple organizations - and coaches/coachees. - - -
-

- Select Your Organization -

-
- -
-

- Select Your Coaching Relationship -

-
- -
-
- -
- - -
-
-
-
- - ); -} - -const ListItem = forwardRef< - React.ElementRef<"a">, - React.ComponentPropsWithoutRef<"a"> ->(({ className, title, children, ...props }, ref) => { - return ( -
  • - - -
    {title}
    -

    - {children} -

    -
    -
    -
  • - ); -}); -ListItem.displayName = "ListItem"; diff --git a/src/components/ui/user-nav.tsx b/src/components/ui/user-nav.tsx index 0d3ad7fc..275fd188 100644 --- a/src/components/ui/user-nav.tsx +++ b/src/components/ui/user-nav.tsx @@ -40,9 +40,6 @@ export function UserNav() { async function logout_user() { try { - console.trace("Deleting active user session: ", userSession.id); - await deleteUserSession(userSession.id); - console.trace("Resetting CoachingSessionStateStore state"); resetCoachingSessionState(); @@ -52,12 +49,21 @@ export function UserNav() { console.trace("Resetting OrganizationStateStore state"); resetOrganizationState(); + console.trace( + "Deleting current user session from backend: ", + userSession.id + ); + await deleteUserSession(userSession.id); + } catch (err) { + console.warn("Error while logging out session: ", userSession.id, err); + } finally { + // Ensure we still log out of the frontend even if the backend request + // to delete the user session fails. console.trace("Resetting AuthStore state"); logout(); - - router.push("/"); - } catch (err) { - console.error("Error while logging out session: ", userSession.id, err); + console.debug("Navigating to /"); + await router.push("/"); + console.debug("Navigation to / completed successfully."); } } diff --git a/src/lib/api/users.ts b/src/lib/api/users.ts index 7385ba2e..7498266f 100644 --- a/src/lib/api/users.ts +++ b/src/lib/api/users.ts @@ -55,7 +55,7 @@ export const UserApi = { * @param userId The ID of the user to delete * @returns Promise resolving to the deleted User object */ - deleteNested: async (_entityId: Id, userId: Id): Promise => { + deleteNested: async (_entityId: Id, _userId: Id): Promise => { throw new Error("Delete nested operation not implemented"); }, }; diff --git a/src/middleware.ts b/src/middleware.ts index 983f80b7..b39f292d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -19,18 +19,18 @@ export default async function middleware(req: NextRequest) { // 3. Get the session from the cookie const sessionCookie = req.cookies.get("id"); - const session = sessionCookie?.value; + const isValidSession = sessionCookie?.value; // 4. Redirect to / if the user is not authenticated // 4b. TODO: Check session validity/expiration? - if (isProtectedRoute && !session) { + if (isProtectedRoute && !isValidSession) { return NextResponse.redirect(new URL("/", req.nextUrl)); } // 5. Redirect to /dashboard if the user is authenticated if ( isPublicRoute && - session && + isValidSession && !req.nextUrl.pathname.startsWith("/dashboard") ) { return NextResponse.redirect(new URL("/dashboard", req.nextUrl)); diff --git a/src/types/coaching-session.ts b/src/types/coaching-session.ts index 348f09ae..a29baf2a 100644 --- a/src/types/coaching-session.ts +++ b/src/types/coaching-session.ts @@ -32,24 +32,41 @@ export function isCoachingSessionArray( return Array.isArray(value) && value.every(isCoachingSession); } -export function sortCoachingSessionArray( +export function filterAndSortCoachingSessions( sessions: CoachingSession[], - order: SortOrder + order: SortOrder, + returnUpcoming: boolean ): CoachingSession[] { - if (order == SortOrder.Ascending) { - sessions.sort( + const now = new Date(); + + // Filter sessions based on the `showUpcoming` parameter + const filteredSessions = sessions.filter((session) => { + const sessionDate = new Date(session.date.toString()); + if (returnUpcoming) { + // Include sessions today that haven't started yet or are in the future + return sessionDate >= now; + } else { + // Include past sessions only + return sessionDate < now; + } + }); + + // Sort the filtered sessions based on the order parameter + if (order === SortOrder.Ascending) { + filteredSessions.sort( (a, b) => new Date(a.date.toString()).getTime() - new Date(b.date.toString()).getTime() ); - } else if (order == SortOrder.Descending) { - sessions.sort( + } else if (order === SortOrder.Descending) { + filteredSessions.sort( (a, b) => new Date(b.date.toString()).getTime() - new Date(a.date.toString()).getTime() ); } - return sessions; + + return filteredSessions; } export function getCoachingSessionById(