From d7ef66ff9bd1f66cd25e4fd22dd022dc0ca43fe7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:24:42 +0200 Subject: [PATCH 01/11] chore(deps): bump @babel/traverse from 7.22.15 to 7.23.2 (#912) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.15 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 64 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41ce0406..c9dfa19c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -228,7 +228,7 @@ packages: '@babel/helpers': 7.22.15 '@babel/parser': 7.22.15 '@babel/template': 7.22.15 - '@babel/traverse': 7.22.15 + '@babel/traverse': 7.23.2 '@babel/types': 7.22.15 convert-source-map: 1.9.0 debug: 4.3.4 @@ -247,6 +247,15 @@ packages: '@jridgewell/trace-mapping': 0.3.19 jsesc: 2.5.2 + /@babel/generator@7.23.0: + resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + jsesc: 2.5.2 + /@babel/helper-compilation-targets@7.22.15: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} engines: {node: '>=6.9.0'} @@ -257,22 +266,26 @@ packages: lru-cache: 5.1.1 semver: 6.3.1 + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + /@babel/helper-environment-visitor@7.22.5: resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} - /@babel/helper-function-name@7.22.5: - resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.22.15 + '@babel/types': 7.23.0 /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.15 + '@babel/types': 7.23.0 /@babel/helper-module-imports@7.22.15: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} @@ -308,7 +321,7 @@ packages: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.15 + '@babel/types': 7.23.0 /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} @@ -318,6 +331,10 @@ packages: resolution: {integrity: sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} engines: {node: '>=6.9.0'} @@ -327,7 +344,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/traverse': 7.22.15 + '@babel/traverse': 7.23.2 '@babel/types': 7.22.15 transitivePeerDependencies: - supports-color @@ -347,6 +364,13 @@ packages: dependencies: '@babel/types': 7.22.15 + /@babel/parser@7.23.0: + resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.0 + /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.15): resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} engines: {node: '>=6.9.0'} @@ -379,21 +403,21 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/parser': 7.22.15 - '@babel/types': 7.22.15 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 - /@babel/traverse@7.22.15: - resolution: {integrity: sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ==} + /@babel/traverse@7.23.2: + resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.15 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 + '@babel/generator': 7.23.0 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.22.15 - '@babel/types': 7.22.15 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: @@ -407,6 +431,14 @@ packages: '@babel/helper-validator-identifier': 7.22.15 to-fast-properties: 2.0.0 + /@babel/types@7.23.0: + resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + /@date-io/core@1.3.13: resolution: {integrity: sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==} dev: false From 2780318dde431f5420305b1141c4dbb99af1713c Mon Sep 17 00:00:00 2001 From: Martin Clementz Date: Thu, 19 Oct 2023 13:06:54 +0200 Subject: [PATCH 02/11] chore: cleanup calendar pup-up (#913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: cleanup calendar pup-up * chore: update conditions * fix: formatting * chore: added emoji ➡️ * chore: remove emoji 😥 --- CHANGELOG.md | 2 ++ .../Landing/components/EventsCalendarPopover.tsx | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e8fcd8..9160cbef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### 🎨 - Designendringer ## Neste versjon + +- ⚡ **Kalender**. Kalender pop-up viser viser infromasjonen bedre. - ✨ **Bannere**. Tidsbegrensede bannere kan nå opprettes. ## versjon 2023.24.09 diff --git a/src/pages/Landing/components/EventsCalendarPopover.tsx b/src/pages/Landing/components/EventsCalendarPopover.tsx index 7bc4d4e0..78f53f25 100644 --- a/src/pages/Landing/components/EventsCalendarPopover.tsx +++ b/src/pages/Landing/components/EventsCalendarPopover.tsx @@ -24,13 +24,17 @@ const EventsCalendarPopover = ({ id }: EventsCalendarPopoverProps) => { {data && ( <> - Detaljer + {data.title} - Påmelding - - + {data.sign_up && ( + <> + 0 && '/' + data.limit}`} title='Påmeldte:' /> + {data.waiting_list_count > 0 && } + + )} + Til arrangement )} From 519842300d84a19e8421a7c2a189b953c40cf6b1 Mon Sep 17 00:00:00 2001 From: martcl Date: Thu, 19 Oct 2023 18:01:20 +0200 Subject: [PATCH 03/11] chore: fix pre-commit file permissions --- .husky/pre-commit | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 From 3bc003ec6d03fd970bf7373b56506fcef239aedd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:14:03 +0200 Subject: [PATCH 04/11] chore(deps-dev): bump postcss from 8.4.29 to 8.4.31 (#904) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.29 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.29...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9dfa19c..9270f04b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4106,8 +4106,8 @@ packages: engines: {node: '>=4.0.0'} dev: false - /postcss@8.4.29: - resolution: {integrity: sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -5196,7 +5196,7 @@ packages: dependencies: '@types/node': 20.6.3 esbuild: 0.18.20 - postcss: 8.4.29 + postcss: 8.4.31 rollup: 3.28.1 optionalDependencies: fsevents: 2.3.3 From 61e535140c54ea85bbc989b691aa214ebcb45b76 Mon Sep 17 00:00:00 2001 From: Anders <69890033+lille-morille@users.noreply.github.com> Date: Wed, 25 Oct 2023 09:38:47 +0200 Subject: [PATCH 05/11] Feat(event)/waitlist (#911) --- CHANGELOG.md | 1 + src/pages/EventDetails/components/EventRenderer.tsx | 8 ++++---- src/types/Event.tsx | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9160cbef..72056369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ## Neste versjon +- ✨ **Arrangement**. Du kan nå se hvilken plass du har på ventelisten. - ⚡ **Kalender**. Kalender pop-up viser viser infromasjonen bedre. - ✨ **Bannere**. Tidsbegrensede bannere kan nå opprettes. diff --git a/src/pages/EventDetails/components/EventRenderer.tsx b/src/pages/EventDetails/components/EventRenderer.tsx index dfffb0ba..ea082849 100644 --- a/src/pages/EventDetails/components/EventRenderer.tsx +++ b/src/pages/EventDetails/components/EventRenderer.tsx @@ -156,16 +156,16 @@ const EventRenderer = ({ data, preview = false }: EventRendererProps) => { } }; - type RegistrationInfoProps = { registration: Registration }; + type RegistrationInfoProps = { registration: Registration; event: Event }; - const RegistrationInfo = ({ registration }: RegistrationInfoProps) => { + const RegistrationInfo = ({ registration, event }: RegistrationInfoProps) => { const unregisteringGivesStrike = isPast(signOffDeadlineDate) && !registration.is_on_wait && data.can_cause_strikes; return ( <> {registration.is_on_wait ? ( <> - Du står på ventelisten, vi gir deg beskjed hvis du får plass + Du står på plass {registration.wait_queue_number}/{event.waiting_list_count} på ventelisten, vi gir deg beskjed hvis du får plass {registration.survey_submission.answers.length > 0 && (
@@ -288,7 +288,7 @@ const EventRenderer = ({ data, preview = false }: EventRendererProps) => { ) : null; } if (registration) { - return ; + return ; } if (isPast(endRegistrationDate)) { return null; diff --git a/src/types/Event.tsx b/src/types/Event.tsx index 9d06cdaa..896cc81f 100644 --- a/src/types/Event.tsx +++ b/src/types/Event.tsx @@ -86,6 +86,7 @@ export type Registration = { user_info: UserList; order: Order; has_paid_order?: boolean; + wait_queue_number?: number; }; export type PublicRegistration = { From 66243d1c70aacd15aa0991a04a8fe646d83373e0 Mon Sep 17 00:00:00 2001 From: Anders <69890033+lille-morille@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:00:07 +0100 Subject: [PATCH 06/11] Feat/event add users maunually (#917) * Added api endpoints locally and hooks * Added component and ui part * Small fix, only allow one person to be added * Updated Changelog --- CHANGELOG.md | 2 + src/api/api.tsx | 4 ++ src/hooks/Event.tsx | 9 +++ .../components/EventParticipants.tsx | 3 + .../components/EventUserRegistrator.tsx | 57 +++++++++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 src/pages/EventAdministration/components/EventUserRegistrator.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 72056369..a7cb5001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ## Neste versjon - ✨ **Arrangement**. Du kan nå se hvilken plass du har på ventelisten. +- ✨ **Arrangement**. Admins kan nå manuelt legge til deltagere på et arrangement. - ⚡ **Kalender**. Kalender pop-up viser viser infromasjonen bedre. - ✨ **Bannere**. Tidsbegrensede bannere kan nå opprettes. @@ -27,6 +28,7 @@ - 🦟 **Arrangement**. Knapp for å opprette arrangemnt på gruppesiden vises kun for admins. ## versjon 2023.09.7 + - ✨ **Vipps betaling**. Vipps betaling på arrangementer, der bruker blir kastet ut hvis ikke betalt innen frist. ## versjon 2023 04.17 diff --git a/src/api/api.tsx b/src/api/api.tsx index 18c1f16a..fa82760e 100644 --- a/src/api/api.tsx +++ b/src/api/api.tsx @@ -161,6 +161,10 @@ export default { deleteRegistration: (eventId: Event['id'], userId: User['user_id']) => IFetch({ method: 'DELETE', url: `${EVENTS_ENDPOINT}/${String(eventId)}/${EVENT_REGISTRATIONS_ENDPOINT}/${userId}/` }), + // Event registrations admin + createRegistrationAdmin: (eventId: Event['id'], userId: User['user_id']) => + IFetch({ method: 'POST', url: `${EVENTS_ENDPOINT}/${eventId}/${EVENT_REGISTRATIONS_ENDPOINT}/add/`, data: { user: userId } }), + // Forms getForm: (formId: string) => IFetch
({ method: 'GET', url: `${FORMS_ENDPOINT}/${formId}/` }), getFormTemplates: () => IFetch>({ method: 'GET', url: `${FORMS_ENDPOINT}/` }), diff --git a/src/hooks/Event.tsx b/src/hooks/Event.tsx index bb6d0481..b38d7c84 100644 --- a/src/hooks/Event.tsx +++ b/src/hooks/Event.tsx @@ -173,6 +173,15 @@ export const useCreateEventRegistration = (eventId: Event['id']): UseMutationRes }); }; +export const useCreateEventRegistrationAdmin = (eventId: Event['id']): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation((userId: User['user_id']) => API.createRegistrationAdmin(eventId, userId), { + onSuccess: () => { + queryClient.invalidateQueries(EVENT_QUERY_KEYS.registrations.all(eventId)); + }, + }); +}; + export const useUpdateEventRegistration = ( eventId: Event['id'], ): UseMutationResult< diff --git a/src/pages/EventAdministration/components/EventParticipants.tsx b/src/pages/EventAdministration/components/EventParticipants.tsx index decaad40..1adeba6c 100644 --- a/src/pages/EventAdministration/components/EventParticipants.tsx +++ b/src/pages/EventAdministration/components/EventParticipants.tsx @@ -16,6 +16,8 @@ import Participant from 'pages/EventAdministration/components/Participant'; import Pagination from 'components/layout/Pagination'; import Paper from 'components/layout/Paper'; +import EventUserRegistrator from './EventUserRegistrator'; + type RegistrationsProps = { onWait?: boolean; eventId: Event['id']; @@ -147,6 +149,7 @@ const EventParticipants = ({ eventId }: EventParticipantsProps) => { + diff --git a/src/pages/EventAdministration/components/EventUserRegistrator.tsx b/src/pages/EventAdministration/components/EventUserRegistrator.tsx new file mode 100644 index 00000000..f143e95d --- /dev/null +++ b/src/pages/EventAdministration/components/EventUserRegistrator.tsx @@ -0,0 +1,57 @@ +import AddIcon from '@mui/icons-material/Add'; +import { Button, ButtonProps } from '@mui/material'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +import { useCreateEventRegistrationAdmin } from 'hooks/Event'; +import { useSnackbar } from 'hooks/Snackbar'; + +import UserSearch from 'components/inputs/UserSearch'; +import Dialog from 'components/layout/Dialog'; + +import { UserBase } from '../../../types'; + +export type EventMessageSenderProps = ButtonProps & { + eventId: number; +}; + +type FormValues = { + user: UserBase; +}; + +const EventUserRegistrator = ({ eventId, ...props }: EventMessageSenderProps) => { + const [dialogOpen, setDialogOpen] = useState(false); + const showSnackbar = useSnackbar(); + const { formState, handleSubmit, reset, control } = useForm(); + const { mutateAsync } = useCreateEventRegistrationAdmin(eventId); + const submit = async (data: FormValues) => { + try { + await mutateAsync(data.user.user_id); + + showSnackbar('Deltager lagt til', 'success'); + reset(); + setDialogOpen(false); + } catch (e) { + showSnackbar(e.detail, 'error'); + } + }; + + return ( + <> + + setDialogOpen(false)} + onConfirm={handleSubmit(submit)} + open={dialogOpen} + titleText='Legg til deltager'> + + + + ); +}; + +export default EventUserRegistrator; From c5766509a7e8f36f7a1bd812112789441fddeeda Mon Sep 17 00:00:00 2001 From: Mads Nylund <73914541+MadsNyl@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:16:46 +0100 Subject: [PATCH 07/11] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7cb5001..162497c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ## Neste versjon +## versjon 2023.06.11 - ✨ **Arrangement**. Du kan nå se hvilken plass du har på ventelisten. - ✨ **Arrangement**. Admins kan nå manuelt legge til deltagere på et arrangement. - ⚡ **Kalender**. Kalender pop-up viser viser infromasjonen bedre. From 6f3760d12decb8f205c458a415a0b0c03661eeb3 Mon Sep 17 00:00:00 2001 From: Mads Nylund Date: Mon, 6 Nov 2023 09:24:00 +0100 Subject: [PATCH 08/11] Trigger build From 47cde6322f51c6bebdc294c17aa744713a8e3f1c Mon Sep 17 00:00:00 2001 From: Mads Nylund <73914541+MadsNyl@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:36:33 +0100 Subject: [PATCH 09/11] made page for creating and deleting QR codes (#907) * made page for creating and deleting QR codes * added error for invalid url, and download option * commit * fixed the error with deleting qr code * Update CHANGELOG.md * added error for invalid url * changed to qr generations at frontend * format * refactored to use QR code generator at frontend, and styled the component * fixes * format --- CHANGELOG.md | 3 + src/AppRoutes.tsx | 2 + src/URLS.tsx | 1 + src/api/api.tsx | 7 ++ src/components/navigation/Navigation.tsx | 1 + src/hooks/QRCode.tsx | 29 +++++++ src/pages/QRCodes/components/QRCodeItem.tsx | 86 +++++++++++++++++++++ src/pages/QRCodes/index.tsx | 77 ++++++++++++++++++ src/types/Misc.tsx | 7 ++ 9 files changed, 213 insertions(+) create mode 100644 src/hooks/QRCode.tsx create mode 100644 src/pages/QRCodes/components/QRCodeItem.tsx create mode 100644 src/pages/QRCodes/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 162497c6..257129db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ - ⚡ **Kalender**. Kalender pop-up viser viser infromasjonen bedre. - ✨ **Bannere**. Tidsbegrensede bannere kan nå opprettes. +## versjon 2023.14.10 +- ✨ **QR-kode generator**. Brukere kan nå generere sine egne QR koder fra en url. + ## versjon 2023.24.09 - 🎨 **Fult navn**. Kontaktperson på arrangementer vil nå bli vist med fult navn. diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 7b592703..6699285e 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -48,6 +48,7 @@ const LogIn = lazy(() => import('pages/LogIn')); const NewsAdministration = lazy(() => import('pages/NewsAdministration')); const Wiki = lazy(() => import('pages/Wiki')); const ShortLinks = lazy(() => import('pages/ShortLinks')); +const QRCodes = lazy(() => import('pages/QRCodes')); const SignUp = lazy(() => import('pages/SignUp')); const StrikeAdmin = lazy(() => import('pages/StrikeAdmin')); const Toddel = lazy(() => import('pages/Toddel')); @@ -148,6 +149,7 @@ const AppRoutes = () => { } />} path={`${URLS.cheatsheet}:studyId/:classId/`} /> } />} path={`${URLS.cheatsheet}*`} /> } />} path={URLS.shortLinks} /> + } />} path={URLS.qrCodes} /> } />} path={URLS.bannerAdmin}> } /> diff --git a/src/URLS.tsx b/src/URLS.tsx index 5c490520..c679fde0 100644 --- a/src/URLS.tsx +++ b/src/URLS.tsx @@ -52,6 +52,7 @@ const URLS = { profile: '/profil/', signup: '/ny-bruker/', shortLinks: '/linker/', + qrCodes: '/qr-koder/', gallery: '/galleri/', userAdmin: '/admin/brukere/', strikeAdmin: '/admin/prikker/', diff --git a/src/api/api.tsx b/src/api/api.tsx index fa82760e..8c3033b8 100644 --- a/src/api/api.tsx +++ b/src/api/api.tsx @@ -48,6 +48,7 @@ import { PaginationResponse, Picture, PublicRegistration, + QRCode, Registration, RequestResponse, ShortLink, @@ -96,6 +97,7 @@ export const NOTIFICATIONS_ENDPOINT = 'notifications'; export const NOTIFICATION_SETTINGS_ENDPOINT = 'notification-settings'; export const WIKI_ENDPOINT = 'pages'; export const SHORT_LINKS_ENDPOINT = 'short-links'; +export const QR_CODE_ENDPOINT = 'qr-codes'; export const STRIKES_ENDPOINT = 'strikes'; export const SUBMISSIONS_ENDPOINT = 'submissions'; export const USERS_ENDPOINT = 'users'; @@ -231,6 +233,11 @@ export default { createShortLink: (item: ShortLink) => IFetch({ method: 'POST', url: `${SHORT_LINKS_ENDPOINT}/`, data: item }), deleteShortLink: (slug: string) => IFetch({ method: 'DELETE', url: `${SHORT_LINKS_ENDPOINT}/${slug}/` }), + // QR codes + getQRCodes: (filters?: any) => IFetch>({ method: 'GET', url: `${QR_CODE_ENDPOINT}/`, data: filters || {} }), + createQRCode: (item: QRCode) => IFetch({ method: 'POST', url: `${QR_CODE_ENDPOINT}/`, data: item }), + deleteQRCode: (id: number) => IFetch({ method: 'DELETE', url: `${QR_CODE_ENDPOINT}/${String(id)}/` }), + // Gallery getGallery: (id: Gallery['id']) => IFetch({ method: 'GET', url: `${GALLERY_ENDPOINT}/${id}/` }), getGalleries: (filters?: any) => IFetch>({ method: 'GET', url: `${GALLERY_ENDPOINT}/`, data: filters || {} }), diff --git a/src/components/navigation/Navigation.tsx b/src/components/navigation/Navigation.tsx index a12963a0..8bb12668 100644 --- a/src/components/navigation/Navigation.tsx +++ b/src/components/navigation/Navigation.tsx @@ -142,6 +142,7 @@ const NavigationContent = ({ children }: NavigationProps) => { items: [ { text: 'Kokebok', to: URLS.cheatsheet }, { text: 'Link-forkorter', to: URLS.shortLinks }, + { text: 'QR koder', to: URLS.qrCodes }, { text: 'Badges ledertavler', to: URLS.badges.index }, { text: 'Galleri', to: URLS.gallery }, ], diff --git a/src/hooks/QRCode.tsx b/src/hooks/QRCode.tsx new file mode 100644 index 00000000..58450d23 --- /dev/null +++ b/src/hooks/QRCode.tsx @@ -0,0 +1,29 @@ +import { useMutation, UseMutationResult, useQuery, useQueryClient } from 'react-query'; + +import { QRCode, RequestResponse } from 'types'; + +import API from 'api/api'; + +export const QR_CODE_QUERY_KEY = 'qr-code'; + +export const useQRCodes = () => { + return useQuery, RequestResponse>([QR_CODE_QUERY_KEY], () => API.getQRCodes()); +}; + +export const useCreateQRCode = (): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation((item) => API.createQRCode(item), { + onSuccess: () => { + queryClient.invalidateQueries(QR_CODE_QUERY_KEY); + }, + }); +}; + +export const useDeleteQRCode = (id: number): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation(() => API.deleteQRCode(id), { + onSuccess: () => { + queryClient.invalidateQueries(QR_CODE_QUERY_KEY); + }, + }); +}; diff --git a/src/pages/QRCodes/components/QRCodeItem.tsx b/src/pages/QRCodes/components/QRCodeItem.tsx new file mode 100644 index 00000000..6c785fdf --- /dev/null +++ b/src/pages/QRCodes/components/QRCodeItem.tsx @@ -0,0 +1,86 @@ +import DeleteIcon from '@mui/icons-material/DeleteRounded'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import { Box, Button, Typography } from '@mui/material'; +import { QRCodeCanvas } from 'qrcode.react'; +import { useState } from 'react'; + +import { QRCode } from 'types'; + +import { useDeleteQRCode } from 'hooks/QRCode'; +import { useSnackbar } from 'hooks/Snackbar'; + +import Dialog from 'components/layout/Dialog'; +import Paper from 'components/layout/Paper'; + +export type QRCodeItemProps = { + qrCode: QRCode; +}; + +const QRCodeItem = ({ qrCode }: QRCodeItemProps) => { + const [removeDialogOpen, setRemoveDialogOpen] = useState(false); + const deleteQRCode = useDeleteQRCode(qrCode.id || -1); + const showSnackbar = useSnackbar(); + + const remove = async () => { + deleteQRCode.mutate(null, { + onSuccess: () => { + showSnackbar('QR koden ble slettet', 'success'); + }, + onError: (e) => { + showSnackbar(e.detail, 'error'); + }, + }); + }; + + const download = () => { + const canvas = document.getElementById(qrCode.id.toString()) as HTMLCanvasElement | null; + + if (canvas) { + const image = canvas.toDataURL('image/png'); + + const link = document.createElement('a'); + + link.download = `${qrCode.name}.png`; + + link.href = image; + + link.click(); + } + }; + + return ( + + + {qrCode.name} + + + + +
+ + +
+ setRemoveDialogOpen(false)} + onConfirm={remove} + open={removeDialogOpen} + titleText='Er du sikker?' + /> +
+ ); +}; + +export default QRCodeItem; diff --git a/src/pages/QRCodes/index.tsx b/src/pages/QRCodes/index.tsx new file mode 100644 index 00000000..1df2abd5 --- /dev/null +++ b/src/pages/QRCodes/index.tsx @@ -0,0 +1,77 @@ +import { Grid, Stack, Typography } from '@mui/material'; +import { useForm } from 'react-hook-form'; + +import { QRCode } from 'types'; + +import { useCreateQRCode, useQRCodes } from 'hooks/QRCode'; +import { useSnackbar } from 'hooks/Snackbar'; +import { useAnalytics } from 'hooks/Utils'; + +import SubmitButton from 'components/inputs/SubmitButton'; +import TextField from 'components/inputs/TextField'; +import Banner from 'components/layout/Banner'; +import Paper from 'components/layout/Paper'; +import NotFoundIndicator from 'components/miscellaneous/NotFoundIndicator'; +import Page from 'components/navigation/Page'; + +import QRCodeItem from './components/QRCodeItem'; + +const QRCodes = () => { + const { event } = useAnalytics(); + const { data, error, isFetching } = useQRCodes(); + const createQRCode = useCreateQRCode(); + const showSnackbar = useSnackbar(); + const { register, formState, handleSubmit, reset } = useForm(); + + const create = (data: QRCode) => { + createQRCode.mutate(data, { + onSuccess: () => { + showSnackbar('QR koden ble opprettet', 'success'); + reset(); + event('create', 'qr-code', `Created ${data.name}`); + }, + onError: (e) => { + showSnackbar(e.detail, 'error'); + }, + }); + }; + + return ( + } options={{ title: 'QR koder' }}> + + + {error && {error.detail}} + {data !== undefined && ( + <> + {!data.length && } + {data.map((qrCode) => ( + + ))} + + )} + + + + + Ny QR kode + + + + Opprett + + + + + + ); +}; + +export default QRCodes; diff --git a/src/types/Misc.tsx b/src/types/Misc.tsx index 61cd2c85..d34d3ae1 100644 --- a/src/types/Misc.tsx +++ b/src/types/Misc.tsx @@ -79,3 +79,10 @@ export interface Warning { type: WarningType; updated_at: string; } + +export interface QRCode { + id: number; + created_at: string; + name: string; + content: string; +} From 932a6cea1696410381dbeb859a134135d94df87e Mon Sep 17 00:00:00 2001 From: Mads Nylund <73914541+MadsNyl@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:20:29 +0100 Subject: [PATCH 10/11] Activity (#922) * first version * changed layout for activities on landing page * changed limit for activities on landing page --- .../miscellaneous/EventListItem.tsx | 14 +- .../components/ActivitiesDefaultView.tsx | 151 ++++++++++++++++++ .../Events/components/EventsDefaultView.tsx | 13 +- src/pages/Events/index.tsx | 9 +- .../components/ActivityEventsListView.tsx | 76 +++++++++ .../Landing/components/EventsCalendarView.tsx | 15 +- src/pages/Landing/components/EventsView.tsx | 27 ++-- src/theme.tsx | 3 + 8 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 src/pages/Events/components/ActivitiesDefaultView.tsx create mode 100644 src/pages/Landing/components/ActivityEventsListView.tsx diff --git a/src/components/miscellaneous/EventListItem.tsx b/src/components/miscellaneous/EventListItem.tsx index 4d24a37b..4746be24 100644 --- a/src/components/miscellaneous/EventListItem.tsx +++ b/src/components/miscellaneous/EventListItem.tsx @@ -81,8 +81,6 @@ const EventListItem = ({ event, sx }: EventListItemProps) => { const { observe, width } = useDimensions(); const theme = useTheme(); - const getColor = () => theme.palette.colors[event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase() ? 'nok_event' : 'other_event']; - const [height, titleFontSize, contentFontSize] = useMemo(() => { if (width < 400) { return [68, 18, 13]; @@ -97,6 +95,18 @@ const EventListItem = ({ event, sx }: EventListItemProps) => { const { data: categories = [] } = useCategories(); const categoryLabel = `${event.organizer ? `${event.organizer.name} | ` : ''}${categories.find((c) => c.id === event.category)?.text || 'Laster...'}`; + const getColor = () => { + if (categories.find((c) => c.id === event.category)?.text === 'Aktivitet') { + return theme.palette.colors.activity_event; + } + + if (event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase()) { + return theme.palette.colors.nok_event; + } + + return theme.palette.colors.other_event; + }; + return ( ({ + grid: { + display: 'grid', + gridTemplateColumns: '3fr 1fr', + gridGap: theme.spacing(2), + alignItems: 'self-start', + paddingBottom: theme.spacing(2), + + [theme.breakpoints.down('lg')]: { + gridTemplateColumns: '1fr', + }, + }, + list: { + display: 'grid', + gridTemplateColumns: '1fr', + gap: theme.spacing(1), + [theme.breakpoints.down('lg')]: { + order: 1, + }, + }, + settings: { + display: 'grid', + gridGap: theme.spacing(1), + position: 'sticky', + top: 80, + + [theme.breakpoints.down('lg')]: { + order: 0, + position: 'static', + top: 0, + }, + }, +})); + +type Filters = { + activity: boolean; + search?: string; + open_for_sign_up?: boolean; + user_favorite?: boolean; + expired: boolean; +}; + +const ActivitiesDefaultView = () => { + const isAuthenticated = useIsAuthenticated(); + const { event } = useAnalytics(); + const getInitialFilters = useCallback((): Filters => { + const params = new URLSearchParams(location.search); + const activity = true; + const expired = params.get('expired') ? Boolean(params.get('expired') === 'true') : false; + const open_for_sign_up = params.get('open_for_sign_up') ? Boolean(params.get('open_for_sign_up') === 'true') : undefined; + const user_favorite = params.get('user_favorite') ? Boolean(params.get('user_favorite') === 'true') : undefined; + const search = params.get('search') || undefined; + return { activity, expired, search, open_for_sign_up, user_favorite }; + }, []); + const { classes } = useStyles(); + const navigate = useNavigate(); + const lgDown = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg')); + const [filters, setFilters] = useState(getInitialFilters()); + const { data, error, hasNextPage, fetchNextPage, isLoading, isFetching } = useEvents(filters); + const events = useMemo(() => (data ? data.pages.map((page) => page.results).flat() : []), [data]); + const { register, control, handleSubmit, setValue, formState } = useForm({ defaultValues: getInitialFilters() }); + const isEmpty = useMemo(() => (data !== undefined ? !data.pages.some((page) => Boolean(page.results.length)) : false), [data]); + + const resetFilters = () => { + setValue('search', ''); + setValue('expired', false); + setValue('user_favorite', false); + setFilters({ activity: true, expired: false, open_for_sign_up: false, user_favorite: false }); + navigate(`${location.pathname}${argsToParams({ expired: false })}`, { replace: true }); + }; + + const search = (data: Filters) => { + event('search', 'events', JSON.stringify(data)); + setFilters(data); + navigate(`${location.pathname}${argsToParams(data)}`, { replace: true }); + !lgDown || setSearchFormExpanded((prev) => !prev); + }; + + const [searchFormExpanded, setSearchFormExpanded] = useState(false); + + const SearchForm = () => ( +
+ + + + {isAuthenticated && } + + Søk + + + + + ); + + return ( + <> +
+
+ {isLoading && } + {isEmpty && } + {error && {error.detail}} + {data !== undefined && ( + fetchNextPage()}> + + {events.map((event) => ( + + ))} + + + )} + {isFetching && } +
+ {lgDown ? ( +
+ setSearchFormExpanded((prev) => !prev)}> + + +
+ ) : ( + + + + )} +
+ + ); +}; + +export default ActivitiesDefaultView; diff --git a/src/pages/Events/components/EventsDefaultView.tsx b/src/pages/Events/components/EventsDefaultView.tsx index 00267f62..eef4fb76 100644 --- a/src/pages/Events/components/EventsDefaultView.tsx +++ b/src/pages/Events/components/EventsDefaultView.tsx @@ -88,6 +88,7 @@ const EventsDefaultView = () => { setValue('category', ''); setValue('search', ''); setValue('expired', false); + setValue('user_favorite', false); setFilters({ expired: false, open_for_sign_up: false, user_favorite: false }); navigate(`${location.pathname}${argsToParams({ expired: false })}`, { replace: true }); }; @@ -106,11 +107,13 @@ const EventsDefaultView = () => { {Boolean(categories.length) && ( )} diff --git a/src/pages/Events/index.tsx b/src/pages/Events/index.tsx index e82c92d3..bff3dd93 100644 --- a/src/pages/Events/index.tsx +++ b/src/pages/Events/index.tsx @@ -1,3 +1,4 @@ +import CelebrationIcon from '@mui/icons-material/Celebration'; import DateRange from '@mui/icons-material/DateRangeRounded'; import Reorder from '@mui/icons-material/ReorderRounded'; import { Collapse, Skeleton } from '@mui/material'; @@ -9,12 +10,15 @@ import Banner from 'components/layout/Banner'; import Tabs from 'components/layout/Tabs'; import Page from 'components/navigation/Page'; +import ActivitiesDefaultView from './components/ActivitiesDefaultView'; + const EventsCalendarView = lazy(() => import(/* webpackChunkName: "events_calendar" */ 'pages/Landing/components/EventsCalendarView')); const Events = () => { const listTab = { value: 'list', label: 'Liste', icon: Reorder }; + const activityTab = { value: 'activity', label: 'Aktiviteter', icon: CelebrationIcon }; const calendarTab = { value: 'calendar', label: 'Kalender', icon: DateRange }; - const tabs = [listTab, calendarTab]; + const tabs = [listTab, activityTab, calendarTab]; const [tab, setTab] = useState(listTab.value); return ( @@ -23,6 +27,9 @@ const Events = () => { + + + }> diff --git a/src/pages/Landing/components/ActivityEventsListView.tsx b/src/pages/Landing/components/ActivityEventsListView.tsx new file mode 100644 index 00000000..1d44747b --- /dev/null +++ b/src/pages/Landing/components/ActivityEventsListView.tsx @@ -0,0 +1,76 @@ +import { Stack, styled, Theme, Typography, useMediaQuery } from '@mui/material'; +import { useCallback, useState } from 'react'; + +import { useEvents } from 'hooks/Event'; + +import EventListItem, { EventListItemLoading } from 'components/miscellaneous/EventListItem'; + +const Container = styled('div')(({ theme }) => ({ + display: 'grid', + alignItems: 'self-start', + gridTemplateColumns: '1fr 1fr', + gap: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, + p: 0.5, +})); + +const NO_OF_EVENTS_TO_SHOW = 6; +const NO_OF_EVENTS_TO_SHOW_MD_DOWN = 4; + +type Filters = { + activity: boolean; +}; + +const ActivityEventsListView = () => { + const mdDown = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')); + const getInitialFilters = useCallback((): Filters => { + const activity = true; + return { activity }; + }, []); + const [filters] = useState(getInitialFilters()); + + const { data, isLoading } = useEvents(filters); + + if (isLoading) { + return ( + + + + + + ); + } else if (!data?.pages[0]?.results.length) { + return ( + + Ingen kommende arrangementer + + ); + } else if (mdDown) { + return ( + + {data?.pages[0]?.results.slice(0, NO_OF_EVENTS_TO_SHOW_MD_DOWN).map((event) => ( + + ))} + + ); + } + + return ( + + {data?.pages[0].results.length ? ( + data?.pages[0]?.results.slice(0, NO_OF_EVENTS_TO_SHOW).map((event) => ) + ) : ( + + + Ingen kommende aktiviteter + + + )} + + ); +}; + +export default ActivityEventsListView; diff --git a/src/pages/Landing/components/EventsCalendarView.tsx b/src/pages/Landing/components/EventsCalendarView.tsx index 3e6ad509..63099125 100644 --- a/src/pages/Landing/components/EventsCalendarView.tsx +++ b/src/pages/Landing/components/EventsCalendarView.tsx @@ -7,6 +7,7 @@ import { ReactNode, useEffect, useMemo, useState } from 'react'; import { Category, EventList } from 'types'; import { Groups } from 'types/Enums'; +import { useCategories } from 'hooks/Categories'; import { useEvents } from 'hooks/Event'; import { useAnalytics } from 'hooks/Utils'; @@ -40,7 +41,19 @@ const Appointment = ({ children, data }: AppointmentProps) => { setAnchorEl(null); }; - const getColor = (event: EventList) => theme.palette.colors[event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase() ? 'nok_event' : 'other_event']; + const { data: categories = [] } = useCategories(); + + const getColor = (event: EventList) => { + if (categories.find((c) => c.id === event.category)?.text === 'Aktivitet') { + return theme.palette.colors.activity_event; + } + + if (event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase()) { + return theme.palette.colors.nok_event; + } + + return theme.palette.colors.other_event; + }; return ( <>