From 55e99bdfc555d9d6201b41f311fc114a9a65c6ae Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 20:11:26 +0200 Subject: [PATCH 01/12] Setup event status types and translations --- frontend/src/i18n/constants.ts | 9 +++++++++ frontend/src/i18n/translations.ts | 18 ++++++++++++++++++ frontend/src/types.ts | 10 +++++++++- frontend/src/utils.ts | 15 +++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index bff73255a..99298970b 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -332,6 +332,14 @@ export const KEY = { event_category_theme_party: 'event_category_theme_party', event_category_uka_event: 'event_category_uka_event', + // Event statuses + event_status: 'event_status', + event_status_public: 'event_status_public', + event_status_private: 'event_status_private', + event_status_archived: 'event_status_archived', + event_status_cancelled: 'event_status_cancelled', + event_status_deleted: 'event_status_deleted', + // Venue Page: venuepage_title: 'venuepage_title', @@ -689,6 +697,7 @@ export const KEY = { event_form_category_required: 'event_form_category_required', event_form_age_restriction_required: 'event_form_age_restriction_required', event_form_ticket_type_required: 'event_form_ticket_type_required', + event_form_status_required: 'event_form_status_required', } as const; // This will ensure that each value matches the key exactly. diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index b2824092c..3bbd747e6 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -305,6 +305,14 @@ export const nb = prepareTranslations({ [KEY.event_category_theme_party]: 'Temafest', [KEY.event_category_uka_event]: 'UKE-arrangement', + // Event statuses + [KEY.event_status]: 'Status', + [KEY.event_status_public]: 'Offentlig', + [KEY.event_status_private]: 'Privat', + [KEY.event_status_archived]: 'Arkivert', + [KEY.event_status_cancelled]: 'Avlyst', + [KEY.event_status_deleted]: 'Slettet', + //Purchase Ticket Info: [KEY.invalid_email_message]: 'Ugyldig e-postformat', [KEY.email_or_membership_number_message]: 'Du må oppgi enten en e-post eller et medlemsnummer', @@ -708,6 +716,7 @@ export const nb = prepareTranslations({ [KEY.event_form_category_required]: 'Kategori er påkrevd', [KEY.event_form_age_restriction_required]: 'Aldersgrense er påkrevd', [KEY.event_form_ticket_type_required]: 'Billetttype er påkrevd', + [KEY.event_form_status_required]: 'Status er påkrevd', }); export const en = prepareTranslations({ @@ -1003,6 +1012,14 @@ export const en = prepareTranslations({ [KEY.event_category_theme_party]: 'Theme party', [KEY.event_category_uka_event]: 'UKA event', + // Event statuses + [KEY.event_status]: 'Status', + [KEY.event_status_public]: 'Public', + [KEY.event_status_private]: 'Private', + [KEY.event_status_archived]: 'Archived', + [KEY.event_status_cancelled]: 'Cancelled', + [KEY.event_status_deleted]: 'Deleted', + // EventPage: [KEY.event_registration_url]: 'Registration URL', [KEY.event_add_ticket]: 'Add ticket', @@ -1409,4 +1426,5 @@ export const en = prepareTranslations({ [KEY.event_form_category_required]: 'Category is required', [KEY.event_form_age_restriction_required]: 'Age restriction is required', [KEY.event_form_ticket_type_required]: 'Ticket type is required', + [KEY.event_form_status_required]: 'Status is required', }); diff --git a/frontend/src/types.ts b/frontend/src/types.ts index cbba76575..8672cae6f 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -105,7 +105,15 @@ export const ALL_DAYS: Day[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'f export const WEEK_DAYS: Day[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']; /** Event types */ -export type EventStatus = 'active' | 'cancelled' | 'archived' | 'deleted'; +export const EventStatusChoice = { + PUBLIC: 'public', + PRIVATE: 'private', + ARCHIVED: 'archived', + CANCELLED: 'cancelled', + DELETED: 'deleted', +} as const; + +export type EventStatus = (typeof EventStatusChoice)[keyof typeof EventStatusChoice]; export const EventAgeRestriction = { NONE: 'none', diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 9e5c6f944..f97fac323 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -14,6 +14,7 @@ import { EventAgeRestriction, type EventAgeRestrictionValue, type EventCategoryValue, + type EventStatus, EventTicketType, type EventTicketTypeValue, } from './types'; @@ -280,6 +281,20 @@ export function getAgeRestrictionKey(age: EventAgeRestrictionValue): Translation } } +/** + * Gets the translation key for a given event status + */ +export function getEventStatusTranslationKey(status: EventStatus): TranslationKeys { + const map: Record = { + public: KEY.event_status_public, + private: KEY.event_status_private, + archived: KEY.event_status_archived, + cancelled: KEY.event_status_cancelled, + deleted: KEY.event_status_deleted, + }; + return map[status]; +} + /** * Converts a UTC timestring from django to * a local timestring suitable for html input elements From e8640f167154a1fd26993c87999ed264f2a69b22 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 20:12:57 +0200 Subject: [PATCH 02/12] Add event status to event dto --- frontend/src/dto.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 41d555090..1309979fe 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -191,6 +191,8 @@ export type EventDto = { }; export type EventWriteDto = { + status?: EventStatus; + title_nb: string; title_en: string; description_long_nb: string; From db6cef405f8ee9daf7b6e8066ec16615e1f35afe Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 20:14:45 +0200 Subject: [PATCH 03/12] Setup event status in event schema --- .../src/PagesAdmin/EventCreatorAdminPage/EventCreatorSchema.ts | 2 ++ .../EventCreatorAdminPage/hooks/useEventCreatorForm.ts | 2 ++ .../src/PagesAdmin/EventCreatorAdminPage/steps/stepConfig.ts | 2 +- frontend/src/schema/event.ts | 3 ++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorSchema.ts b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorSchema.ts index 6a9ca71a6..2add0a854 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorSchema.ts +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorSchema.ts @@ -12,6 +12,7 @@ import { EVENT_LOCATION, EVENT_REGISTRATION_URL, EVENT_START_DT, + EVENT_STATUS, EVENT_TICKET_TYPE, EVENT_TITLE, EVENT_VISIBILITY_FROM_DT, @@ -51,6 +52,7 @@ export const eventSchema = z.object({ // Graphics image: OPTIONAL_IMAGE, // Summary/Publication date + status: EVENT_STATUS, visibility_from_dt: EVENT_VISIBILITY_FROM_DT, visibility_to_dt: EVENT_VISIBILITY_TO_DT, }); diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts b/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts index bc39fa4fd..93a1c8ef6 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts @@ -46,6 +46,7 @@ export function useEventCreatorForm(params: { custom_tickets: [], billig_id: undefined, image: undefined, + status: 'public', visibility_from_dt: '', visibility_to_dt: '', }, @@ -75,6 +76,7 @@ export function useEventCreatorForm(params: { custom_tickets: event.custom_tickets || [], billig_id: event.billig?.id, image: event.image ?? undefined, + status: event.status || 'public', visibility_from_dt: event.visibility_from_dt ? utcTimestampToLocal(event.visibility_from_dt, false) : '', visibility_to_dt: event.visibility_to_dt ? utcTimestampToLocal(event.visibility_to_dt, false) : '', }; diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/stepConfig.ts b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/stepConfig.ts index e5027cbde..5891ad48f 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/stepConfig.ts +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/stepConfig.ts @@ -59,6 +59,6 @@ export const steps: EventCreatorStep[] = [ title_nb: 'Oppsummering', title_en: 'Summary', customIcon: 'ic:outline-remove-red-eye', - validate: (d) => !!d.visibility_from_dt, + validate: (d) => !!d.visibility_from_dt && !!d.status, }, ]; diff --git a/frontend/src/schema/event.ts b/frontend/src/schema/event.ts index f513fd153..d6188e636 100644 --- a/frontend/src/schema/event.ts +++ b/frontend/src/schema/event.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { KEY } from '~/i18n/constants'; -import { EventAgeRestriction, EventCategory, EventTicketType } from '~/types'; +import { EventAgeRestriction, EventCategory, EventStatusChoice, EventTicketType } from '~/types'; import { zodEnum } from './utils'; export const EVENT_TITLE = z.string().min(1, { message: KEY.event_form_title_required }); @@ -26,3 +26,4 @@ export const EVENT_X_LINK = z.string().url().optional(); export const EVENT_CATEGORY = zodEnum(EventCategory, KEY.event_form_category_required); export const EVENT_AGE_RESTRICTION = zodEnum(EventAgeRestriction, KEY.event_form_age_restriction_required); export const EVENT_TICKET_TYPE = zodEnum(EventTicketType, KEY.event_form_ticket_type_required); +export const EVENT_STATUS = zodEnum(EventStatusChoice, KEY.event_form_status_required); From a131acb393797e505ca483fddaeda4aa29115567 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 20:15:44 +0200 Subject: [PATCH 04/12] Add event status field to event form --- .../EventCreatorAdminPage.tsx | 18 +++++- .../steps/SummaryStep.tsx | 56 +++++++++++++------ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx index e0706e0da..f2d9761e1 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx @@ -13,8 +13,15 @@ import type { EventDto } from '~/dto'; import { usePrevious, useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { venueKeys } from '~/queryKeys'; -import { EventAgeRestriction, type EventAgeRestrictionValue, EventCategory, type EventCategoryValue } from '~/types'; -import { dbT, getAgeRestrictionKey, getEventCategoryKey, lowerCapitalize } from '~/utils'; +import { + EventAgeRestriction, + type EventAgeRestrictionValue, + EventCategory, + type EventCategoryValue, + type EventStatus, + EventStatusChoice, +} from '~/types'; +import { dbT, getAgeRestrictionKey, getEventCategoryKey, getEventStatusTranslationKey, lowerCapitalize } from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './EventCreatorAdminPage.module.scss'; import { type FormType, useEventCreatorForm } from './hooks/useEventCreatorForm'; @@ -57,6 +64,11 @@ export function EventCreatorAdminPage() { label: t(getAgeRestrictionKey(age)), })); + const eventStatusOptions: DropdownOption[] = Object.values(EventStatusChoice).map((status) => ({ + value: status, + label: t(getEventStatusTranslationKey(status)), + })); + const { form, watchedValues, buildPayload } = useEventCreatorForm({ event, defaultCategory: eventCategoryOptions[0]?.value ?? EventCategory.ART, @@ -68,7 +80,7 @@ export function EventCreatorAdminPage() { info: , payment: , graphics: , - summary: , + summary: , }; // Fetch event data using the event ID diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx index 3f95cc438..d04f6b6cd 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx @@ -1,27 +1,51 @@ import type { UseFormReturn } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from '~/Components'; +import { Dropdown, FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from '~/Components'; +import type { DropdownOption } from '~/Components/Dropdown/Dropdown'; import { KEY } from '~/i18n/constants'; +import type { EventStatus } from '~/types'; import styles from '../EventCreatorAdminPage.module.scss'; import type { FormType } from '../hooks/useEventCreatorForm'; -export function SummaryStep({ form }: { form: UseFormReturn }) { +type Props = { + form: UseFormReturn; + eventStatusOptions: DropdownOption[]; +}; + +export function SummaryStep({ form, eventStatusOptions }: Props) { const { t } = useTranslation(); return ( - ( - - {t(KEY.saksdokumentpage_publication_date) ?? ''} - - - - - - )} - /> +
+ ( + + {t(KEY.saksdokumentpage_publication_date) ?? ''} + + + + + + )} + /> + + ( + + {t(KEY.event_status)} + + + + + + )} + /> +
); } From c7916b761f9a21855dc33e0b0cae2e299ef1a1e7 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 20:21:23 +0200 Subject: [PATCH 05/12] Fix event creation bug in legacy component page --- frontend/src/Pages/ComponentPage/ComponentPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Pages/ComponentPage/ComponentPage.tsx b/frontend/src/Pages/ComponentPage/ComponentPage.tsx index 7250ffe1f..874910916 100644 --- a/frontend/src/Pages/ComponentPage/ComponentPage.tsx +++ b/frontend/src/Pages/ComponentPage/ComponentPage.tsx @@ -65,7 +65,7 @@ export function ComponentPage() { visibility_from_dt: new Date().toISOString(), visibility_to_dt: '', start_dt: new Date().toISOString(), - status: 'active', + status: 'public', ticket_type: 'free', title_en: 'Von August with a very long title just like this // 23:59', title_nb: 'Von August // 23:59', From 42f96aea85058d141203b6e81604f821abf0f1d9 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 23:22:00 +0200 Subject: [PATCH 06/12] remove harcoded string --- .../EventCreatorAdminPage/hooks/useEventCreatorForm.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts b/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts index 93a1c8ef6..69d85e4b7 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/hooks/useEventCreatorForm.ts @@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'; import type { z } from 'zod'; import type { EventDto, EventWriteDto } from '~/dto'; -import type { EventCategoryValue } from '~/types'; +import { type EventCategoryValue, EventStatusChoice } from '~/types'; import { utcTimestampToLocal } from '~/utils'; import { eventSchema } from '../EventCreatorSchema'; @@ -46,7 +46,7 @@ export function useEventCreatorForm(params: { custom_tickets: [], billig_id: undefined, image: undefined, - status: 'public', + status: EventStatusChoice.PUBLIC, visibility_from_dt: '', visibility_to_dt: '', }, @@ -76,7 +76,7 @@ export function useEventCreatorForm(params: { custom_tickets: event.custom_tickets || [], billig_id: event.billig?.id, image: event.image ?? undefined, - status: event.status || 'public', + status: event.status || EventStatusChoice.PUBLIC, visibility_from_dt: event.visibility_from_dt ? utcTimestampToLocal(event.visibility_from_dt, false) : '', visibility_to_dt: event.visibility_to_dt ? utcTimestampToLocal(event.visibility_to_dt, false) : '', }; From 706e5891889a54b538844eafd3aadd17a8dea128 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2026 23:22:45 +0200 Subject: [PATCH 07/12] Refactor event status options to conditionally include available statuses based on use case --- .../EventCreatorAdminPage/EventCreatorAdminPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx index f2d9761e1..a6fc6e0f3 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx @@ -64,7 +64,11 @@ export function EventCreatorAdminPage() { label: t(getAgeRestrictionKey(age)), })); - const eventStatusOptions: DropdownOption[] = Object.values(EventStatusChoice).map((status) => ({ + const availableEventStatuses: EventStatus[] = id + ? Object.values(EventStatusChoice) + : [EventStatusChoice.PUBLIC, EventStatusChoice.PRIVATE]; + + const eventStatusOptions: DropdownOption[] = availableEventStatuses.map((status) => ({ value: status, label: t(getEventStatusTranslationKey(status)), })); From a6a8517b086ea983a12a3b62fa53597aa53d1122 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 9 Apr 2026 20:22:26 +0200 Subject: [PATCH 08/12] Add translations for event status information modal; Add function to get event status description translation key --- frontend/src/i18n/constants.ts | 8 ++++++++ frontend/src/i18n/translations.ts | 18 ++++++++++++++++++ frontend/src/utils.ts | 14 ++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index 99298970b..c999ebf18 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -339,6 +339,14 @@ export const KEY = { event_status_archived: 'event_status_archived', event_status_cancelled: 'event_status_cancelled', event_status_deleted: 'event_status_deleted', + event_status_help_title: 'event_status_help_title', + event_status_help_intro: 'event_status_help_intro', + event_status_help_button_aria_label: 'event_status_help_button_aria_label', + event_status_help_public: 'event_status_help_public', + event_status_help_private: 'event_status_help_private', + event_status_help_archived: 'event_status_help_archived', + event_status_help_cancelled: 'event_status_help_cancelled', + event_status_help_deleted: 'event_status_help_deleted', // Venue Page: venuepage_title: 'venuepage_title', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index 3bbd747e6..ce768feb3 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -312,6 +312,15 @@ export const nb = prepareTranslations({ [KEY.event_status_archived]: 'Arkivert', [KEY.event_status_cancelled]: 'Avlyst', [KEY.event_status_deleted]: 'Slettet', + [KEY.event_status_help_title]: 'Hva betyr statusene?', + [KEY.event_status_help_intro]: 'Status styrer hvordan arrangementet vises og håndteres.', + [KEY.event_status_help_button_aria_label]: 'Vis forklaring av arrangementsstatus', + [KEY.event_status_help_public]: 'Arrangementet er synlig for alle.', + [KEY.event_status_help_private]: + 'Arrangementet er skjult i alle oversikter, men kan kun nås via direkte lenke. Dette er nyttig for interne arrangementer.', + [KEY.event_status_help_archived]: 'Arrangementet er avsluttet og flyttet til historikk.', + [KEY.event_status_help_cancelled]: 'Arrangementet er avlyst og vises således.', + [KEY.event_status_help_deleted]: 'Arrangementet er markert som slettet og vises ikke i det hele tatt.', //Purchase Ticket Info: [KEY.invalid_email_message]: 'Ugyldig e-postformat', @@ -1019,6 +1028,15 @@ export const en = prepareTranslations({ [KEY.event_status_archived]: 'Archived', [KEY.event_status_cancelled]: 'Cancelled', [KEY.event_status_deleted]: 'Deleted', + [KEY.event_status_help_title]: 'What do the statuses mean?', + [KEY.event_status_help_intro]: 'Status controls how the event is shown and handled.', + [KEY.event_status_help_button_aria_label]: 'Show event status explanation', + [KEY.event_status_help_public]: 'The event is visible to everyone.', + [KEY.event_status_help_private]: + 'The event is hidden in all overviews, but can only be reached through a direct link. This is useful for internal events.', + [KEY.event_status_help_archived]: 'The event is finished and moved to history.', + [KEY.event_status_help_cancelled]: 'The event is cancelled and shown as such.', + [KEY.event_status_help_deleted]: 'The event is marked as deleted and is not shown at all.', // EventPage: [KEY.event_registration_url]: 'Registration URL', diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index f97fac323..3643a9e0b 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -295,6 +295,20 @@ export function getEventStatusTranslationKey(status: EventStatus): TranslationKe return map[status]; } +/** + * Gets the description translation key for a given event status + */ +export function getEventStatusDescriptionTranslationKey(status: EventStatus): TranslationKeys { + const map: Record = { + public: KEY.event_status_help_public, + private: KEY.event_status_help_private, + archived: KEY.event_status_help_archived, + cancelled: KEY.event_status_help_cancelled, + deleted: KEY.event_status_help_deleted, + }; + return map[status]; +} + /** * Converts a UTC timestring from django to * a local timestring suitable for html input elements From 3d6601f44c64bea70ecd629950e8a2e79fbb4339 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 9 Apr 2026 20:36:47 +0200 Subject: [PATCH 09/12] Add a close key to translations --- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index c999ebf18..5537f0ba3 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -100,6 +100,7 @@ export const KEY = { common_venue: 'common_venue', common_active: 'common_active', common_cancel: 'common_cancel', + common_close: 'common_close', common_events: 'common_events', common_repeat: 'common_repeat', common_venues: 'common_venues', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index ce768feb3..67dcea040 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -98,6 +98,7 @@ export const nb = prepareTranslations({ [KEY.common_delete]: 'Slett', [KEY.common_logout]: 'Logg ut', [KEY.common_cancel]: 'Avbryt', + [KEY.common_close]: 'Lukk', [KEY.common_go_back]: 'Tilbake', [KEY.common_interval]: 'Intervall', [KEY.common_preview]: 'Forhåndsvisning', @@ -817,6 +818,7 @@ export const en = prepareTranslations({ [KEY.common_role]: 'Role', [KEY.common_active]: 'Active', [KEY.common_cancel]: 'Cancel', + [KEY.common_close]: 'Close', [KEY.common_venues]: 'Venues', [KEY.common_shortcuts]: 'Shortcuts', [KEY.common_events]: 'Events', From 30871d0381cc3b09ecbc39a483b45ed57fc6d14b Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 9 Apr 2026 20:40:23 +0200 Subject: [PATCH 10/12] Add event status help modal and description to status options --- .../EventCreatorAdminPage.module.scss | 89 ++++++++++++ .../EventCreatorAdminPage.tsx | 13 +- .../steps/SummaryStep.tsx | 132 +++++++++++++----- .../PagesAdmin/EventCreatorAdminPage/types.ts | 6 + 4 files changed, 203 insertions(+), 37 deletions(-) create mode 100644 frontend/src/PagesAdmin/EventCreatorAdminPage/types.ts diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.module.scss b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.module.scss index 8369434ec..5916c83b3 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.module.scss +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.module.scss @@ -87,3 +87,92 @@ padding-bottom: 3.75em; gap: 0.5em; } + +.status_label_row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5em; +} + +.status_info_button { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: none; + border-radius: 999px; + padding: 0; + width: 1.35rem; + height: 1.35rem; + background: transparent; + color: $blue; + + &:hover { + background: rgba($blue, 0.12); + } + + &:focus-visible { + outline: 2px solid $blue; + outline-offset: 2px; + } +} + +.status_help_modal { + width: min(32rem, calc(100vw - 2rem)); +} + +.status_help_modal_header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.75rem; + + h3 { + margin: 0; + } +} + +.status_help_close_button { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: none; + border-radius: 999px; + width: 2rem; + height: 2rem; + background: transparent; + color: $grey-1; + + &:hover { + background: rgba($grey-2, 0.2); + } + + @include theme-dark { + color: $grey-4; + + &:hover { + background: rgba($grey-4, 0.2); + } + } +} + +.status_help_intro { + margin: 0.5rem 0 0; +} + +.status_help_list { + margin: 0.75rem 0 0; + padding-left: 1.2rem; + + li + li { + margin-top: 0.5rem; + } +} + +.status_help_actions { + display: flex; + justify-content: flex-end; + margin-top: 1.25rem; +} diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx index a6fc6e0f3..50ea114c2 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/EventCreatorAdminPage.tsx @@ -21,7 +21,14 @@ import { type EventStatus, EventStatusChoice, } from '~/types'; -import { dbT, getAgeRestrictionKey, getEventCategoryKey, getEventStatusTranslationKey, lowerCapitalize } from '~/utils'; +import { + dbT, + getAgeRestrictionKey, + getEventCategoryKey, + getEventStatusDescriptionTranslationKey, + getEventStatusTranslationKey, + lowerCapitalize, +} from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './EventCreatorAdminPage.module.scss'; import { type FormType, useEventCreatorForm } from './hooks/useEventCreatorForm'; @@ -37,6 +44,7 @@ import { InfoStep } from './steps/InfoStep'; import { PaymentStep } from './steps/PaymentStep'; import { SummaryStep } from './steps/SummaryStep'; import { TextStep } from './steps/TextStep'; +import type { EventStatusOption } from './types'; export function EventCreatorAdminPage() { const { t } = useTranslation(); @@ -68,9 +76,10 @@ export function EventCreatorAdminPage() { ? Object.values(EventStatusChoice) : [EventStatusChoice.PUBLIC, EventStatusChoice.PRIVATE]; - const eventStatusOptions: DropdownOption[] = availableEventStatuses.map((status) => ({ + const eventStatusOptions: EventStatusOption[] = availableEventStatuses.map((status) => ({ value: status, label: t(getEventStatusTranslationKey(status)), + description: t(getEventStatusDescriptionTranslationKey(status)), })); const { form, watchedValues, buildPayload } = useEventCreatorForm({ diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx index d04f6b6cd..304e32f12 100644 --- a/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx @@ -1,51 +1,113 @@ +import { Icon } from '@iconify/react'; +import { useState } from 'react'; import type { UseFormReturn } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { Dropdown, FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from '~/Components'; -import type { DropdownOption } from '~/Components/Dropdown/Dropdown'; +import { + Button, + Dropdown, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, + Modal, + Text, +} from '~/Components'; import { KEY } from '~/i18n/constants'; -import type { EventStatus } from '~/types'; import styles from '../EventCreatorAdminPage.module.scss'; import type { FormType } from '../hooks/useEventCreatorForm'; +import type { EventStatusOption } from '../types'; type Props = { form: UseFormReturn; - eventStatusOptions: DropdownOption[]; + eventStatusOptions: EventStatusOption[]; }; export function SummaryStep({ form, eventStatusOptions }: Props) { const { t } = useTranslation(); + const [isStatusInfoOpen, setIsStatusInfoOpen] = useState(false); return ( -
- ( - - {t(KEY.saksdokumentpage_publication_date) ?? ''} - - - - - - )} - /> - - ( - - {t(KEY.event_status)} - - - - - - )} - /> -
+ <> +
+ ( + + {t(KEY.saksdokumentpage_publication_date) ?? ''} + + + + + + )} + /> + + ( + +
+ {t(KEY.event_status)} + +
+ + + + +
+ )} + /> +
+ + setIsStatusInfoOpen(false)} + className={styles.status_help_modal} + > +
+ + {t(KEY.event_status_help_title)} + + +
+ + {t(KEY.event_status_help_intro)} + +
    + {eventStatusOptions.map((option) => ( +
  • + {option.label}: {option.description} +
  • + ))} +
+ +
+ +
+
+ ); } diff --git a/frontend/src/PagesAdmin/EventCreatorAdminPage/types.ts b/frontend/src/PagesAdmin/EventCreatorAdminPage/types.ts new file mode 100644 index 000000000..0bce08a15 --- /dev/null +++ b/frontend/src/PagesAdmin/EventCreatorAdminPage/types.ts @@ -0,0 +1,6 @@ +import type { DropdownOption } from '~/Components/Dropdown/Dropdown'; +import type { EventStatus } from '~/types'; + +export type EventStatusOption = DropdownOption & { + description: string; +}; From 599e631392d98250bbb9ec073ca66949c30ce66b Mon Sep 17 00:00:00 2001 From: Bragon Date: Sun, 12 Apr 2026 21:03:41 +0200 Subject: [PATCH 11/12] feat: add new page for weekly opening times --- .../src/Components/NavbarSamfThree/Navbar.tsx | 4 ++-- .../WeeklyOpeningPage.module.scss | 3 +++ .../WeeklyOpeningPage/WeeklyOpeningPage.tsx | 23 +++++++++++++++++++ frontend/src/Pages/WeeklyOpeningPage/index.ts | 1 + frontend/src/router/router.tsx | 2 ++ frontend/src/routes/frontend.ts | 1 + 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss create mode 100644 frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx create mode 100644 frontend/src/Pages/WeeklyOpeningPage/index.ts diff --git a/frontend/src/Components/NavbarSamfThree/Navbar.tsx b/frontend/src/Components/NavbarSamfThree/Navbar.tsx index 31abe1b22..69cfea128 100644 --- a/frontend/src/Components/NavbarSamfThree/Navbar.tsx +++ b/frontend/src/Components/NavbarSamfThree/Navbar.tsx @@ -17,7 +17,7 @@ import type { RecruitmentDto } from '~/dto'; import { useDesktop } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { ROUTES } from '~/routes'; -import { SAMF3_MEMBER_URL } from '~/routes/samf-three'; +import { ROUTES_SAMF_THREE, SAMF3_MEMBER_URL } from '~/routes/samf-three'; import styles from './Navbar.module.scss'; import { HamburgerMenu, LanguageButton } from './components'; import { NavbarItem } from './components/NavbarItem'; @@ -85,7 +85,7 @@ export function Navbar() { {t(KEY.common_membership)} setExpandedDropdown('')} > diff --git a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss new file mode 100644 index 000000000..220603ed3 --- /dev/null +++ b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss @@ -0,0 +1,3 @@ +@use 'src/mixins' as *; + +@use 'src/constants' as *; \ No newline at end of file diff --git a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx new file mode 100644 index 000000000..b1f736018 --- /dev/null +++ b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx @@ -0,0 +1,23 @@ +import { useTranslation } from 'react-i18next'; +import { Page } from '~/Components'; +import { useTitle } from '~/hooks'; +import { useState } from 'react'; +import { getVenues } from '~/api'; +import { useQuery } from '@tanstack/react-query'; +import { venueKeys } from '~/queryKeys'; +import { KEY } from '~/i18n/constants'; + +export function WeeklyOpeningPage() { + const { t } = useTranslation(); + useTitle(t(KEY.common_opening_hours)) + + const { data: venues = [], isLoading } = useQuery({ + queryKey: venueKeys.all, + queryFn: getVenues, + select: (data) => [...data].sort((venueA, venueB) => venueA.name.localeCompare(venueB.name)), + }); + console.log(venues) + + + return Test; +} diff --git a/frontend/src/Pages/WeeklyOpeningPage/index.ts b/frontend/src/Pages/WeeklyOpeningPage/index.ts new file mode 100644 index 000000000..35389770b --- /dev/null +++ b/frontend/src/Pages/WeeklyOpeningPage/index.ts @@ -0,0 +1 @@ +export { WeeklyOpeningPage } from './WeeklyOpeningPage' \ No newline at end of file diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 0336b35a1..85acf2ef0 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -88,6 +88,7 @@ import { ROUTES } from '~/routes'; import { t } from 'i18next'; import { App } from '~/App'; import { DynamicOrgOutlet } from '~/Components/DynamicOrgOutlet/DynamicOrgOutlet'; +import { WeeklyOpeningPage } from '~/Pages/WeeklyOpeningPage/WeeklyOpeningPage'; import { RecruitmentRecruiterDashboardPage } from '~/PagesAdmin/RecruitmentRecruiterDashboardPage/RecruitmentRecruiterDashboardPage'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; @@ -121,6 +122,7 @@ export const router = createBrowserRouter( }/> } /> } /> + } /> } />}> } /> } /> diff --git a/frontend/src/routes/frontend.ts b/frontend/src/routes/frontend.ts index a6256e880..ddaae5ec6 100644 --- a/frontend/src/routes/frontend.ts +++ b/frontend/src/routes/frontend.ts @@ -19,6 +19,7 @@ export const ROUTES_FRONTEND = { membership: '/membership', luka: '/luka', contributors: '/contributors', + weekly_opening_hours: '/opening-hours', // Recruitment: recruitment: '/recruitment/', recruitment_application: '/recruitment/:recruitmentId/position/:positionId/', From 22eccc8586777827844f52c2fc83c6d100c21249 Mon Sep 17 00:00:00 2001 From: Bragon Date: Sun, 12 Apr 2026 21:15:23 +0200 Subject: [PATCH 12/12] Revert "feat: add new page for weekly opening times" This reverts commit 599e631392d98250bbb9ec073ca66949c30ce66b. --- .../src/Components/NavbarSamfThree/Navbar.tsx | 4 ++-- .../WeeklyOpeningPage.module.scss | 3 --- .../WeeklyOpeningPage/WeeklyOpeningPage.tsx | 23 ------------------- frontend/src/Pages/WeeklyOpeningPage/index.ts | 1 - frontend/src/router/router.tsx | 2 -- frontend/src/routes/frontend.ts | 1 - 6 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss delete mode 100644 frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx delete mode 100644 frontend/src/Pages/WeeklyOpeningPage/index.ts diff --git a/frontend/src/Components/NavbarSamfThree/Navbar.tsx b/frontend/src/Components/NavbarSamfThree/Navbar.tsx index 69cfea128..31abe1b22 100644 --- a/frontend/src/Components/NavbarSamfThree/Navbar.tsx +++ b/frontend/src/Components/NavbarSamfThree/Navbar.tsx @@ -17,7 +17,7 @@ import type { RecruitmentDto } from '~/dto'; import { useDesktop } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { ROUTES } from '~/routes'; -import { ROUTES_SAMF_THREE, SAMF3_MEMBER_URL } from '~/routes/samf-three'; +import { SAMF3_MEMBER_URL } from '~/routes/samf-three'; import styles from './Navbar.module.scss'; import { HamburgerMenu, LanguageButton } from './components'; import { NavbarItem } from './components/NavbarItem'; @@ -85,7 +85,7 @@ export function Navbar() { {t(KEY.common_membership)} setExpandedDropdown('')} > diff --git a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss deleted file mode 100644 index 220603ed3..000000000 --- a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use 'src/mixins' as *; - -@use 'src/constants' as *; \ No newline at end of file diff --git a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx b/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx deleted file mode 100644 index b1f736018..000000000 --- a/frontend/src/Pages/WeeklyOpeningPage/WeeklyOpeningPage.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { Page } from '~/Components'; -import { useTitle } from '~/hooks'; -import { useState } from 'react'; -import { getVenues } from '~/api'; -import { useQuery } from '@tanstack/react-query'; -import { venueKeys } from '~/queryKeys'; -import { KEY } from '~/i18n/constants'; - -export function WeeklyOpeningPage() { - const { t } = useTranslation(); - useTitle(t(KEY.common_opening_hours)) - - const { data: venues = [], isLoading } = useQuery({ - queryKey: venueKeys.all, - queryFn: getVenues, - select: (data) => [...data].sort((venueA, venueB) => venueA.name.localeCompare(venueB.name)), - }); - console.log(venues) - - - return Test; -} diff --git a/frontend/src/Pages/WeeklyOpeningPage/index.ts b/frontend/src/Pages/WeeklyOpeningPage/index.ts deleted file mode 100644 index 35389770b..000000000 --- a/frontend/src/Pages/WeeklyOpeningPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WeeklyOpeningPage } from './WeeklyOpeningPage' \ No newline at end of file diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 85acf2ef0..0336b35a1 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -88,7 +88,6 @@ import { ROUTES } from '~/routes'; import { t } from 'i18next'; import { App } from '~/App'; import { DynamicOrgOutlet } from '~/Components/DynamicOrgOutlet/DynamicOrgOutlet'; -import { WeeklyOpeningPage } from '~/Pages/WeeklyOpeningPage/WeeklyOpeningPage'; import { RecruitmentRecruiterDashboardPage } from '~/PagesAdmin/RecruitmentRecruiterDashboardPage/RecruitmentRecruiterDashboardPage'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; @@ -122,7 +121,6 @@ export const router = createBrowserRouter( }/> } /> } /> - } /> } />}> } /> } /> diff --git a/frontend/src/routes/frontend.ts b/frontend/src/routes/frontend.ts index ddaae5ec6..a6256e880 100644 --- a/frontend/src/routes/frontend.ts +++ b/frontend/src/routes/frontend.ts @@ -19,7 +19,6 @@ export const ROUTES_FRONTEND = { membership: '/membership', luka: '/luka', contributors: '/contributors', - weekly_opening_hours: '/opening-hours', // Recruitment: recruitment: '/recruitment/', recruitment_application: '/recruitment/:recruitmentId/position/:positionId/',