Skip to content

Commit

Permalink
Merge pull request #49 from Informatik-Projekt-Kurs/IPK-194-Implement…
Browse files Browse the repository at this point in the history
…-Active-User

IPK-194-Implement-Active-User
  • Loading branch information
bencodes07 authored Apr 4, 2024
2 parents 2dc302c + d426cbc commit 95dd4e7
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 117 deletions.
7 changes: 7 additions & 0 deletions src/app/dashboard/bookings/page.tsx
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;
100 changes: 100 additions & 0 deletions src/app/dashboard/layout.tsx
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>
);
}
166 changes: 54 additions & 112 deletions src/app/dashboard/page.tsx
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,
Expand All @@ -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>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/app/dashboard/settings/page.tsx
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;
3 changes: 3 additions & 0 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const LoginForm = () => {
variant: "default",
className: "border-emerald-300"
});
setTimeout(() => {
router.push("/dashboard");
}, 1500);
}
}, [formState, toast, dispatch]);

Expand Down
6 changes: 5 additions & 1 deletion src/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export async function getUser(accessToken?: string): Promise<User | null> {
if (response.ok) {
return (await response.json()) as User;
} else {
console.log(response.status);
console.log("Error getting User", response.status);
return null;
}
} catch (error) {
Expand All @@ -106,6 +106,10 @@ export async function getUser(accessToken?: string): Promise<User | null> {
}
}

export async function getAccessToken() {
return cookies().get("accessToken")?.value;
}

type LoginFormState = {
message: string;
errors: Record<keyof { email: string; password: string }, string> | undefined;
Expand Down
20 changes: 17 additions & 3 deletions src/lib/utils.ts
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();
}
1 change: 0 additions & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export async function middleware(req: NextRequest) {
if (refreshToken !== undefined || refreshToken !== "") {
const newAccessToken = await refreshAccessToken(refreshToken);
if (newAccessToken !== undefined) {
console.log(newAccessToken);
response.cookies.set("accessToken", newAccessToken[0], { httpOnly: true, secure: true, sameSite: "strict" });
response.cookies.set("expires_at", newAccessToken[1], { httpOnly: true, secure: true, sameSite: "strict" });
user = await getUser(newAccessToken[0]);
Expand Down

0 comments on commit 95dd4e7

Please sign in to comment.