From 66e9d8f407c2bd911b0c02c6f6cbe0dd821ff5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Eriksson?= Date: Sun, 1 Sep 2024 02:07:21 +0200 Subject: [PATCH 1/3] [frontend] Final Cleanup --- .../account/pages/account/accountForm.tsx | 259 +++++++++--------- .../account/pages/account/receptionForm.tsx | 101 +++++-- .../news/upload/[slug]/commandBar.tsx | 24 +- .../bulletin/news/upload/[slug]/page.tsx | 3 + .../app/[language]/bulletin/recruiting.tsx | 6 +- .../committees/[committee]/client/manage.tsx | 25 +- .../committees/[committee]/committee.tsx | 64 ++++- .../committees/[committee]/manage/edit.tsx | 23 +- .../[committee]/manage/forms/memberForm.tsx | 34 ++- .../[committee]/manage/forms/positionForm.tsx | 47 ++-- .../manage/forms/recruitmentForm.tsx | 42 ++- .../manage/forms/removePosition.tsx | 12 +- .../committees/[committee]/manage/landing.tsx | 149 ++++++++++ .../committees/[committee]/manage/manage.tsx | 150 ++-------- .../[committee]/manage/pages/members.tsx | 150 ++++++---- .../[committee]/manage/redirect.tsx | 72 +++++ .../committees/[committee]/members.tsx | 107 +++++--- .../chapter/documents/views/GridView.tsx | 248 +++++++++-------- .../chapter/documents/views/ListView.tsx | 3 + .../src/app/[language]/chapter/members.tsx | 22 +- .../student/[studentId]/eventTab.tsx | 3 +- .../[language]/student/[studentId]/page.tsx | 166 +---------- .../student/[studentId]/positionsTab.tsx | 18 +- .../student/[studentId]/student.tsx | 165 +++++++++++ frontend/src/app/i18n/locales/en/header.json | 4 - frontend/src/app/i18n/locales/sv/header.json | 6 +- .../src/components/dialogs/EventUpload.tsx | 1 + .../src/providers/AuthenticationProvider.tsx | 37 ++- 28 files changed, 1198 insertions(+), 743 deletions(-) create mode 100644 frontend/src/app/[language]/chapter/committees/[committee]/manage/landing.tsx create mode 100644 frontend/src/app/[language]/chapter/committees/[committee]/manage/redirect.tsx create mode 100644 frontend/src/app/[language]/student/[studentId]/student.tsx diff --git a/frontend/src/app/[language]/account/pages/account/accountForm.tsx b/frontend/src/app/[language]/account/pages/account/accountForm.tsx index 39583355..efaf4937 100644 --- a/frontend/src/app/[language]/account/pages/account/accountForm.tsx +++ b/frontend/src/app/[language]/account/pages/account/accountForm.tsx @@ -122,8 +122,6 @@ export default function AccountForm({ }, }) - const { setValue } = accountForm - const profileForm = useForm>({ resolver: zodResolver(ProfileFormSchema), defaultValues: { @@ -291,6 +289,7 @@ export default function AccountForm({ ( @@ -308,6 +307,7 @@ export default function AccountForm({ ( @@ -319,8 +319,7 @@ export default function AccountForm({ /> - Manage your accounts email for external services (e.g. - Notifications) + Manage your accounts email for external services @@ -334,6 +333,9 @@ export default function AccountForm({ render={({ field }) => ( Password + + Can be used to change your password + { - setValue('csrf_token', csrf.token) + accountForm.setValue('csrf_token', csrf.token) }} > Save @@ -381,127 +383,134 @@ export default function AccountForm({ -
-
- -

- Profile Settings -

- ( - - - - Facebook - - - { - e.target.value = 'https://www.facebook.com/' - }} - onBlur={(e) => { - if (e.target.value === 'https://www.facebook.com/') { - e.target.value = '' - } - }} - /> - - - - )} - /> - ( - - - - Instagram - - - { - e.target.value = 'https://www.instagram.com/' - }} - onBlur={(e) => { - if (e.target.value === 'https://www.instagram.com/') { - e.target.value = '' - } - }} - /> - - - - )} - /> - ( - - - - LinkedIn - - - { - e.target.value = 'https://www.linkedin.com/in/' - }} - onBlur={(e) => { - if (e.target.value === 'https://www.linkedin.com/in/') { - e.target.value = '' - } - }} - /> - - - - )} - /> - ( - - - Email Notifications - - -
- - - Receive email notifications - -
-
- -
- )} - /> - - +
+
+

To be Added

- +
+
+ +

+ Profile Settings +

+ ( + + + + Facebook + + + { + e.target.value = 'https://www.facebook.com/' + }} + onBlur={(e) => { + if (e.target.value === 'https://www.facebook.com/') { + e.target.value = '' + } + }} + /> + + + + )} + /> + ( + + + + Instagram + + + { + e.target.value = 'https://www.instagram.com/' + }} + onBlur={(e) => { + if (e.target.value === 'https://www.instagram.com/') { + e.target.value = '' + } + }} + /> + + + + )} + /> + ( + + + + LinkedIn + + + { + e.target.value = 'https://www.linkedin.com/in/' + }} + onBlur={(e) => { + if ( + e.target.value === 'https://www.linkedin.com/in/' + ) { + e.target.value = '' + } + }} + /> + + + + )} + /> + ( + + + Email Notifications + + +
+ + + Receive email notifications + +
+
+ +
+ )} + /> + + +
+ +
) } diff --git a/frontend/src/app/[language]/account/pages/account/receptionForm.tsx b/frontend/src/app/[language]/account/pages/account/receptionForm.tsx index 094c6b89..d18d17dc 100644 --- a/frontend/src/app/[language]/account/pages/account/receptionForm.tsx +++ b/frontend/src/app/[language]/account/pages/account/receptionForm.tsx @@ -1,7 +1,7 @@ 'use client' import Logo from 'public/images/logo.webp' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { zodResolver } from '@hookform/resolvers/zod' import { Input } from '@/components/ui/input' import { @@ -18,19 +18,18 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { z } from 'zod' import { useForm } from 'react-hook-form' import { Button } from '@/components/ui/button' +import { API_BASE_URL } from '@/utility/Constants' +import useSWR from 'swr' +import Loading from '@/components/tooltips/Loading' +import { useAuthentication } from '@/providers/AuthenticationProvider' -const FormSchema = z.object({ - image: z.instanceof(window.File), - receptionName: z.string().optional().or(z.literal('')), -}) - -const MAX_FILE_SIZE = 500 * 1024 -const ACCEPTED_IMAGE_TYPES = [ - 'image/jpeg', - 'image/jpg', - 'image/png', - 'image/webp', -] +const fetcher = (url: string) => + fetch(url).then( + (res) => + res.json() as Promise<{ + token: string + }> + ) export default function ReceptionForm({ params: { language }, @@ -39,6 +38,22 @@ export default function ReceptionForm({ }) { const [receptionPicturePreview, setReceptionPicturePreview] = useState(null) + const { student } = useAuthentication() + const [csrfToken, setCsrfToken] = useState() + + const FormSchema = z.object({ + image: z.instanceof(window.File), + receptionName: z.string().optional().or(z.literal('')), + csrf_token: z.string().optional().or(z.literal('')), + }) + + const MAX_FILE_SIZE = 500 * 1024 + const ACCEPTED_IMAGE_TYPES = [ + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/webp', + ] const form = useForm>({ resolver: zodResolver(FormSchema), @@ -48,8 +63,49 @@ export default function ReceptionForm({ }, }) - const onSubmit = (data: z.infer) => { - console.log(data) + const { + data: csrf, + error, + isLoading, + } = useSWR(`${API_BASE_URL}/csrf-token`, fetcher) + + useEffect(() => { + if (csrf) { + setCsrfToken(csrf.token) + form.setValue('csrf_token', csrf.token) + } + }) + + if (!student) return null + if (error) return
Failed to load
+ if (isLoading) return + if (!csrf) return null + + const onSubmit = async (data: z.infer) => { + const formData = new FormData() + + formData.append('reception_image', data.image) + formData.append('reception_name', data.receptionName || '') + formData.append('csrf_token', data.csrf_token || csrf.token) + + try { + const response = await fetch(`${API_BASE_URL}/students/reception`, { + method: 'PUT', + headers: { + 'X-CSRF-Token': csrfToken || data.csrf_token || '', + }, + credentials: 'include', + body: formData, + }) + + if (!response.ok) { + alert('Failed to save') + return + } + } catch (error) { + alert('Failed to save') + return + } } return ( @@ -72,7 +128,7 @@ export default function ReceptionForm({ src={ receptionPicturePreview ? URL.createObjectURL(receptionPicturePreview) - : Logo.src + : student.reception_profile_picture_url || Logo.src } /> Profile Icon @@ -144,7 +200,18 @@ export default function ReceptionForm({
)} /> - + } + /> + diff --git a/frontend/src/app/[language]/bulletin/news/upload/[slug]/commandBar.tsx b/frontend/src/app/[language]/bulletin/news/upload/[slug]/commandBar.tsx index 99dd2955..f5940c93 100644 --- a/frontend/src/app/[language]/bulletin/news/upload/[slug]/commandBar.tsx +++ b/frontend/src/app/[language]/bulletin/news/upload/[slug]/commandBar.tsx @@ -95,6 +95,23 @@ export default function CommandBar({ const postForm = async (data: z.infer) => { await saveCallback(language, true) + /* + const formData = new window.FormData() + + supportedLanguages.forEach((lang, index) => { + formData.append(`translations[${index}][language_code]`, lang) + formData.append(`translations[${index}][title]`, data.title) + formData.append(`translations[${index}][main_image_url]`, data.image) + formData.append( + `translations[${index}][body]`, + content.translations[index].body + ) + formData.append( + `translations[${index}][short_description]`, + data.short_description + ) + })*/ + const json_data = { ...content, translations: [ @@ -140,10 +157,6 @@ export default function CommandBar({
- - Styrelsen - - Articles @@ -175,6 +188,7 @@ export default function CommandBar({ className='ml-4' title='Import/Export' aria-label='Import or Export' + disabled // TODO: Enable when import/export is ready > @@ -183,6 +197,7 @@ export default function CommandBar({ className='ml-4' title='Language' aria-label='Language' + disabled // TODO: Enable when language is ready > diff --git a/frontend/src/app/[language]/bulletin/news/upload/[slug]/page.tsx b/frontend/src/app/[language]/bulletin/news/upload/[slug]/page.tsx index 64b60a60..41ea89a6 100644 --- a/frontend/src/app/[language]/bulletin/news/upload/[slug]/page.tsx +++ b/frontend/src/app/[language]/bulletin/news/upload/[slug]/page.tsx @@ -75,6 +75,7 @@ export default function UploadNews({ value='tags' className='p-2 mb-2 hover:bg-neutral-400/30' title='Select tags' + disabled // TODO: Implement tags page > @@ -82,6 +83,7 @@ export default function UploadNews({ value='engagement' className='p-2 mb-2 hover:bg-neutral-400/30' title='View article engagement' + disabled // TODO: Implement engagement page > @@ -89,6 +91,7 @@ export default function UploadNews({ value='settings' className='p-2 hover:bg-neutral-400/30' title='Article settings' + disabled // TODO: Implement settings page > diff --git a/frontend/src/app/[language]/bulletin/recruiting.tsx b/frontend/src/app/[language]/bulletin/recruiting.tsx index 6fe4bc93..e7dbbd90 100644 --- a/frontend/src/app/[language]/bulletin/recruiting.tsx +++ b/frontend/src/app/[language]/bulletin/recruiting.tsx @@ -97,7 +97,11 @@ export default function Recruitment({
diff --git a/frontend/src/app/[language]/chapter/committees/[committee]/client/manage.tsx b/frontend/src/app/[language]/chapter/committees/[committee]/client/manage.tsx index 0279315d..3c710d10 100644 --- a/frontend/src/app/[language]/chapter/committees/[committee]/client/manage.tsx +++ b/frontend/src/app/[language]/chapter/committees/[committee]/client/manage.tsx @@ -13,22 +13,17 @@ export default function ManageButton({ language, committee }: Props) { const { committees, role } = useAuthentication() return (
- {committees.includes(committee) || - (role === 'ADMIN' && ( - - ))} + + + + )}
) } -// \ No newline at end of file +// diff --git a/frontend/src/app/[language]/chapter/committees/[committee]/committee.tsx b/frontend/src/app/[language]/chapter/committees/[committee]/committee.tsx index 4c0a9623..9ca86f24 100644 --- a/frontend/src/app/[language]/chapter/committees/[committee]/committee.tsx +++ b/frontend/src/app/[language]/chapter/committees/[committee]/committee.tsx @@ -6,12 +6,31 @@ import { fallbackLanguage } from '@/app/i18n/settings' import Image from 'next/image' import FallbackImage from 'public/images/logo.webp' import CommitteeMembers from './members' -import ExploreMore from './client/explore' +//import ExploreMore from './client/explore' import ManageButton from './client/manage' +import Link from 'next/link' export const revalidate = 60 * 60 * 24 * 30 -export async function generateStaticParams() { +interface Params { + language: string + committee: string +} + +interface Props { + params: Params +} + +/** + * @name generateStaticParams + * @description Generates the static paths for the committee pages + * + * @returns {Promise<{ language: string; committee: string }[]>} The generated static paths + * @see {@link https://nextjs.org/docs/app/api-reference/functions/generate-static-params | Next.js Static Generation} + */ +export async function generateStaticParams(): Promise< + { language: string; committee: string }[] +> { try { const response = await fetch( API_BASE_URL + `/public/committees?language=${fallbackLanguage}` @@ -29,14 +48,24 @@ export async function generateStaticParams() { } } catch (error) { console.error(error) + return [] } + + return [] } +/** + * @name Committee + * @description The page for displaying a committee + * + * @param {object} param - The dynamic URL parameters + * @param {string} param.language - The language of the page + * @param {string} param.committee - The committee name to display + * @returns {Promise} The rendered server component + */ export default async function Committee({ params: { language, committee }, -}: { - params: { language: string; committee: string } -}) { +}: Props): Promise { const data: Committee | null = await GetCommitteePublic(committee, language) if (!data || Object.keys(data).length === 0) { @@ -87,17 +116,30 @@ export default async function Committee({ className='w-24 lg:w-[9.5rem] bg-white h-auto absolute left-0 top-0 bottom-0 right-0 m-auto hover:scale-105 duration-300 transition-transform' /> -
-

+
+

= 15 + ? 'text-lg xxs:text-xl md:text-4xl xl:text-6xl desktop:text-7xl' + : 'text-3xl xxs:text-4xl md:text-6xl xl:text-7xl' + } uppercase tracking-wide w-fit text-center lg:text-start flex flex-col-reverse justify-center`} + > {committeeName}

-

+ + {data.email} + +

{data.translations[0].description}

@@ -105,7 +147,7 @@ export default async function Committee({
- + {/**/} ) } diff --git a/frontend/src/app/[language]/chapter/committees/[committee]/manage/edit.tsx b/frontend/src/app/[language]/chapter/committees/[committee]/manage/edit.tsx index db75ee44..c0adbda2 100644 --- a/frontend/src/app/[language]/chapter/committees/[committee]/manage/edit.tsx +++ b/frontend/src/app/[language]/chapter/committees/[committee]/manage/edit.tsx @@ -28,6 +28,9 @@ import { Label } from '@/components/ui/label' import '/node_modules/flag-icons/css/flag-icons.min.css' import { API_BASE_URL, LANGUAGES } from '@/utility/Constants' import { useState } from 'react' +import { useAuthentication } from '@/providers/AuthenticationProvider' +import { Permission } from '@/models/Permission' +import { Textarea } from '@/components/ui/textarea' /** * @name TranslatedInputs @@ -65,7 +68,7 @@ function TranslatedInputs({ [{language}] - +