diff --git a/package-lock.json b/package-lock.json index 748b0b88..05184172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2355,6 +2355,126 @@ "glob": "10.3.10" } }, + "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" + } + }, "node_modules/@next/swc-win32-x64-msvc": { "version": "14.2.9", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.9.tgz", @@ -8673,6 +8793,8 @@ }, "node_modules/embla-carousel-react": { "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.3.0.tgz", + "integrity": "sha512-P1FlinFDcIvggcErRjNuVqnUR8anyo8vLMIH8Rthgofw7Nj8qTguCa2QjFAbzxAUTQTPNNjNL7yt0BGGinVdFw==", "license": "MIT", "dependencies": { "embla-carousel": "8.3.0", @@ -8684,6 +8806,8 @@ }, "node_modules/embla-carousel-reactive-utils": { "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.3.0.tgz", + "integrity": "sha512-EYdhhJ302SC4Lmkx8GRsp0sjUhEN4WyFXPOk0kGu9OXZSRMmcBlRgTvHcq8eKJE1bXWBsOi1T83B+BSSVZSmwQ==", "license": "MIT", "peerDependencies": { "embla-carousel": "8.3.0" diff --git a/src/actions/announcement.actions.ts b/src/actions/announcement.actions.ts new file mode 100644 index 00000000..93fcff59 --- /dev/null +++ b/src/actions/announcement.actions.ts @@ -0,0 +1,18 @@ +'use server'; + +import { campusFetch } from '@/lib/client'; +import { Announcement } from '@/types/announcement'; + +export const getAnnouncements = async () => { + try { + const response = await campusFetch('announcements'); + + if (!response.ok) { + return []; + } + + return response.json(); + } catch (error) { + return []; + } +}; diff --git a/src/actions/auth.actions.ts b/src/actions/auth.actions.ts index 022fa164..79acea05 100644 --- a/src/actions/auth.actions.ts +++ b/src/actions/auth.actions.ts @@ -6,6 +6,7 @@ import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; import { campusFetch } from '@/lib/client'; import { User } from '@/types/user'; +import { AuthResponse } from '@/types/auth-response'; const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN; const OLD_CAMPUS_URL = process.env.OLD_CAMPUS_URL; @@ -18,7 +19,7 @@ export async function loginWithCredentials(username: string, password: string, r grant_type: 'password', }; - const response = await campusFetch('oauth/token', { + const response = await campusFetch('oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -36,8 +37,7 @@ export async function loginWithCredentials(username: string, password: string, r return null; } - const userResponse = await campusFetch('profile', { - method: 'GET', + const userResponse = await campusFetch('profile', { headers: { Authorization: `Bearer ${jsonResponse.access_token}`, }, @@ -52,7 +52,7 @@ export async function loginWithCredentials(username: string, password: string, r const token = JWT.decode(access_token) as { exp: number }; const tokenExpiresAt = new Date(token.exp * 1000); - const user: User = await userResponse.json(); + const user = await userResponse.json(); const expires = rememberMe ? tokenExpiresAt : undefined; @@ -94,10 +94,8 @@ export async function resetPassword(username: string, recaptchaToken: string) { } } -export async function getUserDetails(): Promise { - const userResponse = await campusFetch('profile', { - method: 'GET', - }); +export async function getUserDetails() { + const userResponse = await campusFetch('profile'); if (!userResponse.ok) { return null; diff --git a/src/actions/group.actions.ts b/src/actions/group.actions.ts index 8768f031..df50bbd0 100644 --- a/src/actions/group.actions.ts +++ b/src/actions/group.actions.ts @@ -4,13 +4,11 @@ import { campusFetch } from '@/lib/client'; import { Group } from '@/types/group'; import queryString from 'query-string'; -export async function searchByGroupName(search: string): Promise { +export async function searchByGroupName(search: string) { try { const query = queryString.stringify({ name: search }); - const response = await campusFetch(`group/find?${query}`, { - method: 'GET', - }); + const response = await campusFetch(`group/find?${query}`); if (response.status < 200 || response.status >= 300) { return []; diff --git a/src/actions/profile.actions.ts b/src/actions/profile.actions.ts index c1582ae7..6656bd59 100644 --- a/src/actions/profile.actions.ts +++ b/src/actions/profile.actions.ts @@ -5,9 +5,9 @@ import { campusFetch } from '@/lib/client'; import { revalidatePath } from 'next/cache'; import { getUserDetails } from '@/actions/auth.actions'; -export async function getContacts(): Promise { +export async function getContacts() { try { - const response = await campusFetch('profile/contacts'); + const response = await campusFetch('profile/contacts'); return response.json(); } catch (error) { @@ -15,9 +15,9 @@ export async function getContacts(): Promise { } } -export async function getContactTypes(): Promise { +export async function getContactTypes() { try { - const response = await campusFetch('profile/contacts/types'); + const response = await campusFetch('profile/contacts/types'); return response.json(); } catch (error) { diff --git a/src/app/[locale]/(private)/cards/announcements-card/announcement-slide.tsx b/src/app/[locale]/(private)/cards/announcements-card/announcement-slide.tsx new file mode 100644 index 00000000..7a2219c0 --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/announcement-slide.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import Link from 'next/link'; +import { Heading3 } from '@/components/typography/headers'; +import { Paragraph } from '@/components/typography/paragraph'; +import { AspectRatio } from '@/components/ui/aspect-ratio'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +interface AnnouncementSlideProps { + title: React.ReactNode; + description: React.ReactNode; + link?: string; + linkTitle?: string; + image?: string; +} + +export const AnnouncementSlide = ({ title, description, link, linkTitle, image }: AnnouncementSlideProps) => { + const t = useTranslations('private.main.cards.carousel'); + + return ( +
+
+ {title} + + {description} + + {link && ( + + )} +
+
+ + {image && {title} + +
+
+ ); +}; diff --git a/src/app/[locale]/(private)/cards/announcements-card/announcements-card.tsx b/src/app/[locale]/(private)/cards/announcements-card/announcements-card.tsx new file mode 100644 index 00000000..4513191b --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/announcements-card.tsx @@ -0,0 +1,20 @@ +import { getAnnouncements } from '@/actions/announcement.actions'; +import { AnnouncementsCarousel } from './announcements-carousel'; +import { Card, CardContent } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; + +interface AnnouncementsCardProps { + className?: string; +} + +export const AnnouncementsCard = async ({ className }: AnnouncementsCardProps) => { + const announcements = await getAnnouncements(); + + return ( + + + + + + ); +}; diff --git a/src/app/[locale]/(private)/cards/announcements-card/announcements-carousel.tsx b/src/app/[locale]/(private)/cards/announcements-card/announcements-carousel.tsx new file mode 100644 index 00000000..64e16dec --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/announcements-carousel.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { Carousel, CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel'; + +import Autoplay from 'embla-carousel-autoplay'; +import { AnnouncementSlide } from './announcement-slide'; +import { Announcement } from '@/types/announcement'; +import { DotButton } from './dot-button'; +import { useState } from 'react'; +import { useDotButton } from './use-dot-button'; +import { DefaultAnnouncementSlide } from './default-announcement-slide'; + +const AUTOPLAY_DELAY = 10_000; // 10 seconds + +interface AnnouncementsCarouselProps { + announcements: Announcement[]; +} + +const Slide = ({ children }: { children: React.ReactNode }) => {children}; + +export const AnnouncementsCarousel = ({ announcements }: AnnouncementsCarouselProps) => { + const [api, setApi] = useState(); + const { selectedIndex, scrollSnaps, onDotButtonClick } = useDotButton(api); + + return ( + + + + + + {announcements.map((announcement) => ( + + + + ))} + +
+ {scrollSnaps.map((_, index) => ( + onDotButtonClick(index)} selected={selectedIndex === index} /> + ))} +
+
+ ); +}; diff --git a/src/app/[locale]/(private)/cards/announcements-card/default-announcement-slide.tsx b/src/app/[locale]/(private)/cards/announcements-card/default-announcement-slide.tsx new file mode 100644 index 00000000..187086f8 --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/default-announcement-slide.tsx @@ -0,0 +1,20 @@ +import { useTranslations } from 'next-intl'; +import { AnnouncementSlide } from './announcement-slide'; + +export const DefaultAnnouncementSlide = () => { + const t = useTranslations('private.main.cards.carousel.default-slide'); + + const title = t.rich('title-base', { + systemname: () => «{t('title-system-name')}», + }); + + return ( + + ); +}; diff --git a/src/app/[locale]/(private)/cards/announcements-card/dot-button.tsx b/src/app/[locale]/(private)/cards/announcements-card/dot-button.tsx new file mode 100644 index 00000000..25889d41 --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/dot-button.tsx @@ -0,0 +1,25 @@ +import { cn } from '@/lib/utils'; + +interface DotButtonProps { + selected?: boolean; + onClick: () => void; +} + +export const DotButton = ({ selected = false, onClick }: DotButtonProps) => { + return ( + + ); +}; diff --git a/src/app/[locale]/(private)/cards/announcements-card/index.ts b/src/app/[locale]/(private)/cards/announcements-card/index.ts new file mode 100644 index 00000000..913df317 --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/index.ts @@ -0,0 +1,3 @@ +import { AnnouncementsCard } from './announcements-card'; + +export { AnnouncementsCard }; diff --git a/src/app/[locale]/(private)/cards/announcements-card/use-dot-button.ts b/src/app/[locale]/(private)/cards/announcements-card/use-dot-button.ts new file mode 100644 index 00000000..8fee2f17 --- /dev/null +++ b/src/app/[locale]/(private)/cards/announcements-card/use-dot-button.ts @@ -0,0 +1,41 @@ +import { CarouselApi } from '@/components/ui/carousel'; +import { useCallback, useEffect, useState } from 'react'; + +export const useDotButton = (api: CarouselApi) => { + const [selectedIndex, setSelectedIndex] = useState(0); + const [scrollSnaps, setScrollSnaps] = useState([]); + + const onDotButtonClick = useCallback( + (index: number) => { + if (!api) { + return; + } + api.scrollTo(index); + }, + [api], + ); + + const onInit = useCallback((api: any) => { + setScrollSnaps(api.scrollSnapList()); + }, []); + + const onSelect = useCallback((api: any) => { + setSelectedIndex(api.selectedScrollSnap()); + }, []); + + useEffect(() => { + if (!api) { + return; + } + + onInit(api); + onSelect(api); + api.on('reInit', onInit).on('reInit', onSelect).on('select', onSelect); + }, [api, onInit, onSelect]); + + return { + selectedIndex, + scrollSnaps, + onDotButtonClick, + }; +}; diff --git a/src/app/[locale]/(private)/cards/index.ts b/src/app/[locale]/(private)/cards/index.ts new file mode 100644 index 00000000..64ec35ee --- /dev/null +++ b/src/app/[locale]/(private)/cards/index.ts @@ -0,0 +1,6 @@ +import { AnnouncementsCard } from './announcements-card'; +import { InformationCard } from './information-card'; +import { SocialNetworksCard } from './social-networks-card'; +import { SupportCard } from './support-card'; + +export { AnnouncementsCard, InformationCard, SocialNetworksCard, SupportCard }; diff --git a/src/app/[locale]/(private)/cards/information-card.tsx b/src/app/[locale]/(private)/cards/information-card.tsx new file mode 100644 index 00000000..99f85a73 --- /dev/null +++ b/src/app/[locale]/(private)/cards/information-card.tsx @@ -0,0 +1,44 @@ +import { Heading3 } from '@/components/typography/headers'; +import { Paragraph } from '@/components/typography/paragraph'; +import { Card, CardContent } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { useTranslations } from 'next-intl'; +import Link from 'next/link'; +import { redirectToOldCampus } from '@/actions/auth.actions'; +import { Separator } from '@/components/ui/separator'; + +interface InformationCardProps { + className?: string; +} + +export const InformationCard = ({ className }: InformationCardProps) => { + const t = useTranslations('private.main.cards.information'); + + return ( + + + {t('title')} + + {t('schedule')} + + + {t('notice-board')} + + + {t('faq')} + + + {t('user-manual')} + + + {t('old-campus.description')} +
+ +
+
+
+ ); +}; diff --git a/src/app/[locale]/(private)/social-networks-card.tsx b/src/app/[locale]/(private)/cards/social-networks-card.tsx similarity index 100% rename from src/app/[locale]/(private)/social-networks-card.tsx rename to src/app/[locale]/(private)/cards/social-networks-card.tsx diff --git a/src/app/[locale]/(private)/support-card.tsx b/src/app/[locale]/(private)/cards/support-card.tsx similarity index 100% rename from src/app/[locale]/(private)/support-card.tsx rename to src/app/[locale]/(private)/cards/support-card.tsx diff --git a/src/app/[locale]/(private)/header.tsx b/src/app/[locale]/(private)/header.tsx index bdc73154..8aeef4c4 100644 --- a/src/app/[locale]/(private)/header.tsx +++ b/src/app/[locale]/(private)/header.tsx @@ -1,5 +1,6 @@ 'use client'; +import React from 'react'; import { LocaleSwitch } from '@/components/ui/locale-switch'; import { SidebarTrigger } from '@/components/ui/sidebar'; import { ProfilePicture } from '@/components/ui/profile-picture'; @@ -14,10 +15,11 @@ import { useTranslations } from 'next-intl'; import { SignOut } from '@/app/images'; import { Paragraph } from '@/components/typography/paragraph'; import { USER_CATEGORIES } from '@/types/constants'; -import React from 'react'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useIsClient } from '@/hooks/use-is-client'; export const Header = () => { + const isClient = useIsClient(); const isMobile = useIsMobile(); const [user] = useLocalStorage('user'); @@ -43,17 +45,19 @@ export const Header = () => {
- {user?.username} - {user?.userCategories.map((category) => ( - - {tUserCategory(USER_CATEGORIES[category])} - - ))} + + {user?.username} + {user?.userCategories.map((category) => ( + + {tUserCategory(USER_CATEGORIES[category])} + + ))} +
- + - - Аккаунт - - Налаштування - - - - ); -}; diff --git a/src/app/[locale]/(private)/page.tsx b/src/app/[locale]/(private)/page.tsx index 3d5511ef..3079e1d9 100644 --- a/src/app/[locale]/(private)/page.tsx +++ b/src/app/[locale]/(private)/page.tsx @@ -1,17 +1,14 @@ import Greeting from './greeting'; -import { MainCard } from './main-card'; -import { NotificationsCard } from './notifications-card'; -import { SocialNetworksCard } from './social-networks-card'; -import { SupportCard } from './support-card'; +import { AnnouncementsCard, InformationCard, SocialNetworksCard, SupportCard } from './cards'; export default function Home() { return ( -
+
- - - - + + + +
); } diff --git a/src/app/[locale]/(private)/profile/profile.tsx b/src/app/[locale]/(private)/profile/profile.tsx index 4e2e89fe..3af3cc82 100644 --- a/src/app/[locale]/(private)/profile/profile.tsx +++ b/src/app/[locale]/(private)/profile/profile.tsx @@ -39,7 +39,7 @@ export function Profile({ contacts, contactTypes }: Props) {
{t('title')} {t('subtitle')} -
+
diff --git a/src/app/[locale]/(public)/footer.tsx b/src/app/[locale]/(public)/footer.tsx index e59958f8..76fe3786 100644 --- a/src/app/[locale]/(public)/footer.tsx +++ b/src/app/[locale]/(public)/footer.tsx @@ -1,7 +1,7 @@ import Link from 'next/link'; import dayjs from 'dayjs'; import { useTranslations } from 'next-intl'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; interface FooterProps { className?: string; @@ -13,8 +13,12 @@ export const Footer = ({ className }: FooterProps) => { return (
{t.rich('footer', { - kbislink: (chunks) => {chunks}, - year: dayjs().year() + kbislink: (chunks) => ( + + {chunks} + + ), + year: dayjs().year(), })}
); diff --git a/src/components/typography/headers.tsx b/src/components/typography/headers.tsx index 88d6c71a..b91bf59c 100644 --- a/src/components/typography/headers.tsx +++ b/src/components/typography/headers.tsx @@ -13,28 +13,29 @@ const createHeading = ( }); export const Heading0 = createHeading('h1', 'text-7xl leading-6xl'); + Heading0.displayName = 'Heading0'; -export const Heading1 = createHeading('h1', 'text-6xl leading-5xl'); +export const Heading1 = createHeading('h1', 'lg:text-6xl lg:leading-5xl text-4xl leading-3xl'); Heading1.displayName = 'Heading1'; -export const Heading2 = createHeading('h2', 'text-5xl leading-4xl'); +export const Heading2 = createHeading('h2', 'lg:text-5xl lg:leading-4xl text-3xl leading-2xl'); Heading2.displayName = 'Heading2'; -export const Heading3 = createHeading('h3', 'text-4xl leading-3xl'); +export const Heading3 = createHeading('h3', 'lg:text-4xl lg:leading-3xl text-2xl leading-xl'); Heading3.displayName = 'Heading3'; -export const Heading4 = createHeading('h4', 'text-3xl leading-2xl'); +export const Heading4 = createHeading('h4', 'lg:text-3xl lg:leading-2xl text-xl leading-lg'); Heading4.displayName = 'Heading4'; -export const Heading5 = createHeading('h5', 'text-2xl leading-xl'); +export const Heading5 = createHeading('h5', 'lg:text-2xl lg:leading-xl text-lg leading-md'); Heading5.displayName = 'Heading5'; -export const Heading6 = createHeading('h6', 'text-xl leading-lg'); +export const Heading6 = createHeading('h6', 'lg:text-xl lg:leading-lg text-base leading-base'); Heading6.displayName = 'Heading6'; diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx index 79a13901..41a34910 100644 --- a/src/components/ui/carousel.tsx +++ b/src/components/ui/carousel.tsx @@ -142,7 +142,11 @@ const CarouselContent = React.forwardRef
diff --git a/src/lib/client.ts b/src/lib/client.ts index 8873a518..c5f2f235 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -4,7 +4,7 @@ import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; const Client = (basePath: string) => { - return async (url: string | URL, options: RequestInit = {}) => { + return async (url: string | URL, options: RequestInit = {}) => { const { headers = {}, ...otherOptions } = options; const jwt = cookies().get('token')?.value; @@ -12,7 +12,7 @@ const Client = (basePath: string) => { const contentType = new Headers(headers).get('Content-type') ?? 'application/json'; - const response = await fetch(input, { + const response = await fetch(input, { cache: 'no-cache', headers: { Accept: 'application/json', diff --git a/src/messages/en.json b/src/messages/en.json index 7cc80d12..7a09b631 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -49,8 +49,7 @@ "student": "Student", "spw": "SPW" }, - "footer": "Developer: KBIS© {year} Igor Sikorsky Kyiv Polytechnic Institute", - "goto-old": "Go to the old version of Campus" + "footer": "Developer: KBIS© {year} Igor Sikorsky Kyiv Polytechnic Institute" }, "auth": { "login": { @@ -141,6 +140,30 @@ } }, "private": { + "main": { + "cards": { + "information": { + "title": "Information", + "schedule": "Schedule", + "notice-board": "Notice board", + "faq": "Frequently asked questions", + "user-manual": "User manual", + "old-campus": { + "description": "Some modules have not yet been ported to the new system. To access these modules, you can upgrade to system version 1.0.", + "link-title": "Go to the old version of Campus" + } + }, + "carousel": { + "default-link-title": "Go to schedule", + "default-slide": { + "title-base": "You have entered the system", + "title-system-name": "Electronic Campus", + "description": "Here you can find methodological support for academic disciplines, results of current control, news of the educational process and other important information.", + "link-title": "Learn more" + } + } + } + }, "about": { "title": "About", "content": "

Information and telecommunications system \"Electronic Campus\" - application software, which is an element of the information and telecommunications environment of the university and is used for information support of the daily activities of students, teachers, university employees, as well as for information support of all types of innovative activities at the university (hereinafter referred to as ITS EC).

ITS EC unites internal information resources (educational, methodological and others), provides centralized access to them on the basis of unified system and technological solutions and ensures their use for effective management and planning of the scientific and educational process.

Main functions of ITS EC

ITS EC ensures the implementation of the following main functions:

  • multilateral communication between students, teachers, scientific community;
  • formation of a single information resource that reflects the state of the scientific and educational process of the university;
  • ensuring timely and operational placement of: complete, objective, reliable and consistent information about the educational process of the university;
  • dissemination of information about upcoming events and activities and other reference information;
  • ensuring centralized and convenient access to information about the activities of the administration and university departments;
  • ensuring navigation through the entire information content of the ITS EC;
  • organizing interaction and information exchange between the ITS EC and other information resources and systems of the university.

The ITS EC system has developed virtual offices according to user profiles: student, lecturer-scientist, methodologist of the department.

" diff --git a/src/messages/uk.json b/src/messages/uk.json index ccbd0d6c..db07dfeb 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -49,8 +49,7 @@ "student": "Студент", "spw": "НПП" }, - "footer": "Розробник: КБІС© {year} КПІ ім. Ігоря Сікорського", - "goto-old": "Перейти на стару версію Кампусу" + "footer": "Розробник: КБІС© {year} КПІ ім. Ігоря Сікорського" }, "auth": { "login": { @@ -141,6 +140,30 @@ } }, "private": { + "main": { + "cards": { + "information": { + "title": "Інформація", + "schedule": "Розклад занять та сесії", + "notice-board": "Дошка оголошень", + "faq": "Поширенi запитання", + "user-manual": "Інструкція користувача", + "old-campus": { + "description": "Частина модулей ще не портовані у нову систему. Для доступу до цих модулів, ви можете перейти у версію системи 1.0.", + "link-title": "Перейти на стару версію Кампусу" + } + }, + "carousel": { + "default-link-title": "Перейти до розкладу", + "default-slide": { + "title-base": "Ви увійшли до системи ", + "title-system-name": "Електронний Кампус", + "description": "Тут можна знайти методичне забезпечення до навчальних дисциплін, результати поточного контролю, новини навчального процесу та іншу важливу інформацію.", + "link-title": "Дізнатись більше" + } + } + } + }, "about": { "title": "Про систему", "content": "

Інформаційно-телекомунікаційна система «Електронний кампус» – прикладне програмне забезпечення, яке є елементом інформаційно-телекомунікаційного середовища університету та використовується для інформаційної підтримки повсякденної діяльності студентів, викладачів, співробітників університету, а так само для інформаційної підтримки всіх видів інноваційної діяльності в університеті (далі ІТС ЕК).

ІТС ЕК об'єднує внутрішні інформаційні ресурси (навчальні, методичні та інші), надає централізований доступ до них на основі єдиних системних і технологічних рішень та забезпечує їх використання для ефективного управління та планування науково-освітнім процесом.

Основні функції ІТС ЕК

ІТС ЕК забезпечує виконання таких основних функцій:

  • багатобічна комунікація між студентами, викладачами, науковим співтовариством;
  • формування єдиного інформаційного ресурсу, що відображає стан науково-освітнього процесу університету;
  • забезпечення своєчасного та оперативного розміщення: повної, об'єктивної, достовірної та несуперечливої інформації про освітній процес університету;
  • розповсюдження інформації про майбутні події і заходи та інші довідкові відомості;
  • забезпечення централізованого і зручного доступу до відомостей про діяльність ректорату і підрозділів університету;
  • забезпечення навігації по всьому інформаційному наповненню ІТС ЕК;
  • організація взаємодії та інформаційного обміну між ІТС ЕК та іншими інформаційними ресурсами і системами університету.

В системі ІТС ЕК розроблені віртуальні кабінети за профілями користувачів: студент, викладач-науковець, методист кафедри.

" @@ -186,7 +209,6 @@ "header": "Контакти", "content": "

Контактнi данi

Адреса конструкторського бюро: Україна, м. Київ, вул. Політехнічна 14-в, корпус 13, 4 поверх, 25 кабінет

Соцiальнi мережi

КПI у GitHub

Facebook

Twitter

Instagram

Служба підтримки

Якщо у вас є питання, ви можете звернутися до служби підтримки:

Форма скарг i пропозицiй

Email" }, - "profile": { "title": "Профіль", "subtitle": "Тут ви можете переглядати свій профіль, редагувати контакти, додавати посилання на облікові записи та назначати деталі прийому.", @@ -244,7 +266,6 @@ "not-specified": "Не вказано" } }, - "settings": { "title": "Налаштування", "subtitle": "Тут ви можете змінити фото, пошту та пароль для свого профілю.", diff --git a/src/types/announcement.ts b/src/types/announcement.ts new file mode 100644 index 00000000..fcfb5c93 --- /dev/null +++ b/src/types/announcement.ts @@ -0,0 +1,7 @@ +export interface Announcement { + id: number; + title: string; + description: string; + image?: string; + link?: string; +} diff --git a/src/types/auth-response.ts b/src/types/auth-response.ts new file mode 100644 index 00000000..0fd7ba73 --- /dev/null +++ b/src/types/auth-response.ts @@ -0,0 +1,4 @@ +export interface AuthResponse { + sessionId: string; + access_token: string; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 00000000..3f0e396b --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,14 @@ +declare global { + declare function fetch( + input: RequestInfo | URL, + init?: TypedRequestInit, + ): Promise>; + + type TypedRequestInit = RequestInit; + + interface TypedResponse extends Response { + json(): Promise; + } +} + +export {}; diff --git a/tailwind.config.js b/tailwind.config.js index a846946a..87ca5e10 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -8,6 +8,9 @@ module.exports = { tall: { raw: '(min-height: 933px)', }, + '3xl': { + raw: '(min-width: 1920px)', + }, }, borderRadius: { xl: 'calc(var(--radius) * 4.5)',