diff --git a/src/actions/menu.actions.ts b/src/actions/menu.actions.ts index c8e5a1e1..8851ad2b 100644 --- a/src/actions/menu.actions.ts +++ b/src/actions/menu.actions.ts @@ -7,11 +7,22 @@ import { cookies } from 'next/headers'; import { getUserDetails } from './auth.actions'; import { MenuItemMeta } from '@/types/menu-item-meta'; import { MODULES } from '@/lib/constants/modules'; +import { ProfileArea } from '@/types/enums/profile-area'; +import { Module } from '@/types/module'; const OLD_CAMPUS_URL = process.env.OLD_CAMPUS_URL; -const composeUrlToOldCampus = (profileArea: string, mode: string) => { - return `${OLD_CAMPUS_URL}/${profileArea}/index.php?mode=${mode}`; +const OLD_CAMPUS_PROFILE_AREA = { + [ProfileArea.Employee]: 'tutor', + [ProfileArea.Student]: 'student', +}; + +const composeUrl = (module: Module, profileArea: ProfileArea) => { + if (module.isExternal) { + return `${OLD_CAMPUS_URL}/${OLD_CAMPUS_PROFILE_AREA[profileArea]}/index.php?mode=${module.name}`; + } + + return `/module/${module.name}`; }; const getStaticMenuItems = async (): Promise => { @@ -45,12 +56,6 @@ const getStaticMenuItems = async (): Promise => { url: '/feedback', isExternal: false, }, - { - name: 'employment-system', - title: t('employment-system'), - url: '/employment-system', - isExternal: false, - }, { name: 'settings', title: t('settings'), @@ -83,14 +88,14 @@ const getModuleMenuItems = async (): Promise => { const t = await getTranslations('global.modules'); - const areaType = userDetails.studentProfile ? 'student' : 'tutor'; + const profileArea = userDetails.studentProfile ? ProfileArea.Student : ProfileArea.Employee; const availableModules = MODULES.filter((module) => jwtPayload.modules.includes(module.name)); return availableModules.map((module) => { return { name: module.name, title: t(module.name), - url: composeUrlToOldCampus(areaType, module.name), + url: composeUrl(module, profileArea), isExternal: module.isExternal, } satisfies MenuItemMeta; }); diff --git a/src/app/[locale]/(private)/employment-system/page.tsx b/src/app/[locale]/(private)/module/employment/page.tsx similarity index 91% rename from src/app/[locale]/(private)/employment-system/page.tsx rename to src/app/[locale]/(private)/module/employment/page.tsx index 92031bf2..ceb710dd 100644 --- a/src/app/[locale]/(private)/employment-system/page.tsx +++ b/src/app/[locale]/(private)/module/employment/page.tsx @@ -1,5 +1,5 @@ import { Heading1 } from '@/components/typography/headers'; -import { SubLayout } from '../sub-layout'; +import { SubLayout } from '../../sub-layout'; import { useTranslations } from 'next-intl'; import { getTranslations } from 'next-intl/server'; import { Paragraph } from '@/components/typography/paragraph'; @@ -16,7 +16,7 @@ export async function generateMetadata({ params: { locale } }: any) { }; } -export default function EmploymentSystemPage() { +export default function EmploymentPage() { const t = useTranslations(INTL_NAMESPACE); return ( diff --git a/src/components/app-sidebar/menu-icon.tsx b/src/components/app-sidebar/menu-icon.tsx index 9d972e1a..6701131a 100644 --- a/src/components/app-sidebar/menu-icon.tsx +++ b/src/components/app-sidebar/menu-icon.tsx @@ -1,4 +1,18 @@ -import { Gear, House, UserCircle, CircleWavyCheck, Scroll, Exam, ChatCenteredText, BagSimple } from '@/app/images'; +import { + Gear, + House, + UserCircle, + CircleWavyCheck, + Exam, + ChatCenteredText, + BagSimple, + ListNumbers, + GraduationCap, + Books, + ChartBarHorizontal, + IdentificationBadge, + Scroll, +} from '@/app/images'; // TODO: Add icons for modules export const menuIcon: Map = new Map([ @@ -7,7 +21,14 @@ export const menuIcon: Map = new Map([ ['current-superintendence', CircleWavyCheck], ['certification-results', Exam], ['feedback', ChatCenteredText], - ['employment-system', BagSimple], + ['employment', BagSimple], ['settings', Gear], + ['sdchoice2021admin', ListNumbers], + ['sdchoice2021moderator', ListNumbers], + ['sdchoice2021nmv', ListNumbers], + ['vedomost', GraduationCap], + ['mob', Books], + ['vote', ChartBarHorizontal], + ['contacts', IdentificationBadge], ['notice-board', Scroll], ]); diff --git a/src/lib/constants/modules.ts b/src/lib/constants/modules.ts index b2988d99..4950d9c9 100644 --- a/src/lib/constants/modules.ts +++ b/src/lib/constants/modules.ts @@ -49,4 +49,5 @@ export const MODULES: Module[] = [ { name: 'sdchoice2021admin', isExternal: true }, { name: 'sdchoice2021moderator', isExternal: true }, { name: 'sdchoice2021nmv', isExternal: true }, + { name: 'employment', isExternal: false }, ]; diff --git a/src/messages/en.json b/src/messages/en.json index 166d47bf..899e6f78 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -74,19 +74,12 @@ "adminpanel": "Admin panel", "sdchoice2021admin": "Discipline selection (Admin)", "sdchoice2021moderator": "Discipline selection (Moderator)", - "sdchoice2021nmv": "Discipline selection (NMV)" + "sdchoice2021nmv": "Discipline selection (NMV)", + "employment": "Employment system" }, "menu": { "main": "Home", "profile": "Profile", - "current-superintendence": "Current superintendence", - "certification-results": "Certification results", - "disciplines": "Disciplines", - "term": "Term", - "methodological-support": "Methodological support", - "poll": "Poll", - "curriculum": "Curriculum", - "lecturer-contacts": "Contacts", "feedback": "Send feedback", "settings": "Settings", "user-manual": "User manual", @@ -95,7 +88,6 @@ "documents": "KPI documents", "terms-of-service": "Terms of service", "contacts": "Contacts", - "employment-system": "Employment system", "notice-board": "Notice board" }, "user-category": { diff --git a/src/messages/uk.json b/src/messages/uk.json index 88f438c4..c363f489 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -74,19 +74,12 @@ "adminpanel": "Адміністративна панель", "sdchoice2021admin": "Вибір дисциплін (адм.)", "sdchoice2021moderator": "Вибір дисциплін (мод.)", - "sdchoice2021nmv": "Вибір дисциплін (НМВ)" + "sdchoice2021nmv": "Вибір дисциплін (НМВ)", + "employment": "Система працевлаштування" }, "menu": { "main": "Головна", "profile": "Профіль", - "current-superintendence": "Поточний контроль", - "certification-results": "Результати атестації", - "disciplines": "Вибір дисциплін", - "term": "Сесія", - "methodological-support": "Метод. забезпечення", - "poll": "Опитування", - "curriculum": "Навчальний план", - "lecturer-contacts": "Контакти", "feedback": "Відправити відгук", "settings": "Налаштування", "user-manual": "Інструкція користувача", @@ -95,7 +88,6 @@ "documents": "Документи КПІ", "terms-of-service": "Правила використання", "contacts": "Контакти", - "employment-system": "Система працевлаштування", "notice-board": "Оголошення" }, "user-category": { diff --git a/src/middleware/authorization.middleware.ts b/src/middleware/authorization.middleware.ts index 76972b86..c4230b62 100644 --- a/src/middleware/authorization.middleware.ts +++ b/src/middleware/authorization.middleware.ts @@ -1,21 +1,17 @@ import { NextRequest } from 'next/server'; -import { MODULES_BASE_PATHS } from './contants'; -import { getAuthInfo, gotoRoot, matchesAnyUrl } from './utils'; +import { MODULES_BASE_PATH } from './contants'; +import { getAuthInfo, gotoNotFound, matchesUrl } from './utils'; import UrlPattern from 'url-pattern'; -import { getUserDetails } from '@/actions/auth.actions'; -import { ProfileArea } from '@/types/enums/profile-area'; import { intlMiddleware } from './intl.middleware'; const isAuthorized = async (request: NextRequest) => { const payload = getAuthInfo(request); - // TODO: Refactor to not use actions here - const user = await getUserDetails(); - if (!payload || !user) { + if (!payload) { return false; } - const pattern = new UrlPattern('/:locale/:profileArea/module/:module(/*)'); + const pattern = new UrlPattern('/:locale/module/:module(/*)'); const match = pattern.match(request.nextUrl.pathname); @@ -23,24 +19,21 @@ const isAuthorized = async (request: NextRequest) => { return false; } - const { profileArea, module } = match; - - if (!!user.studentProfile && profileArea !== ProfileArea.Student) { - return false; - } + const { module } = match; return payload.modules.includes(module); }; -export const authorizationMiddleware = (request: NextRequest) => { +export const authorizationMiddleware = async (request: NextRequest) => { // Skip authorization check for non-restricted pages - if (!matchesAnyUrl(request, MODULES_BASE_PATHS, false)) { + if (!matchesUrl(request, MODULES_BASE_PATH, false)) { return intlMiddleware(request); } - if (!isAuthorized(request)) { - // Do we need a dedicated "Unauthorized" page? - return gotoRoot(request); + const authorized = await isAuthorized(request); + + if (!authorized) { + return gotoNotFound(request); } return intlMiddleware(request); diff --git a/src/middleware/contants.ts b/src/middleware/contants.ts index a17d5058..74d0961f 100644 --- a/src/middleware/contants.ts +++ b/src/middleware/contants.ts @@ -1,7 +1,6 @@ -import { ProfileArea } from '@/types/enums/profile-area'; - export const ROOT_PATH = '/'; +export const NOT_FOUND_PATH = '/not-found'; export const LOGIN_PATH = '/login'; export const PUBLIC_PATHS = ['/login', '/password-reset', '/curator-search', '/complaints', '/support', '/faq']; -export const MODULES_BASE_PATHS = [`/${ProfileArea.Student}/module`, `/${ProfileArea.Employee}/module`]; +export const MODULES_BASE_PATH = '/module'; export const CODE_OF_HONOR_PATH = '/accept-code-of-honor'; diff --git a/src/middleware/utils.ts b/src/middleware/utils.ts index 12eda7cc..ebee4d5d 100644 --- a/src/middleware/utils.ts +++ b/src/middleware/utils.ts @@ -2,7 +2,7 @@ import { LOCALES } from '@/i18n/routing'; import { NextRequest, NextResponse } from 'next/server'; import { trim } from 'radash'; import UrlPattern from 'url-pattern'; -import { LOGIN_PATH, ROOT_PATH } from './contants'; +import { LOGIN_PATH, NOT_FOUND_PATH, ROOT_PATH } from './contants'; import { getJWTPayload } from '@/lib/jwt'; import { CampusJwtPayload } from '@/types/campus-jwt-payload'; @@ -17,6 +17,7 @@ export const redirectWithIntl = (request: NextRequest, path: string) => { export const gotoRoot = (request: NextRequest) => redirectWithIntl(request, ROOT_PATH); export const gotoLogin = (request: NextRequest) => redirectWithIntl(request, LOGIN_PATH); +export const gotoNotFound = (request: NextRequest) => NextResponse.rewrite(new URL(NOT_FOUND_PATH, request.url)); export const isRoot = (request: NextRequest) => { const pattern = new UrlPattern('(/)'); @@ -24,8 +25,8 @@ export const isRoot = (request: NextRequest) => { return !!pattern.match(request.nextUrl.pathname); }; -export const matchesUrl = (request: NextRequest, url: string, strict = false) => { - const pattern = new UrlPattern(`/:locale${url}${strict ? '(/*)' : ''}`); +export const matchesUrl = (request: NextRequest, url: string, strict = true) => { + const pattern = new UrlPattern(`/:locale${url}${strict ? '' : '(/*)'}`); const match = pattern.match(request.nextUrl.pathname); @@ -36,7 +37,7 @@ export const matchesUrl = (request: NextRequest, url: string, strict = false) => return LOCALES.includes(match.locale); }; -export const matchesAnyUrl = (request: NextRequest, urls: string[], strict = false) => +export const matchesAnyUrl = (request: NextRequest, urls: string[], strict = true) => urls.some((url) => matchesUrl(request, url, strict)); export const getAuthInfo = (request: NextRequest) => {