Skip to content

Commit

Permalink
Merge pull request #71 from appKom/setup-admin-dashboard
Browse files Browse the repository at this point in the history
Setup admin dashboard
  • Loading branch information
fredrir authored Sep 25, 2024
2 parents afa9257 + ef7f98a commit 1ddcd2d
Show file tree
Hide file tree
Showing 17 changed files with 971 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .env.local.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

NEXTAUTH_SECRET=your-secret-key
NEXTAUTH_URL=http://localhost:3000
9 changes: 9 additions & 0 deletions app/admin/applications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const ApplicationsPage = () => {
return (
<div className="flex flex-col min-h-screen items-center pt-8">
<h1 className="text-4xl">Søknader</h1>
</div>
);
};

export default ApplicationsPage;
76 changes: 76 additions & 0 deletions app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';

import { signIn, signOut, useSession } from 'next-auth/react';
import Button from '@/components/all/Button';

export default function AdminLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const { data: session, status } = useSession();

const handleLogin = () =>
signIn('google', {
callbackUrl: '/admin',
});

const handleLogout = () => signOut({ callbackUrl: '/' });

if (status === 'loading') {
return (
<div className="min-h-screen text-white flex items-center justify-center">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-16 w-16 border-y-2 border-onlineyellow mb-4"></div>
<h2 className="text-2xl font-semibold">
Laster inn administrasjonspanel...
</h2>
<p className="text-slate-400 mt-2">
Vennligst vent mens vi henter informasjonen din
</p>
</div>
</div>
);
}

if (!session) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="flex flex-col items-center justify-center px-6 gap-5">
<h1 className="text-3xl">Vennligst logg inn</h1>
<Button
color="orange"
title="Logg inn"
onClick={() => handleLogin()}
/>
</div>
</div>
);
}

if (session?.user?.role !== 'admin') {
console.log(session.user);
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="flex flex-col items-center justify-center px-6 gap-5">
<h1 className="text-3xl">Du har ikke tilgang til denne siden</h1>
<h1>{session.user?.name}</h1>
<Button
color="orange"
title="Logg ut"
onClick={() => handleLogout()}
/>
</div>
</div>
);
}
if (session && session?.user?.role === 'admin') {
return (
<div>
<div className="flex flex-col min-h-screen items-center">
{children}
</div>
</div>
);
}
}
9 changes: 9 additions & 0 deletions app/admin/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const MembersPage = () => {
return (
<div className="flex flex-col min-h-screen items-center pt-8">
<h1 className="text-4xl">Medlemmer</h1>
</div>
);
};

export default MembersPage;
56 changes: 56 additions & 0 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { useSession } from 'next-auth/react';
import Link from 'next/link';
import { CircleDollarSignIcon, PaperclipIcon, UserIcon } from 'lucide-react';

const AdminPage = () => {
const { data: session } = useSession();

const routes = [
{
title: 'Portfølje',
href: '/admin/portfolio',
icon: CircleDollarSignIcon,
},
{
title: 'Medlemmer',
href: '/admin/members',
icon: UserIcon,
},
{
title: 'Søknader',
href: '/admin/applications',
icon: PaperclipIcon,
},
];

return (
<div className="flex flex-col items-center py-8 ">
<div className="flex flex-col items-center px-6 gap-5">
<h1 className="text-3xl">{`Velkommen ${session?.user!.name}`}</h1>

<div className="grid gap-4 grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{routes.map((route, index) => (
<div key={index}>
<Link href={route.href} className="">
<div className="border-2 border-white rounded-md px-8 hover:scale-110">
<div className="flex flex-col items-center justify-between w-full shadow-md min-h-48 p-4">
<h1 className="mb-4 text-2xl font-semibold">
{route.title}
</h1>
<div className="flex-grow flex items-center justify-center">
<route.icon size={96} />
</div>
</div>
</div>
</Link>
</div>
))}
</div>
</div>
</div>
);
};

export default AdminPage;
9 changes: 9 additions & 0 deletions app/admin/portfolio/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const PortfolioPage = () => {
return (
<div className="flex flex-col min-h-screen items-center pt-8">
<h1 className="text-4xl">Portfølje</h1>
</div>
);
};

export default PortfolioPage;
54 changes: 54 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { getUserGroups } from '@/lib/auth/getUserGroups';
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
authorization: {
params: {
scope:
'openid email profile https://www.googleapis.com/auth/admin.directory.group.readonly',
},
},
}),
],
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
},
callbacks: {
async jwt({ token, account, user }) {
if (account) {
token.accessToken = account.access_token;
}

if (token.accessToken && user?.email) {
token.groups = await getUserGroups(
token.accessToken as string,
user.email,
);
}

return token;
},
async session({ session, token }) {
session.user = {
id: token.sub ? parseInt(token.sub, 10) : 0,
name: session.user?.name || '',
role:
session.user?.email == '[email protected]' ||
'[email protected] '
? 'admin'
: 'user', //TODO vente på dotkom
email: session.user?.email || '',
groups: (token.groups as string[]) || [],
};
return session;
},
},
});

export { handler as GET, handler as POST };
9 changes: 6 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next';
import './globals.css';
import Navbar from '@/components/all/Navbar';
import Footer from '@/components/all/Footer';
import SessionWrapper from '@/lib/auth/SessionWrapper';

// TODO: add favicon, and maybe dynamic title and description based on page
export const metadata: Metadata = {
Expand All @@ -20,9 +21,11 @@ export default function RootLayout({
<link rel="icon" href="public/icon-256.png" sizes="any" />
</head>
<body className="antialiased">
<Navbar />
{children}
<Footer />
<SessionWrapper>
<Navbar />
{children}
<Footer />
</SessionWrapper>
</body>
</html>
);
Expand Down
25 changes: 25 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Button from '@/components/all/Button';
import Image from 'next/image';

export default function Custom404() {
return (
<div className="flex flex-col items-center justify-center min-h-screen text-center px-6 gap-12">
<h2 className="text-3xl sm:text-3xl lg:text-5xl font-bold text-online-darkBlue dark:text-white">
Fant ikke siden du lette etter
</h2>

<div>
<Image
src="/not-found.svg"
alt="Not Found Illustrasjon"
width={500}
height={500}
/>
</div>

<div className="py-10">
<Button title="Gå tilbake til hjem siden" color="orange" href="/" />
</div>
</div>
);
}
46 changes: 46 additions & 0 deletions components/all/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import Link from 'next/link';

interface Props {
title: string;
onClick?: () => void;
href?: string;
color: 'orange' | 'white';
className?: string;
}

const Button = ({ title, onClick, color, href, className }: Props) => {
let colorStyle = '';

switch (color) {
case 'orange':
colorStyle =
'border border-onlineyellow text-onlineyellow hover:border-orange-600 hover:text-orange-600';
break;

case 'white':
colorStyle =
'border border-white text-white hover:border-gray-600 hover:text-gray-600';
break;
}

const buttonStyle = `px-4 py-3 rounded-md ${colorStyle} ${className}`;

if (onClick) {
return (
<button className={`${buttonStyle}`} onClick={onClick}>
{title}
</button>
);
}
if (href) {
return (
<Link className={`${buttonStyle}`} href={href}>
{title}
</Link>
);
}
};

export default Button;
Loading

0 comments on commit 1ddcd2d

Please sign in to comment.