mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 23:15:36 +00:00
feat: admin dashboard
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@types/react-toggle": "^4.0.5",
|
"@types/react-toggle": "^4.0.5",
|
||||||
"accept-language": "^3.0.18",
|
"accept-language": "^3.0.18",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"framer-motion": "^10.16.16",
|
"framer-motion": "^10.16.16",
|
||||||
"i18next": "^23.7.11",
|
"i18next": "^23.7.11",
|
||||||
"next": "^14.0.4",
|
"next": "^14.0.4",
|
||||||
@@ -4623,6 +4624,11 @@
|
|||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@types/react-toggle": "^4.0.5",
|
"@types/react-toggle": "^4.0.5",
|
||||||
"accept-language": "^3.0.18",
|
"accept-language": "^3.0.18",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"framer-motion": "^10.16.16",
|
"framer-motion": "^10.16.16",
|
||||||
"i18next": "^23.7.11",
|
"i18next": "^23.7.11",
|
||||||
"next": "^14.0.4",
|
"next": "^14.0.4",
|
||||||
@@ -57,4 +58,4 @@
|
|||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": "eslint --cache --fix"
|
"*.js": "eslint --cache --fix"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `user` ADD COLUMN `is_admin` BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -17,6 +17,7 @@ model User {
|
|||||||
email String @unique
|
email String @unique
|
||||||
name String?
|
name String?
|
||||||
image String?
|
image String?
|
||||||
|
is_admin Boolean @default(false)
|
||||||
|
|
||||||
categories Category[]
|
categories Category[]
|
||||||
links Link[]
|
links Link[]
|
||||||
|
|||||||
9
public/locales/en/admin.json
Normal file
9
public/locales/en/admin.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"role": "Role",
|
||||||
|
"created_at": "Created at",
|
||||||
|
"updated_at": "Updated at",
|
||||||
|
"admin": "Administrator",
|
||||||
|
"user": "User",
|
||||||
|
"users": "Users",
|
||||||
|
"stats": "Statistics"
|
||||||
|
}
|
||||||
9
public/locales/fr/admin.json
Normal file
9
public/locales/fr/admin.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"role": "Rôle",
|
||||||
|
"created_at": "Création",
|
||||||
|
"updated_at": "Mise à jour",
|
||||||
|
"admin": "Administrateur",
|
||||||
|
"user": "Utilisateur",
|
||||||
|
"users": "Utilisateurs",
|
||||||
|
"stats": "Statistiques"
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import PATHS from 'constants/paths';
|
import PATHS from 'constants/paths';
|
||||||
|
import useUser from 'hooks/useUser';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import LinkTag from 'next/link';
|
import LinkTag from 'next/link';
|
||||||
@@ -6,11 +7,12 @@ import RoundedImage from '../RoundedImage/RoundedImage';
|
|||||||
import styles from './navbar.module.scss';
|
import styles from './navbar.module.scss';
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const { data, status } = useSession();
|
const { status } = useSession();
|
||||||
|
const { user } = useUser();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const avatarLabel = t('common:avatar', {
|
const avatarLabel = t('common:avatar', {
|
||||||
name: data?.user?.name,
|
name: user?.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -27,12 +29,17 @@ export default function Navbar() {
|
|||||||
</li>
|
</li>
|
||||||
{status === 'authenticated' ? (
|
{status === 'authenticated' ? (
|
||||||
<>
|
<>
|
||||||
|
{user?.is_admin && (
|
||||||
|
<li>
|
||||||
|
<LinkTag href={PATHS.ADMIN}>Admin</LinkTag>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li className={styles['user']}>
|
<li className={styles['user']}>
|
||||||
<RoundedImage
|
<RoundedImage
|
||||||
src={data.user.image}
|
src={user?.image}
|
||||||
alt={avatarLabel}
|
alt={avatarLabel}
|
||||||
/>
|
/>
|
||||||
{data.user.name}
|
{user?.name}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<LinkTag href={PATHS.LOGOUT}>{t('common:logout')}</LinkTag>
|
<LinkTag href={PATHS.LOGOUT}>{t('common:logout')}</LinkTag>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export default function RoundedImage({
|
|||||||
height = 24,
|
height = 24,
|
||||||
alt,
|
alt,
|
||||||
}: (typeof Image)['defaultProps']) {
|
}: (typeof Image)['defaultProps']) {
|
||||||
|
if (!src) {
|
||||||
|
console.warn('No src provided');
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles['rounded-image']}>
|
<div className={styles['rounded-image']}>
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
.rounded-image {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded-image img {
|
.rounded-image img {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
|
import RoundedImage from 'components/RoundedImage/RoundedImage';
|
||||||
import SettingsModal from 'components/Settings/SettingsModal';
|
import SettingsModal from 'components/Settings/SettingsModal';
|
||||||
import { useSession } from 'next-auth/react';
|
import PATHS from 'constants/paths';
|
||||||
|
import useUser from 'hooks/useUser';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import Image from 'next/image';
|
import Link from 'next/link';
|
||||||
|
import { MdOutlineAdminPanelSettings } from 'react-icons/md';
|
||||||
import styles from './user-card.module.scss';
|
import styles from './user-card.module.scss';
|
||||||
|
|
||||||
export default function UserCard() {
|
export default function UserCard() {
|
||||||
const { data } = useSession({ required: true });
|
const { user } = useUser();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const avatarLabel = t('common:avatar', {
|
const avatarLabel = t('common:avatar', {
|
||||||
name: data.user.name,
|
name: user.name,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className={styles['user-card-wrapper']}>
|
<div className={styles['user-card-wrapper']}>
|
||||||
<div className={styles['user-card']}>
|
<div className={styles['user-card']}>
|
||||||
<Image
|
<RoundedImage
|
||||||
src={data.user.image}
|
src={user.image}
|
||||||
width={28}
|
width={28}
|
||||||
height={28}
|
height={28}
|
||||||
alt={avatarLabel}
|
alt={avatarLabel}
|
||||||
title={avatarLabel}
|
title={avatarLabel}
|
||||||
/>
|
/>
|
||||||
{data.user.name}
|
{user.name}
|
||||||
</div>
|
</div>
|
||||||
|
{user.is_admin && (
|
||||||
|
<Link href={PATHS.ADMIN}>
|
||||||
|
<MdOutlineAdminPanelSettings />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<SettingsModal />
|
<SettingsModal />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const PATHS = {
|
|||||||
HOME: '/',
|
HOME: '/',
|
||||||
PRIVACY: '/privacy',
|
PRIVACY: '/privacy',
|
||||||
TERMS: '/terms',
|
TERMS: '/terms',
|
||||||
|
ADMIN: '/admin',
|
||||||
CATEGORY: {
|
CATEGORY: {
|
||||||
CREATE: '/category/create',
|
CREATE: '/category/create',
|
||||||
EDIT: '/category/edit',
|
EDIT: '/category/edit',
|
||||||
|
|||||||
8
src/hooks/useUser.tsx
Normal file
8
src/hooks/useUser.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { User } from '@prisma/client';
|
||||||
|
import { Session } from 'next-auth';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
|
||||||
|
export default function useUser() {
|
||||||
|
const { data } = useSession();
|
||||||
|
return data as Session & { user?: User };
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import home from '../../public/locales/en/home.json';
|
|||||||
import login from '../../public/locales/en/login.json';
|
import login from '../../public/locales/en/login.json';
|
||||||
import privacy from '../../public/locales/en/privacy.json';
|
import privacy from '../../public/locales/en/privacy.json';
|
||||||
import terms from '../../public/locales/en/terms.json';
|
import terms from '../../public/locales/en/terms.json';
|
||||||
|
import admin from '../../public/locales/en/admin.json';
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
common,
|
common,
|
||||||
@@ -10,6 +11,7 @@ const resources = {
|
|||||||
home,
|
home,
|
||||||
privacy,
|
privacy,
|
||||||
terms,
|
terms,
|
||||||
|
admin,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default resources;
|
export default resources;
|
||||||
|
|||||||
18
src/lib/user/getUsers.ts
Normal file
18
src/lib/user/getUsers.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Session } from 'next-auth';
|
||||||
|
import prisma from 'utils/prisma';
|
||||||
|
|
||||||
|
export default async function getUsers(session: Session) {
|
||||||
|
if (!session?.user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await prisma.user.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
categories: true,
|
||||||
|
links: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import ErrorBoundary from 'components/ErrorBoundary/ErrorBoundary';
|
import ErrorBoundary from 'components/ErrorBoundary/ErrorBoundary';
|
||||||
import * as Keys from 'constants/keys';
|
import * as Keys from 'constants/keys';
|
||||||
import PATHS from 'constants/paths';
|
import PATHS from 'constants/paths';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import 'dayjs/locale/en';
|
||||||
|
import 'dayjs/locale/fr';
|
||||||
import { SessionProvider } from 'next-auth/react';
|
import { SessionProvider } from 'next-auth/react';
|
||||||
import { appWithTranslation, useTranslation } from 'next-i18next';
|
import { appWithTranslation, useTranslation } from 'next-i18next';
|
||||||
import { DefaultSeo } from 'next-seo';
|
import { DefaultSeo } from 'next-seo';
|
||||||
@@ -10,12 +13,15 @@ import 'nprogress/nprogress.css';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import 'styles/globals.scss';
|
import 'styles/globals.scss';
|
||||||
|
import 'styles/table.scss';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import nextI18nextConfig from '../../next-i18next.config';
|
import nextI18nextConfig from '../../next-i18next.config';
|
||||||
|
|
||||||
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
// TODO: use dynamic locale import
|
||||||
|
dayjs.locale(i18n.language);
|
||||||
|
|
||||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, () => router.push(PATHS.HOME), {
|
useHotkeys(Keys.CLOSE_SEARCH_KEY, () => router.push(PATHS.HOME), {
|
||||||
enabled: router.pathname !== PATHS.HOME,
|
enabled: router.pathname !== PATHS.HOME,
|
||||||
|
|||||||
140
src/pages/admin.tsx
Normal file
140
src/pages/admin.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { User } from '@prisma/client';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import Navbar from 'components/Navbar/Navbar';
|
||||||
|
import PageTransition from 'components/PageTransition';
|
||||||
|
import RoundedImage from 'components/RoundedImage/RoundedImage';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import { getServerSideTranslation } from 'i18n';
|
||||||
|
import getUsers from 'lib/user/getUsers';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
|
import 'react-tabs/style/react-tabs.css';
|
||||||
|
import styles from 'styles/admin.module.scss';
|
||||||
|
import { withAuthentication } from 'utils/session';
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
type UserExtended = User & { _count: { categories: number; links: number } };
|
||||||
|
|
||||||
|
export default function AdminDashboard({ users }: { users: UserExtended[] }) {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
return (
|
||||||
|
<PageTransition className={styles['admin']}>
|
||||||
|
<Navbar />
|
||||||
|
<Tabs>
|
||||||
|
<TabList>
|
||||||
|
<Tab>{t('common:users')}</Tab>
|
||||||
|
<Tab>{t('common:stats')}</Tab>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<p>
|
||||||
|
{users.length} {t('admin:users')}
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<TH>#</TH>
|
||||||
|
<TH>{t('common:name')}</TH>
|
||||||
|
<TH>{t('common:email')}</TH>
|
||||||
|
<TH>{t('common:category.categories')}</TH>
|
||||||
|
<TH>{t('common:link.links')}</TH>
|
||||||
|
<TH>{t('admin:role')}</TH>
|
||||||
|
<TH>{t('admin:created_at')}</TH>
|
||||||
|
<TH>{t('admin:updated_at')}</TH>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{users.length !== 0 &&
|
||||||
|
users.map(
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
image,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
_count,
|
||||||
|
is_admin,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
}) => (
|
||||||
|
<tr key={id}>
|
||||||
|
<TD>{id}</TD>
|
||||||
|
<TD fixed>
|
||||||
|
{image && (
|
||||||
|
<RoundedImage
|
||||||
|
src={image}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
alt={name}
|
||||||
|
title={name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{name ?? '-'}
|
||||||
|
</TD>
|
||||||
|
<TD>{email}</TD>
|
||||||
|
<TD>{_count.categories}</TD>
|
||||||
|
<TD>{_count.links}</TD>
|
||||||
|
<TD>
|
||||||
|
{is_admin ? (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
{t('admin:admin')}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: 'green' }}>
|
||||||
|
{t('admin:user')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TD>
|
||||||
|
<TD>{dayjs(createdAt).fromNow()}</TD>
|
||||||
|
<TD>{dayjs(updatedAt).fromNow()}</TD>
|
||||||
|
</tr>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>{/* // stats */}</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</PageTransition>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableItem = { children: ReactNode; fixed?: boolean };
|
||||||
|
function TH({ children, fixed = false }: TableItem) {
|
||||||
|
return (
|
||||||
|
<th>
|
||||||
|
<div className={clsx('cell', fixed && 'fixed')}>{children}</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TD({ children, fixed = false }: TableItem) {
|
||||||
|
return (
|
||||||
|
<td>
|
||||||
|
<div className={clsx('cell', fixed && 'fixed')}>{children}</div>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps = withAuthentication(
|
||||||
|
async ({ session, locale, user }) => {
|
||||||
|
if (!user.is_admin) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: '/',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await getUsers(session);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
session,
|
||||||
|
...(await getServerSideTranslation(locale, ['admin'])),
|
||||||
|
users: JSON.parse(JSON.stringify(users)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -39,7 +39,11 @@ export const authOptions = {
|
|||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
where: { email: session.user.email },
|
where: { email: session.user.email },
|
||||||
});
|
});
|
||||||
return user ? session : undefined;
|
if (user) {
|
||||||
|
session.user = JSON.parse(JSON.stringify(user));
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
},
|
},
|
||||||
async signIn({ account: accountParam, user }) {
|
async signIn({ account: accountParam, user }) {
|
||||||
if (!checkProvider(accountParam.provider)) {
|
if (!checkProvider(accountParam.provider)) {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export default function Terms() {
|
|||||||
<h3>{t('terms:personal_data.collect.title')}</h3>
|
<h3>{t('terms:personal_data.collect.title')}</h3>
|
||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
// @ts-ignore
|
|
||||||
i18nKey='terms:personal_data.collect.description'
|
i18nKey='terms:personal_data.collect.description'
|
||||||
components={{ a: <LinkTag href={PATHS.PRIVACY} /> }}
|
components={{ a: <LinkTag href={PATHS.PRIVACY} /> }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
5
src/styles/admin.module.scss
Normal file
5
src/styles/admin.module.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.admin {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
32
src/styles/table.scss
Normal file
32
src/styles/table.scss
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
@import 'colors.scss';
|
||||||
|
|
||||||
|
table {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: $lightest-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .cell {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
&:not(.fixed) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
|
import { User } from '@prisma/client';
|
||||||
|
import PATHS from 'constants/paths';
|
||||||
|
import { getServerSideTranslation } from 'i18n';
|
||||||
|
import getUser from 'lib/user/getUser';
|
||||||
import {
|
import {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
NextApiRequest,
|
NextApiRequest,
|
||||||
NextApiResponse,
|
NextApiResponse,
|
||||||
} from 'next';
|
} from 'next';
|
||||||
|
import { Session } from 'next-auth';
|
||||||
import { getServerSession } from 'next-auth/next';
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
|
||||||
import PATHS from 'constants/paths';
|
|
||||||
import getUser from 'lib/user/getUser';
|
|
||||||
import { authOptions } from 'pages/api/auth/[...nextauth]';
|
import { authOptions } from 'pages/api/auth/[...nextauth]';
|
||||||
|
|
||||||
export async function getSession(req: NextApiRequest, res: NextApiResponse) {
|
export async function getSession(req: NextApiRequest, res: NextApiResponse) {
|
||||||
return await getServerSession(req, res, authOptions);
|
return await getServerSession(req, res, authOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withAuthentication(serverSidePropsFunc) {
|
type AuthenticationContext = GetServerSidePropsContext & {
|
||||||
|
session: Session;
|
||||||
|
user: User;
|
||||||
|
};
|
||||||
|
type AuthenticationReturnType = {
|
||||||
|
redirect?: { destination: string };
|
||||||
|
props?: Awaited<ReturnType<typeof getServerSideTranslation>> & {
|
||||||
|
session: Session;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function withAuthentication(
|
||||||
|
serverSidePropsFunc: (
|
||||||
|
context: AuthenticationContext,
|
||||||
|
) => Promise<AuthenticationReturnType>,
|
||||||
|
): (context: GetServerSidePropsContext) => Promise<AuthenticationReturnType> {
|
||||||
return async (context: GetServerSidePropsContext) => {
|
return async (context: GetServerSidePropsContext) => {
|
||||||
const { req, res } = context;
|
const { req, res } = context;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user