From f738b4dddec0f5b0bc699823db2b533c9d0679c1 Mon Sep 17 00:00:00 2001 From: Markusplay Date: Tue, 28 Jan 2025 22:12:02 +0200 Subject: [PATCH 1/9] add code of honor alert --- package-lock.json | 120 ++++++++++++++++++++++ package.json | 2 +- src/app/[locale]/(private)/sub-layout.tsx | 10 ++ src/components/code-of-honor-alert.tsx | 66 ++++++++++++ src/components/ui/alert-dialog.tsx | 16 ++- 5 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 src/components/code-of-honor-alert.tsx diff --git a/package-lock.json b/package-lock.json index 748b0b88..648d14b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15907,6 +15907,126 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.9.tgz", + "integrity": "sha512-/kfQifl3uLYi3DlwFlzCkgxe6fprJNLzzTUFknq3M5wGYicDIbdGlxUl6oHpVLJpBB/CBY3Y//gO6alz/K4NWA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.9.tgz", + "integrity": "sha512-tK/RyhCmOCiXQ9IVdFrBbZOf4/1+0RSuJkebXU2uMEsusS51TjIJO4l8ZmEijH9gZa0pJClvmApRHi7JuBqsRw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.9.tgz", + "integrity": "sha512-tS5eqwsp2nO7mzywRUuFYmefNZsUKM/mTG3exK2jIHv9TEVklE1SByB1KMhFkqlit1PxS9YK1tV8BOV90Wpbrw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.9.tgz", + "integrity": "sha512-8svpeTFNAMTUMKQbEzE8qRAwl9o7mNBv7LR1bmSkQvo1oy4WrNyZbhWsldOiKrc4mZ5dfQkGYsI9T75mIFMfeA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.9.tgz", + "integrity": "sha512-0HNulLWpKTB7H5BhHCkEhcRAnWUHeAYCftrrGw3QC18+ZywTdAoPv/zEqKy/0adqt+ks4JDdlgSQ1lNKOKjo0A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.9.tgz", + "integrity": "sha512-hhVFViPHLAVUJRNtwwm609p9ozWajOmRvzOZzzKXgiVGwx/CALxlMUeh+M+e0Zj6orENhWLZeilOPHpptuENsA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.9.tgz", + "integrity": "sha512-p/v6XlOdrk06xfN9z4evLNBqftVQUWiyduQczCwSj7hNh8fWTbzdVxsEiNOcajMXJbQiaX/ZzZdFgKVmmJnnGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.9.tgz", + "integrity": "sha512-IcW9dynWDjMK/0M05E3zopbRen7v0/yEaMZbHFOSS1J/w+8YG3jKywOGZWNp/eCUVtUUXs0PW+7Lpz8uLu+KQA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/package.json b/package.json index 4e82c51d..fe1dfefc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "^1.2.1", - "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-alert-dialog": "^1.1.5", "@radix-ui/react-aspect-ratio": "^1.1.0", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.2", diff --git a/src/app/[locale]/(private)/sub-layout.tsx b/src/app/[locale]/(private)/sub-layout.tsx index 7347fe87..8dde2f3b 100644 --- a/src/app/[locale]/(private)/sub-layout.tsx +++ b/src/app/[locale]/(private)/sub-layout.tsx @@ -1,3 +1,4 @@ +'use client'; import { Breadcrumb, BreadcrumbItem, @@ -8,6 +9,10 @@ import { } from '@/components/ui/breadcrumb'; import { cn } from '@/lib/utils'; import { useTranslations } from 'next-intl'; +import React from 'react'; +import { useLocalStorage } from '@/hooks/use-storage'; +import { User } from '@/types/user'; +import CodeOfHonorAlert from '@/components/code-of-honor-alert'; interface SubLayoutProps { children: React.ReactNode; @@ -18,6 +23,11 @@ interface SubLayoutProps { export const SubLayout = ({ children, breadcrumbs = [], pageTitle, className }: SubLayoutProps) => { const t = useTranslations('global.menu'); + const [user] = useLocalStorage('user'); + + if (!user?.codeOfHonorSignDate) { + return ; + } return (
diff --git a/src/components/code-of-honor-alert.tsx b/src/components/code-of-honor-alert.tsx new file mode 100644 index 00000000..f7555b04 --- /dev/null +++ b/src/components/code-of-honor-alert.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useServerErrorToast } from '@/hooks/use-server-error-toast'; +import React, { useState } from 'react'; +import { useLocalStorage } from '@/hooks/use-storage'; +import { User } from '@/types/user'; +import { acceptCodeOfHonor } from '@/actions/profile.actions'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { Link } from '@/i18n/routing'; +import { Paragraph } from '@/components/typography/paragraph'; + +export default function CodeOfHonorAlert() { + const t = useTranslations('private.profile'); + + const { errorToast } = useServerErrorToast(); + + const [loading, setLoading] = useState(false); + + const [, setUser] = useLocalStorage('user'); + + const handleAcceptCodeOfHonor = async () => { + setLoading(true); + const res = await acceptCodeOfHonor(); + setLoading(false); + + if (!res) { + errorToast(); + return; + } + setUser(res); + }; + + return ( + + + + {t('codeOfHonor.title')} + + {t.rich('codeOfHonor.content', { + documentsLink: (chunks) => {chunks}, + paragraph: (chunks) => {chunks}, + })} + + + + + На головну + + + {t('button.agree')} + + + + + ); +} diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx index 1f6dce21..1eaec4f2 100644 --- a/src/components/ui/alert-dialog.tsx +++ b/src/components/ui/alert-dialog.tsx @@ -25,10 +25,14 @@ const AlertDialogOverlay = React.forwardRef< )); AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; +interface AlertDialogContentProps extends React.ComponentPropsWithoutRef { + closable?: boolean; +} + const AlertDialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + AlertDialogContentProps +>(({ className, closable = true, ...props }, ref) => ( - - )} ); diff --git a/src/middleware.ts b/src/middleware.ts index 85da6a36..85f9db95 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -17,7 +17,7 @@ const composePathsRegExp = (paths: string[]) => RegExp(`^(/(${LOCALES.join('|')} const rootRegExp = new RegExp('^\/?$', 'i'); -const honorPageRegExp = composePathsRegExp(['/accept-honor']); +const codeOfHonorPathRegExp = composePathsRegExp(['/accept-code-of-honor']); const authPathRegExp = composePathsRegExp(['/login', '/password-reset/success', '/password-reset']); @@ -35,7 +35,7 @@ const publicPathRegExp = composePathsRegExp([ const isRoot = (request: NextRequest) => rootRegExp.test(request.nextUrl.pathname); const isPublicPath = (request: NextRequest) => publicPathRegExp.test(request.nextUrl.pathname); const isAuthPath = (request: NextRequest) => authPathRegExp.test(request.nextUrl.pathname); -const isAcceptHonorPath = (request: NextRequest) => honorPageRegExp.test(request.nextUrl.pathname); +const isAcceptCodeOfHonorPath = (request: NextRequest) => codeOfHonorPathRegExp.test(request.nextUrl.pathname); const isAuthenticated = (request: NextRequest) => { const cookie = request.cookies.get('token'); @@ -77,25 +77,28 @@ const authMiddleware = (request: NextRequest) => { return redirectWithIntl(request, '/'); } - return intlMiddleware(request); + return null; }; -const CoHMiddleware = async (request: NextRequest) => { +const codeOfHonorMiddleware = async (request: NextRequest) => { try { const user = await getUserDetails(); - const hasAcceptedCoH = !!user?.codeOfHonorSignDate; - - if ((!user?.studentProfile || hasAcceptedCoH) && isAcceptHonorPath(request)) { - return redirectWithIntl(request, '/'); + const hasAcceptedCodeOfHonor = !!user?.codeOfHonorSignDate; + + if (isAcceptCodeOfHonorPath(request)) { + if (!user?.studentProfile || hasAcceptedCodeOfHonor) { + return redirectWithIntl(request, '/'); + } + } else { + if (!!user?.studentProfile && !hasAcceptedCodeOfHonor) { + return redirectWithIntl(request, '/accept-code-of-honor'); + } } - if (!hasAcceptedCoH && !isAcceptHonorPath(request)) { - return redirectWithIntl(request, '/accept-honor'); - } + return intlMiddleware(request); } catch (error) { - return null; + return intlMiddleware(request); } - return intlMiddleware(request); }; export async function middleware(request: NextRequest) { @@ -112,10 +115,5 @@ export async function middleware(request: NextRequest) { return intlMiddleware(request); } - const honorRedirect = await CoHMiddleware(request); - if (honorRedirect) { - return honorRedirect; - } - - return (authMiddleware as any)(request); + return authMiddleware(request) || codeOfHonorMiddleware(request); }