-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from Informatik-Projekt-Kurs/IPK-194-Implement…
…-Active-User IPK-194-Implement-Active-User
- Loading branch information
Showing
8 changed files
with
191 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import React from "react"; | ||
|
||
function Bookings() { | ||
return <div className="flex flex-col items-center justify-start p-8 px-6">Settings</div>; | ||
} | ||
|
||
export default Bookings; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
"use client"; | ||
import Image from "next/image"; | ||
import { Button } from "@/components/ui/button"; | ||
import { LuBookCopy, LuBuilding, LuGauge, LuLayoutDashboard, LuSettings, LuUser2 } from "react-icons/lu"; | ||
import React, { useEffect, useState } from "react"; | ||
import type { User } from "@/types"; | ||
import { getAccessToken, getUser } from "@/lib/actions"; | ||
import Link from "next/link"; | ||
|
||
export default function DashboardLayout({ children }: { children: React.ReactNode }) { | ||
const [user, setUser] = useState<User | null>(); | ||
const [isAdmin, setIsAdmin] = useState(user?.role === "ADMIN"); | ||
|
||
useEffect(() => { | ||
const fetchUser = async () => { | ||
try { | ||
const accessToken = await getAccessToken(); | ||
setUser(await getUser(accessToken)); | ||
} catch (error) { | ||
setIsAdmin(false); | ||
console.error("Failed to fetch user", error); | ||
} | ||
}; | ||
fetchUser().catch(console.error); | ||
}, []); | ||
|
||
useEffect(() => { | ||
setIsAdmin(user?.role === "ADMIN"); | ||
}, [user]); | ||
return ( | ||
<div className="flex w-full flex-col gap-5 pl-8 md:flex-row"> | ||
<aside className="hidden lg:block"> | ||
<div className="sticky top-8 flex h-[calc(100vh-64px)] flex-row"> | ||
<div className="flex h-full w-[80px] flex-col items-center justify-start"> | ||
<div className="absolute mt-10 flex h-16 w-[80px] items-center justify-start rounded-l-md bg-subtle shadow-lg"> | ||
<div className="ml-[8px] h-[50%] w-[1px] bg-primary"></div> | ||
</div> | ||
<div className="mt-12 size-12 rounded-md"> | ||
<Image width={48} height={48} src="/landingLogo.png" alt="logo" /> | ||
</div> | ||
<div className="mt-14 flex flex-col gap-y-6"> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
</div> | ||
</div> | ||
<div className="flex h-full w-[230px] flex-col items-center justify-start rounded-[20px] border-2 border-primary"> | ||
<h1 className="mt-8 text-xl font-bold">MeetMate</h1> | ||
<div className="mt-[35%] flex flex-col items-center justify-start gap-y-4"> | ||
<header className="relative left-[-40%] mb-[-6px] text-xs text-muted-foreground">Tools</header> | ||
<Link href={"/dashboard"}> | ||
<Button className="w-[168px] justify-start text-foreground" variant="default"> | ||
<LuLayoutDashboard className="mx-2" size={18} /> | ||
Dashboard | ||
</Button> | ||
</Link> | ||
<Link href={"/dashboard/bookings"}> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuBookCopy className="mx-2" size={18} /> | ||
Bookings | ||
</Button> | ||
</Link> | ||
<Link href={"/dashboard/settings"}> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuSettings className="mx-2" size={18} /> | ||
Settings | ||
</Button> | ||
</Link> | ||
|
||
{isAdmin && ( | ||
<React.Fragment> | ||
<header className="relative left-[-40%] mb-[-6px] mt-6 text-xs text-muted-foreground">Admin</header> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuBuilding className="mx-2" size={18} /> | ||
Companies | ||
</Button> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuUser2 className="mx-2" size={18} /> | ||
Users | ||
</Button> | ||
<Button disabled className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuGauge className="mx-2" size={18} /> | ||
Analytics | ||
</Button> | ||
</React.Fragment> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</aside> | ||
<main className="mr-8 mt-8 min-h-[calc(100vh-64px)] w-full rounded-[20px] border-2 border-border"> | ||
{children} | ||
<footer className="flex h-8 w-full items-center justify-start rounded-b-[20px] bg-primary"> | ||
<p className="pl-4 text-sm font-medium text-background">MeetMate</p> | ||
</footer> | ||
</main> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,9 @@ | ||
"use client"; | ||
import { Button } from "@/components/ui/button"; | ||
import Image from "next/image"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; | ||
import React, { useEffect, useState } from "react"; | ||
import { LuLayoutDashboard, LuBookCopy, LuSettings, LuGauge, LuUser2, LuBuilding } from "react-icons/lu"; | ||
import { deleteToken, getUser } from "@/lib/actions"; | ||
import { deleteToken, getAccessToken, getUser } from "@/lib/actions"; | ||
import { | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
|
@@ -14,132 +12,76 @@ import { | |
DropdownMenuSeparator, | ||
DropdownMenuTrigger | ||
} from "@/components/ui/dropdown-menu"; | ||
import { type User } from "@/types"; | ||
import { extractNameInitials } from "@/lib/utils"; | ||
|
||
function Dashboard() { | ||
const [isAdmin, setIsAdmin] = useState(false); | ||
const [user, setUser] = useState<User | null>(); | ||
|
||
useEffect(() => { | ||
const fetchUser = async () => { | ||
try { | ||
const accessToken = await getAccessToken(); | ||
setUser(await getUser(accessToken)); | ||
} catch (error) { | ||
console.error("Failed to fetch user", error); | ||
} | ||
}; | ||
fetchUser().catch(console.error); | ||
}, []); | ||
|
||
const logout = async () => { | ||
try { | ||
await deleteToken(); | ||
window.location.reload(); | ||
} catch (error) { | ||
console.error("Logout failed", error); | ||
throw error; | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
const checkAdmin = async () => { | ||
const user = await getUser(); | ||
setIsAdmin(user?.role === "ADMIN"); | ||
}; | ||
checkAdmin().catch(() => { | ||
setIsAdmin(false); | ||
}); | ||
}, []); | ||
return ( | ||
<div className="flex w-full flex-col gap-5 pl-8 md:flex-row"> | ||
<aside className="hidden lg:block"> | ||
<div className="sticky top-8 flex h-[calc(100vh-64px)] flex-row"> | ||
<div className="flex h-full w-[80px] flex-col items-center justify-start"> | ||
<div className="absolute mt-10 flex h-16 w-[80px] items-center justify-start rounded-l-md bg-subtle shadow-lg"> | ||
<div className="ml-[8px] h-[50%] w-[1px] bg-primary"></div> | ||
</div> | ||
<div className="mt-12 size-12 rounded-md"> | ||
<Image width={48} height={48} src="/landingLogo.png" alt="logo" /> | ||
</div> | ||
<div className="mt-14 flex flex-col gap-y-6"> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
<div className="size-12 rounded-md bg-secondary"></div> | ||
</div> | ||
</div> | ||
<div className="flex h-full w-[230px] flex-col items-center justify-start rounded-[20px] border-2 border-primary"> | ||
<h1 className="mt-8 text-xl font-bold">MeetMate</h1> | ||
<div className="mt-[35%] flex flex-col items-center justify-start gap-y-4"> | ||
<header className="relative left-[-40%] mb-[-6px] text-xs text-muted-foreground">Tools</header> | ||
<Button className="w-[168px] justify-start text-foreground" variant="default"> | ||
<LuLayoutDashboard className="mx-2" size={18} /> | ||
Dashboard | ||
</Button> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuBookCopy className="mx-2" size={18} /> | ||
Bookings | ||
<div className="flex flex-col items-center justify-start p-8 px-6"> | ||
<header className="flex w-full flex-row items-center justify-between"> | ||
<h1 className="m-4 font-medium text-muted-foreground md:text-2xl">Welcome back, {user?.name}</h1> | ||
<div className="flex items-center gap-x-6"> | ||
<Input className="w-[320px]" placeholder="Search"></Input> | ||
<DropdownMenu modal={false}> | ||
<DropdownMenuTrigger asChild className={"mr-4"}> | ||
<Button variant="ghost" className="relative size-8 rounded-full"> | ||
<Avatar className="size-10"> | ||
<AvatarFallback className={"bg-primary"}>{extractNameInitials(user?.name)}</AvatarFallback> | ||
</Avatar> | ||
</Button> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuSettings className="mx-2" size={18} /> | ||
Settings | ||
</Button> | ||
|
||
{isAdmin && ( | ||
<React.Fragment> | ||
<header className="relative left-[-40%] mb-[-6px] mt-6 text-xs text-muted-foreground">Admin</header> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuBuilding className="mx-2" size={18} /> | ||
Companies | ||
</Button> | ||
<Button className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuUser2 className="mx-2" size={18} /> | ||
Users | ||
</Button> | ||
<Button disabled className="w-[168px] justify-start text-muted-foreground" variant="ghost"> | ||
<LuGauge className="mx-2" size={18} /> | ||
Analytics | ||
</Button> | ||
</React.Fragment> | ||
)} | ||
</div> | ||
</div> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent align={"end"} className={"w-56 border-border"}> | ||
<DropdownMenuLabel className="font-normal"> | ||
<div className="flex flex-col space-y-1"> | ||
<p className="text-sm font-medium leading-none">{user?.name}</p> | ||
<p className="text-xs leading-none text-muted-foreground">{user?.email}</p> | ||
</div> | ||
</DropdownMenuLabel> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem>Settings</DropdownMenuItem> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem onClick={logout} className={"text-red-500"}> | ||
Log out | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
</aside> | ||
<main className="mr-8 mt-8 min-h-screen w-full rounded-[20px] border-2 border-border"> | ||
<div className="flex flex-col items-center justify-start p-8 px-6"> | ||
<header className="flex w-full flex-row items-center justify-between"> | ||
<h1 className="m-4 font-medium text-muted-foreground md:text-2xl">Welcome back, Tim</h1> | ||
<div className="flex items-center gap-x-6"> | ||
<Input className="w-[320px]" placeholder="Search"></Input> | ||
<DropdownMenu modal={false}> | ||
<DropdownMenuTrigger asChild className={"mr-4"}> | ||
<Button variant="ghost" className="relative size-8 rounded-full"> | ||
<Avatar className="size-10"> | ||
<AvatarFallback className={"bg-primary"}>TL</AvatarFallback> | ||
</Avatar> | ||
</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent align={"end"} className={"w-56 border-border"}> | ||
<DropdownMenuLabel className="font-normal"> | ||
<div className="flex flex-col space-y-1"> | ||
<p className="text-sm font-medium leading-none">shadcn</p> | ||
<p className="text-xs leading-none text-muted-foreground">[email protected]</p> | ||
</div> | ||
</DropdownMenuLabel> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem>Settings</DropdownMenuItem> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem onClick={logout} className={"text-red-500"}> | ||
Log out | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
</header> | ||
</header> | ||
|
||
<div className="mt-8 flex h-[200px] w-full flex-col items-start justify-center gap-2 rounded-[20px] bg-primary pl-12"> | ||
<h2 className="text-3xl font-semibold">MeetMate Dashboard</h2> | ||
<p className="text-sm">Create your appointments in minutes</p> | ||
<Button className="mt-2" variant={"secondary"}> | ||
Book now | ||
</Button> | ||
</div> | ||
<div className="mt-8 flex h-[200px] w-full flex-col items-start justify-center gap-2 rounded-[20px] bg-primary pl-12"> | ||
<h2 className="text-3xl font-semibold">MeetMate Dashboard</h2> | ||
<p className="text-sm">Create your appointments in minutes</p> | ||
<Button className="mt-2" variant={"secondary"}> | ||
Book now | ||
</Button> | ||
</div> | ||
|
||
<div className="mt-8 flex h-[600px] w-full rounded-[20px] bg-subtle"></div> | ||
<div className="mt-8 flex h-[600px] w-full rounded-[20px] bg-subtle"></div> | ||
|
||
<div className="h-[50vh]"></div> | ||
</div> | ||
<footer className="flex h-8 w-full items-center justify-start rounded-b-[20px] bg-primary"> | ||
<p className="pl-4 text-sm font-medium text-background">MeetMate</p> | ||
</footer> | ||
</main> | ||
<div className="h-[50vh]"></div> | ||
</div> | ||
); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
function Settings() { | ||
return <div className="flex flex-col items-center justify-start p-8 px-6">Settings</div>; | ||
} | ||
|
||
export default Settings; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,20 @@ | ||
import { type ClassValue, clsx } from "clsx" | ||
import { twMerge } from "tailwind-merge" | ||
import { type ClassValue, clsx } from "clsx"; | ||
import { twMerge } from "tailwind-merge"; | ||
|
||
export function cn(...inputs: ClassValue[]) { | ||
return twMerge(clsx(inputs)) | ||
return twMerge(clsx(inputs)); | ||
} | ||
|
||
export function extractNameInitials(name: string | undefined) { | ||
if (name === undefined) { | ||
return ""; | ||
} | ||
const parts = name.trim().split(/\s+/); | ||
let initials = parts[0][0]; | ||
|
||
if (parts.length > 1) { | ||
initials += parts[parts.length - 1][0]; | ||
} | ||
|
||
return initials.toUpperCase(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters