diff --git a/TODO.md b/TODO.md index 6cde50da..fe1504b1 100644 --- a/TODO.md +++ b/TODO.md @@ -10,19 +10,19 @@ # Enter-näppäimellä EditModalin ja DeleteModalin hyväksyminen -### +# Jos tulee virhe vaikka lahjoja hakiessa, ehkä jokin nappula olisi hyvä tehdä, jolla voisi mahdollisesti kokeilla hakea uudestaan lahjoja -TODO: +# Kun lisää uuden lahjan / kun lahjoja haetaan, järjestetään ne uusin -> vanhin. Myöhemmin voidaan lisätä käyttäjälle mahdollisuus valita miten lahjat haluaa järjestää (vaikka vanhin -> uusin, aakkosjärjestys, ymsyms) -Lisää /pages/login.tsx:n Kirjaudu sisään -nappulaan "select-none" jottei teksti tule maalatuksi jos silmää painaa muutaman kerran liian nopeasti +# Tässä toteutustapa:const sortedArrayNewestToOldest = gifts.sort((a, b) =>new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),); -Jos tulee virhe vaikka lahjoja hakiessa, ehkä jokin nappula olisi hyvä tehdä, jolla voisi mahdollisesti kokeilla hakea uudestaan lahjoja +# Tanstackin requesteihin joku maksimiaika mitä odotetaan -Kun lisää uuden lahjan / kun lahjoja haetaan, järjestetään ne uusin -> vanhin. Myöhemmin voidaan lisätä käyttäjälle mahdollisuus valita miten lahjat haluaa järjestää (vaikka vanhin -> uusin, aakkosjärjestys, ymsyms) +### + +TODO: -Tässä toteutustapa: +1. Tee https://github.com/samuliasmala/lahjalista/pull/62#discussion_r1921024704 omaan branchiin -const sortedArrayNewestToOldest = gifts.sort( -(a, b) => -new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), -); +2.1 Vaihda "Kirjaudu sisään"- ja "Luo käyttäjätunnus" -nappuloihin pyörivä spinner-indikaattori "..." sijaan +2.2 Lisää /pages/login.tsx:n Kirjaudu sisään -nappulaan "select-none" jottei teksti tule maalatuksi jos silmää painaa muutaman kerran liian nopeasti diff --git a/components/Button.tsx b/components/Button.tsx index a3447f4b..46ae1a82 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,13 +1,17 @@ -import { ButtonHTMLAttributes } from 'react'; +import { ComponentPropsWithoutRef } from 'react'; import { twMerge } from 'tailwind-merge'; +import { errorWrapper } from '~/utils/utilFunctions'; -export function Button({ - children, - className, - ...rest -}: ButtonHTMLAttributes) { +type ButtonProps = ComponentPropsWithoutRef<'button'> & { + onClick?: ( + event: React.MouseEvent, + ) => void | Promise; +}; + +export function Button({ children, onClick, className, ...rest }: ButtonProps) { return ( @@ -159,7 +172,12 @@ function GiftList() { if (isFetching) return ( -

Noudetaan lahjoja

+

+ Noudetaan lahjoja{' '} + + + +

); if (error) return

{error.message}

; @@ -231,22 +249,32 @@ function UserDetailModal({ showUserWindow: boolean; closeUserWindow: () => void; }) { - async function handleLogout() { - try { - await axios.post('/api/auth/logout'); - window.location.href = '/logout'; - } catch (e) { - console.error(e); - window.location.href = '/'; - } - } + const router = useRouter(); + + const queryClient = useQueryClient(); + + const { isPending, error, mutateAsync } = useMutation({ + mutationKey: QueryKeys.LOGOUT, + mutationFn: async () => await axios.post('/api/auth/logout'), + onSuccess: () => { + queryClient.clear(); + router.push('/logout').catch((e) => console.error(e)); + }, + }); + + useShowErrorToast(error); if (user && showUserWindow) { return ( <>
closeUserWindow()} + className={`fixed left-0 top-0 h-full w-full max-w-full bg-transparent ${isPending ? 'z-[100]' : ''}`} + onClick={() => { + // this blocks the closing of the User Modal if request for logout is sent + if (!isPending) { + closeUserWindow(); + } + }} />

@@ -255,15 +283,31 @@ function UserDetailModal({

{user.email}

diff --git a/pages/login.tsx b/pages/login.tsx index 44383f06..0a0250ce 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -16,7 +16,7 @@ import { Label } from '~/components/Label'; import { GetServerSidePropsContext } from 'next'; import { handleErrorToast } from '~/utils/handleToasts'; import { ErrorParagraph } from '~/components/ErrorParagraph'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useShowErrorToast } from '~/hooks/useShowErrorToast'; export async function getServerSideProps(context: GetServerSidePropsContext) { @@ -48,11 +48,16 @@ export default function Login() { const router = useRouter(); + const queryClient = useQueryClient(); + const { mutateAsync, isPending, error } = useMutation({ mutationKey: QueryKeys.LOGIN, mutationFn: async (loginCredentials: UserLoginDetails) => await axios.post('/api/auth/login', loginCredentials), - onSuccess: () => router.push('/'), + onSuccess: () => { + queryClient.clear(); + router.push('/').catch((e) => console.error(e)); + }, }); useShowErrorToast(error); diff --git a/public/credits_for_images.txt b/public/credits_for_images.txt index 1eeea6be..d6fac190 100644 --- a/public/credits_for_images.txt +++ b/public/credits_for_images.txt @@ -4,4 +4,5 @@ eye_open.svg = https://heroicons.com/ eye_slash.svg = https://heroicons.com/ user.svg = https://heroicons.com/ arrow-right-start-on-rectangle.svg = https://heroicons.com/ -logo = https://www.freepik.com/free-vector/flat-wheat-collection_1545231.htm \ No newline at end of file +logo = https://www.freepik.com/free-vector/flat-wheat-collection_1545231.htm +spinner.svg = https://www.svgrepo.com/svg/310005/spinner-ios \ No newline at end of file diff --git a/public/images/icons/spinner.svg b/public/images/icons/spinner.svg new file mode 100644 index 00000000..d67fecca --- /dev/null +++ b/public/images/icons/spinner.svg @@ -0,0 +1,27 @@ + + + + ic_fluent_spinner_ios_20_regular + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/shared/types.ts b/shared/types.ts index 6967cae0..062427dd 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -158,6 +158,8 @@ export const QueryKeys = { GIFTS: ['gifts'], LOGIN: ['login'], REGISTER: ['register'], + CREATE_GIFT: ['createGift'], + LOGOUT: ['logout'], // ei vielä käytössä //GIFT_DETAILS: (uuid: string) => ['gifts', uuid], }; diff --git a/utils/utilFunctions.ts b/utils/utilFunctions.ts index 54e86d08..45f1ae03 100644 --- a/utils/utilFunctions.ts +++ b/utils/utilFunctions.ts @@ -10,3 +10,38 @@ export async function sleep(timeoutTimeInMs: number) { export function randomBoolean() { return !Math.round(Math.random()); } + +/** + * A higher-order function that wraps a given function to handle both + * synchronous and asynchronous errors. Used to ensure that errors are + * caught in event handlers and other places where they would otherwise be + * uncaugh. + * + * @template A - The type of the arguments that the wrapped function accepts. + * @param {(...args: A) => Promise | void} fn - The function to be + * wrapped. It can be either synchronous or asynchronous. + * @returns {(...args: A) => void} - A new function that wraps the original + * function and handles errors and returns a synchronous result. + * + * @example + * // Usage with an asynchronous function in click handler + * const handleClick = async () => { throw new Error('Asynchronous error'); }; + * + * + */ +export function errorWrapper( + fn: (...args: A) => Promise | void, +): (...args: A) => void { + return (...args: A) => { + try { + const p = fn(...args); + // Check if the function returns a promise to catch async errors + if (p instanceof Promise) + p.catch((error: unknown) => { + console.error('Error thrown asynchronously', error); + }); + } catch (error) { + console.error('Error thrown synchronously', error); + } + }; +}