forked from sk1982/actaeon
add homepage setting
This commit is contained in:
parent
1b79472e34
commit
7d164a2a12
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||||
|
}
|
@ -17,10 +17,6 @@ const nextConfig = {
|
|||||||
permanent: false,
|
permanent: false,
|
||||||
basePath: false
|
basePath: false
|
||||||
}] : []), {
|
}] : []), {
|
||||||
source: '/',
|
|
||||||
destination: '/dashboard',
|
|
||||||
permanent: false
|
|
||||||
}, {
|
|
||||||
source: '/chuni',
|
source: '/chuni',
|
||||||
destination: '/chuni/dashboard',
|
destination: '/chuni/dashboard',
|
||||||
permanent: false
|
permanent: false
|
||||||
@ -36,7 +32,11 @@ const nextConfig = {
|
|||||||
experimental: {
|
experimental: {
|
||||||
instrumentationHook: true
|
instrumentationHook: true
|
||||||
},
|
},
|
||||||
productionBrowserSourceMaps: true
|
productionBrowserSourceMaps: true,
|
||||||
|
webpack: config => {
|
||||||
|
config.externals = [...config.externals, 'bcrypt'];
|
||||||
|
return config;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
25
package-lock.json
generated
25
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"mysql2": "^3.9.2",
|
"mysql2": "^3.9.2",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"next-auth": "^5.0.0-beta.15",
|
"next-auth": "^5.0.0-beta.15",
|
||||||
|
"next-client-cookies": "^1.1.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-day-picker": "^8.10.0",
|
"react-day-picker": "^8.10.0",
|
||||||
@ -227,7 +228,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/memoize": "0.7.4"
|
"@emotion/memoize": "0.7.4"
|
||||||
}
|
}
|
||||||
@ -236,8 +236,7 @@
|
|||||||
"version": "0.7.4",
|
"version": "0.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
@ -7754,6 +7753,14 @@
|
|||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -8523,6 +8530,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-client-cookies": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-client-cookies/-/next-client-cookies-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-e/rqQTXHSFuvUJELMeCDgM7dWW6IUNOGr7noWyRSgE/5l033UaqseDrjShfRZYG45JnrYKoX653OdXTJ8cn+NA==",
|
||||||
|
"dependencies": {
|
||||||
|
"js-cookie": "^3.0.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": ">= 13.0.0",
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next-themes": {
|
"node_modules/next-themes": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz",
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"mysql2": "^3.9.2",
|
"mysql2": "^3.9.2",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"next-auth": "^5.0.0-beta.15",
|
"next-auth": "^5.0.0-beta.15",
|
||||||
|
"next-client-cookies": "^1.1.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-day-picker": "^8.10.0",
|
"react-day-picker": "^8.10.0",
|
||||||
|
28
src/auth.ts
28
src/auth.ts
@ -1,7 +1,6 @@
|
|||||||
import NextAuth from 'next-auth';
|
import NextAuth from 'next-auth';
|
||||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||||
import { db, GeneratedDB } from '@/db';
|
import { db, GeneratedDB } from '@/db';
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
import { DBUserPayload } from '@/types/user';
|
import { DBUserPayload } from '@/types/user';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
import { SelectQueryBuilder, sql } from 'kysely';
|
import { SelectQueryBuilder, sql } from 'kysely';
|
||||||
@ -42,12 +41,15 @@ const nextAuth = NextAuth({
|
|||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, user }) {
|
async jwt({ token, user }) {
|
||||||
token.user ??= user;
|
token.user ??= user;
|
||||||
const dbUser = await selectUserProps(db.selectFrom('aime_user as u')
|
|
||||||
.where('u.id', '=', (token.user as any).id));
|
|
||||||
|
|
||||||
if (dbUser) {
|
if (db) {
|
||||||
const { password, ...payload } = dbUser;
|
const dbUser = await selectUserProps(db.selectFrom('aime_user as u')
|
||||||
token.user = { ...(token.user as any), ...payload };
|
.where('u.id', '=', (token.user as any).id));
|
||||||
|
|
||||||
|
if (dbUser) {
|
||||||
|
const { password, ...payload } = dbUser;
|
||||||
|
token.user = { ...(token.user as any), ...payload };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
@ -70,6 +72,13 @@ const nextAuth = NextAuth({
|
|||||||
(user as any).visibility = 0;
|
(user as any).visibility = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
(user as any).last_login_date = now;
|
||||||
|
await db.updateTable('aime_user')
|
||||||
|
.set({ last_login_date: now })
|
||||||
|
.where('id', '=', (user as any).id)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -80,6 +89,8 @@ const nextAuth = NextAuth({
|
|||||||
password: { label: 'Password', type: 'password' }
|
password: { label: 'Password', type: 'password' }
|
||||||
},
|
},
|
||||||
async authorize({ username, password }, req) {
|
async authorize({ username, password }, req) {
|
||||||
|
const bcrypt = await import('bcrypt');
|
||||||
|
|
||||||
if (typeof username !== 'string' || typeof password !== 'string')
|
if (typeof username !== 'string' || typeof password !== 'string')
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -97,10 +108,11 @@ const nextAuth = NextAuth({
|
|||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
|
|
||||||
export const auth = cache(nextAuth.auth);
|
export const auth = process.env.NEXT_RUNTIME !== 'edge' ? cache(nextAuth.auth) : (null as never);
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
handlers: { GET, POST },
|
handlers: { GET, POST },
|
||||||
signIn,
|
signIn,
|
||||||
signOut
|
signOut,
|
||||||
|
auth: uncachedAuth
|
||||||
} = nextAuth;
|
} = nextAuth;
|
||||||
|
@ -1,40 +1,89 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Button, Divider, Navbar } from '@nextui-org/react';
|
import { Button, Divider, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Navbar } from '@nextui-org/react';
|
||||||
import { Bars3Icon, ChevronLeftIcon, XMarkIcon } from '@heroicons/react/20/solid';
|
import { Bars3Icon, ChevronLeftIcon, XMarkIcon } from '@heroicons/react/20/solid';
|
||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ThemeSwitcherDropdown, ThemeSwitcherSwitch } from '@/components/theme-switcher';
|
import { ThemeSwitcherDropdown, ThemeSwitcherSwitch } from '@/components/theme-switcher';
|
||||||
import { AdjustmentsHorizontalIcon } from '@heroicons/react/24/solid';
|
import { AdjustmentsHorizontalIcon } from '@heroicons/react/24/solid';
|
||||||
import { login, logout } from '@/actions/auth';
|
import { login, logout } from '@/actions/auth';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { UserPayload } from '@/types/user';
|
import { UserPayload } from '@/types/user';
|
||||||
import { MAIN_ROUTES, ROUTES, UserOnly } from '@/routes';
|
import { MAIN_ROUTES, ROUTES, Subroute, UserOnly } from '@/routes';
|
||||||
import { useUser } from '@/helpers/use-user';
|
import { useUser } from '@/helpers/use-user';
|
||||||
import { useBreakpoint } from '@/helpers/use-breakpoint';
|
import { useBreakpoint } from '@/helpers/use-breakpoint';
|
||||||
|
import { useCookies } from 'next-client-cookies';
|
||||||
|
import { UserPermissions } from '@/types/permissions';
|
||||||
|
import { hasPermission } from '@/helpers/permissions';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
export type HeaderSidebarProps = {
|
export type HeaderSidebarProps = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterUserOnly = (user: UserPayload | null | undefined, { userOnly }: { userOnly?: UserOnly }) => {
|
const filterRoute = (user: UserPayload | null | undefined, { userOnly, permissions }: { userOnly?: UserOnly, permissions?: (UserPermissions | UserPermissions[])[] }) => {
|
||||||
if (!userOnly) return true;
|
if (typeof userOnly === 'string' && !user?.[userOnly])
|
||||||
if (typeof userOnly === 'string') return user?.[userOnly];
|
return false;
|
||||||
return user;
|
if (typeof userOnly === 'boolean' && !user)
|
||||||
|
return false;
|
||||||
|
if (permissions?.length && !hasPermission(user?.permissions, ...permissions))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const path = usePathname();
|
const pathname = usePathname();
|
||||||
const params = useSearchParams();
|
|
||||||
const [isMenuOpen, setMenuOpen] = useState(false);
|
const [isMenuOpen, setMenuOpen] = useState(false);
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
|
const cookies = useCookies();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const from = params?.get('from');
|
const path = pathname === '/' ? (user?.homepage ?? '/dashboard') : pathname;
|
||||||
const filter = filterUserOnly.bind(null, user);
|
|
||||||
|
const from = cookies.get('actaeon-navigated-from');
|
||||||
|
const filter = filterRoute.bind(null, user);
|
||||||
const routeGroup = ROUTES.find(route => route.title === from || path?.startsWith(route.url))!;
|
const routeGroup = ROUTES.find(route => route.title === from || path?.startsWith(route.url))!;
|
||||||
|
|
||||||
|
const renderHeaderLink = (route: Subroute) => {
|
||||||
|
const linkStyle = path?.startsWith(route.url) ?
|
||||||
|
'font-semibold text-primary' : 'hover:text-secondary';
|
||||||
|
|
||||||
|
if (route.routes?.length)
|
||||||
|
return (<Dropdown key={route.url}>
|
||||||
|
<DropdownTrigger>
|
||||||
|
<Button className="bg-transparent p-0 m-0 min-h-0 min-w-0 h-auto overflow-visible" disableRipple>
|
||||||
|
<div className={`flex items-center cursor-pointer mr-4 transition text-lg ${linkStyle}`} >
|
||||||
|
{route.name}
|
||||||
|
<ChevronDownIcon className="h-5 ml-1 mt-1" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</DropdownTrigger>
|
||||||
|
<DropdownMenu>
|
||||||
|
{route.routes.filter(filter)
|
||||||
|
.map(route => (<DropdownItem key={route.url} className="[&:hover_*]:text-secondary"
|
||||||
|
onPress={() => {
|
||||||
|
router.push(route.url);
|
||||||
|
cookies.set('actaeon-navigated-from', routeGroup.title);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => router.prefetch(route.url)}>
|
||||||
|
<span className={`transition text-medium ${path?.startsWith(route.url) ? 'font-semibold text-primary' : ''}`}>{route.name}</span>
|
||||||
|
</DropdownItem>))}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>);
|
||||||
|
|
||||||
|
return (<Link
|
||||||
|
onClick={() => {
|
||||||
|
cookies.set('actaeon-navigated-from', routeGroup.title);
|
||||||
|
}}
|
||||||
|
href={`${route.url}`} key={route.url}
|
||||||
|
className={`mr-4 transition ${linkStyle}`}>
|
||||||
|
{route.name}
|
||||||
|
</Link>);
|
||||||
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
{/* begin sidebar */}
|
||||||
<div className={`fixed inset-0 w-full h-full z-[49] ${isMenuOpen ? '' : 'pointer-events-none'}`}>
|
<div className={`fixed inset-0 w-full h-full z-[49] ${isMenuOpen ? '' : 'pointer-events-none'}`}>
|
||||||
<div className={`transition bg-black z-[49] absolute inset-0 w-full h-full ${isMenuOpen ? 'bg-opacity-25' : 'bg-opacity-0 pointer-events-none'}`} onClick={() => setMenuOpen(false)} />
|
<div className={`transition bg-black z-[49] absolute inset-0 w-full h-full ${isMenuOpen ? 'bg-opacity-25' : 'bg-opacity-0 pointer-events-none'}`} onClick={() => setMenuOpen(false)} />
|
||||||
<div className={`dark flex flex-col text-white absolute p-6 top-0 h-full max-w-full w-96 bg-gray-950 z-[49] transition-all ${isMenuOpen ? 'left-0 shadow-2xl' : '-left-full'}`}>
|
<div className={`dark flex flex-col text-white absolute p-6 top-0 h-full max-w-full w-96 bg-gray-950 z-[49] transition-all ${isMenuOpen ? 'left-0 shadow-2xl' : '-left-full'}`}>
|
||||||
@ -53,16 +102,31 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
|||||||
<div>
|
<div>
|
||||||
{!filter(route) ? <div className={`select-none text-2xl ${route === routeGroup ? 'font-bold' : 'font-semibold'}`}>
|
{!filter(route) ? <div className={`select-none text-2xl ${route === routeGroup ? 'font-bold' : 'font-semibold'}`}>
|
||||||
{route.name}
|
{route.name}
|
||||||
</div> : <Link href={route.url} onClick={() => setMenuOpen(false)}
|
</div> : <Link href={route.url} onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
cookies.remove('actaeon-navigated-from');
|
||||||
|
}}
|
||||||
className={`text-2xl transition hover:text-secondary ${route === routeGroup ? 'font-bold' : 'font-semibold'}`}>
|
className={`text-2xl transition hover:text-secondary ${route === routeGroup ? 'font-bold' : 'font-semibold'}`}>
|
||||||
{route.name}
|
{route.name}
|
||||||
</Link>}
|
</Link>}
|
||||||
<div className="ml-2 mt-2">
|
<div className="ml-2 mt-2">
|
||||||
{route.routes?.filter(filter)?.map(subroute => <div className="mb-1" key={subroute.url}>
|
{route.routes?.filter(filter)?.map(subroute => <div className="mb-1" key={subroute.url}>
|
||||||
<Link href={subroute.url} onClick={() => setMenuOpen(false)}
|
{subroute.routes?.length ? <div
|
||||||
className={`text-xl transition hover:text-secondary ${path?.startsWith(subroute.url) ? 'font-semibold' : ''}`}>
|
className={`text-xl`}>
|
||||||
{subroute.name}
|
{subroute.name}
|
||||||
</Link>
|
<div className="flex flex-col ml-1.5 pl-3 border-l border-gray-500/25 mt-0.5">
|
||||||
|
{subroute.routes.filter(filter).map(route => (<Link href={route.url} key={route.url}
|
||||||
|
className={`text-[1.075rem] transition ${path?.startsWith(route.url) ? 'font-semibold text-primary' : 'hover:text-secondary'}`}>
|
||||||
|
{route.name}
|
||||||
|
</Link>))}
|
||||||
|
</div>
|
||||||
|
</div> : <Link href={subroute.url} onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
cookies.remove('actaeon-navigated-from');
|
||||||
|
}}
|
||||||
|
className={`text-xl transition ${path?.startsWith(subroute.url) ? 'font-semibold text-primary' : 'hover:text-secondary'}`}>
|
||||||
|
{subroute.name}
|
||||||
|
</Link>}
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,6 +147,9 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* end sidebar */}
|
||||||
|
|
||||||
|
{/* begin top navbar */}
|
||||||
<div className="flex flex-col flex-grow">
|
<div className="flex flex-col flex-grow">
|
||||||
<Navbar className="w-full fixed" classNames={{ wrapper: 'max-w-full p-0' }} shouldHideOnScroll={breakpoint === undefined} height="5.5rem">
|
<Navbar className="w-full fixed" classNames={{ wrapper: 'max-w-full p-0' }} shouldHideOnScroll={breakpoint === undefined} height="5.5rem">
|
||||||
<div className="flex p-6 items-center flex-shrink-0 w-full z-[48]">
|
<div className="flex p-6 items-center flex-shrink-0 w-full z-[48]">
|
||||||
@ -92,18 +159,10 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
|||||||
<span>{ routeGroup.title }</span>
|
<span>{ routeGroup.title }</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div className="mr-auto mt-1 hidden md:flex text-lg">
|
<div className="mr-auto mt-1 hidden md:flex text-lg">
|
||||||
{routeGroup.routes?.filter(filter).map(route =>
|
{routeGroup.routes?.filter(filter).map(renderHeaderLink)}
|
||||||
<Link href={route.url} key={route.url} className={`mr-4 transition ${path?.startsWith(route.url) ? 'font-semibold text-primary' : 'hover:text-secondary'}`}>
|
|
||||||
{route.name}
|
|
||||||
</Link>)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{routeGroup !== MAIN_ROUTES && <div className="mr-4 mt-1 hidden lg:flex text-lg transition hover:text-secondary">
|
{routeGroup !== MAIN_ROUTES && <div className="mr-4 mt-1 hidden [@media(min-width:1080px)]:flex text-lg">
|
||||||
{MAIN_ROUTES.routes.filter(filter).map(route => <Link
|
{MAIN_ROUTES.routes.filter(filter).map(renderHeaderLink)}
|
||||||
href={`${route.url}?from=${encodeURIComponent(routeGroup.title)}`} key={route.url}
|
|
||||||
className={`mr-4 transition ${path?.startsWith(route.url) ? 'font-semibold text-primary' : 'hover:text-secondary'}`}>
|
|
||||||
{route.name}
|
|
||||||
</Link>)}
|
|
||||||
</div>}
|
</div>}
|
||||||
<div className="hidden md:flex">
|
<div className="hidden md:flex">
|
||||||
<Link href={routeGroup === MAIN_ROUTES ? '/settings' : `/settings?from=${encodeURIComponent(routeGroup.title)}`}>
|
<Link href={routeGroup === MAIN_ROUTES ? '/settings' : `/settings?from=${encodeURIComponent(routeGroup.title)}`}>
|
||||||
@ -123,5 +182,6 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* end top navbar */}
|
||||||
</>)
|
</>)
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { auth } from '@/auth';
|
import { auth } from '@/auth';
|
||||||
import { SessionProvider } from 'next-auth/react';
|
import { SessionProvider } from 'next-auth/react';
|
||||||
|
import { CookiesProvider } from 'next-client-cookies/server';
|
||||||
|
|
||||||
export async function Providers({ children }: { children: ReactNode }) {
|
export async function Providers({ children }: { children: ReactNode }) {
|
||||||
return (<SessionProvider session={(await auth())} basePath={process.env.NEXT_PUBLIC_BASE_PATH + '/api/auth'}>
|
return (<SessionProvider session={(await auth())} basePath={process.env.NEXT_PUBLIC_BASE_PATH + '/api/auth'}>
|
||||||
{children}
|
<CookiesProvider>
|
||||||
|
{children}
|
||||||
|
</CookiesProvider>
|
||||||
</SessionProvider>);
|
</SessionProvider>);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@ import { createPool } from 'mysql2';
|
|||||||
import { Generated, Kysely, MysqlDialect } from 'kysely';
|
import { Generated, Kysely, MysqlDialect } from 'kysely';
|
||||||
|
|
||||||
const createDb = () => {
|
const createDb = () => {
|
||||||
|
if (process.env.NEXT_RUNTIME === 'edge')
|
||||||
|
return null;
|
||||||
|
|
||||||
if ((globalThis as any).db)
|
if ((globalThis as any).db)
|
||||||
return (globalThis as any).db as Kysely<GeneratedDB>;
|
return (globalThis as any).db as Kysely<GeneratedDB>;
|
||||||
|
|
||||||
@ -26,4 +29,4 @@ export type GeneratedDB = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const db = createDb();
|
export const db = createDb()!;
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { uncachedAuth } from '@/auth';
|
||||||
|
|
||||||
export function middleware(request: NextRequest) {
|
export const middleware = uncachedAuth((request: NextRequest) => {
|
||||||
const headers = new Headers(request.headers);
|
const headers = new Headers(request.headers);
|
||||||
headers.set('x-path', request.nextUrl.basePath + request.nextUrl.pathname);
|
headers.set('x-path', request.nextUrl.basePath + request.nextUrl.pathname);
|
||||||
return NextResponse.next({
|
|
||||||
|
const options: ResponseInit & { request: { headers: Headers; }; } = {
|
||||||
request: { headers }
|
request: { headers }
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
if (request.nextUrl.pathname === '/') {
|
||||||
|
const newUrl = request.nextUrl.clone();
|
||||||
|
newUrl.pathname = request.auth?.user?.homepage ?? '/dashboard';
|
||||||
|
return NextResponse.rewrite(newUrl, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.nextUrl.pathname === '/forbidden')
|
||||||
|
options.status = 403;
|
||||||
|
else if (request.nextUrl.pathname === '/auth/login' && request.nextUrl.searchParams.get('error'))
|
||||||
|
options.status = 401;
|
||||||
|
|
||||||
|
return NextResponse.next(options);
|
||||||
|
});
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import { UserPayload } from '@/types/user';
|
import { UserPayload } from '@/types/user';
|
||||||
|
import { UserPermissions } from '@/types/permissions';
|
||||||
|
|
||||||
export type UserOnly = boolean | keyof UserPayload;
|
export type UserOnly = boolean | keyof UserPayload;
|
||||||
|
|
||||||
type Subroute = {
|
export type Subroute = {
|
||||||
url: string,
|
url: string,
|
||||||
name: string,
|
name: string,
|
||||||
userOnly?: UserOnly
|
userOnly?: UserOnly,
|
||||||
|
permissions?: (UserPermissions | UserPermissions[])[],
|
||||||
|
routes?: Omit<Subroute, 'routes'>[]
|
||||||
};
|
};
|
||||||
|
|
||||||
type Route = {
|
export type Route = {
|
||||||
url: string,
|
url: string,
|
||||||
name: string,
|
name: string,
|
||||||
title: string,
|
title: string,
|
||||||
userOnly?: UserOnly,
|
userOnly?: UserOnly,
|
||||||
|
permissions?: (UserPermissions | UserPermissions[])[],
|
||||||
routes: Subroute[]
|
routes: Subroute[]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,6 +30,19 @@ export const MAIN_ROUTES: Route = {
|
|||||||
}, {
|
}, {
|
||||||
url: '/arcade',
|
url: '/arcade',
|
||||||
name: 'Arcades'
|
name: 'Arcades'
|
||||||
|
}, {
|
||||||
|
url: '/admin',
|
||||||
|
name: 'Admin',
|
||||||
|
permissions: [[UserPermissions.USERMOD, UserPermissions.SYSADMIN]],
|
||||||
|
routes: [{
|
||||||
|
url: '/admin/users',
|
||||||
|
name: 'Users',
|
||||||
|
permissions: [UserPermissions.USERMOD]
|
||||||
|
}, {
|
||||||
|
url: '/admin/system-config',
|
||||||
|
name: 'System Config',
|
||||||
|
permissions: [UserPermissions.SYSADMIN]
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
9
src/types/next-auth/index.d.ts
vendored
9
src/types/next-auth/index.d.ts
vendored
@ -14,3 +14,12 @@ declare module 'next-auth' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'next/server' {
|
||||||
|
interface NextRequest {
|
||||||
|
auth: {
|
||||||
|
user: UserPayload,
|
||||||
|
expires: string
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user