Skip to content
Open
2 changes: 1 addition & 1 deletion frontend/src/Pages/ComponentPage/ComponentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,22 @@ 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,
getEventStatusDescriptionTranslationKey,
getEventStatusTranslationKey,
lowerCapitalize,
} from '~/utils';
import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout';
import styles from './EventCreatorAdminPage.module.scss';
import { type FormType, useEventCreatorForm } from './hooks/useEventCreatorForm';
Expand All @@ -30,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();
Expand Down Expand Up @@ -57,6 +72,16 @@ export function EventCreatorAdminPage() {
label: t(getAgeRestrictionKey(age)),
}));

const availableEventStatuses: EventStatus[] = id
? Object.values(EventStatusChoice)
: [EventStatusChoice.PUBLIC, EventStatusChoice.PRIVATE];

const eventStatusOptions: EventStatusOption[] = availableEventStatuses.map((status) => ({
value: status,
label: t(getEventStatusTranslationKey(status)),
description: t(getEventStatusDescriptionTranslationKey(status)),
}));

const { form, watchedValues, buildPayload } = useEventCreatorForm({
event,
defaultCategory: eventCategoryOptions[0]?.value ?? EventCategory.ART,
Expand All @@ -68,7 +93,7 @@ export function EventCreatorAdminPage() {
info: <InfoStep form={form} eventCategoryOptions={eventCategoryOptions} locationOptions={locationOptions} />,
payment: <PaymentStep form={form} ageLimitOptions={ageLimitOptions} />,
graphics: <GraphicsStep form={form} />,
summary: <SummaryStep form={form} />,
summary: <SummaryStep form={form} eventStatusOptions={eventStatusOptions} />,
};

// Fetch event data using the event ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
EVENT_LOCATION,
EVENT_REGISTRATION_URL,
EVENT_START_DT,
EVENT_STATUS,
EVENT_TICKET_TYPE,
EVENT_TITLE,
EVENT_VISIBILITY_FROM_DT,
Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -46,6 +46,7 @@ export function useEventCreatorForm(params: {
custom_tickets: [],
billig_id: undefined,
image: undefined,
status: EventStatusChoice.PUBLIC,
visibility_from_dt: '',
visibility_to_dt: '',
},
Expand Down Expand Up @@ -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 || 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) : '',
};
Expand Down
118 changes: 102 additions & 16 deletions frontend/src/PagesAdmin/EventCreatorAdminPage/steps/SummaryStep.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,113 @@
import { Icon } from '@iconify/react';
import { useState } from 'react';
import type { UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from '~/Components';
import {
Button,
Dropdown,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
Modal,
Text,
} from '~/Components';
import { KEY } from '~/i18n/constants';
import styles from '../EventCreatorAdminPage.module.scss';
import type { FormType } from '../hooks/useEventCreatorForm';
import type { EventStatusOption } from '../types';

export function SummaryStep({ form }: { form: UseFormReturn<FormType> }) {
type Props = {
form: UseFormReturn<FormType>;
eventStatusOptions: EventStatusOption[];
};

export function SummaryStep({ form, eventStatusOptions }: Props) {
const { t } = useTranslation();
const [isStatusInfoOpen, setIsStatusInfoOpen] = useState(false);

return (
<FormField
control={form.control}
name="visibility_from_dt"
key="visibility_from_dt"
render={({ field }) => (
<FormItem className={styles.form_item}>
<FormLabel>{t(KEY.saksdokumentpage_publication_date) ?? ''}</FormLabel>
<FormControl>
<Input type="datetime-local" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<>
<div className={styles.input_row}>
<FormField
control={form.control}
name="visibility_from_dt"
key="visibility_from_dt"
render={({ field }) => (
<FormItem className={styles.form_item}>
<FormLabel>{t(KEY.saksdokumentpage_publication_date) ?? ''}</FormLabel>
<FormControl>
<Input type="datetime-local" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="status"
key="status"
render={({ field }) => (
<FormItem className={styles.form_item}>
<div className={styles.status_label_row}>
<FormLabel>{t(KEY.event_status)}</FormLabel>
<button
type="button"
className={styles.status_info_button}
onClick={() => setIsStatusInfoOpen(true)}
title={t(KEY.common_more_info)}
aria-label={t(KEY.event_status_help_button_aria_label)}
>
<Icon icon="material-symbols:info-outline-rounded" width={18} height={18} />
</button>
</div>
<FormControl>
<Dropdown options={eventStatusOptions} nullOption={{ label: t(KEY.common_choose) }} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<Modal
isOpen={isStatusInfoOpen}
onRequestClose={() => setIsStatusInfoOpen(false)}
className={styles.status_help_modal}
>
<div className={styles.status_help_modal_header}>
<Text as="strong" size="l">
{t(KEY.event_status_help_title)}
</Text>
<button
type="button"
className={styles.status_help_close_button}
onClick={() => setIsStatusInfoOpen(false)}
aria-label={t(KEY.common_close)}
>
<Icon icon="material-symbols:close-rounded" width={20} height={20} />
</button>
</div>

<Text className={styles.status_help_intro}>{t(KEY.event_status_help_intro)}</Text>

<ul className={styles.status_help_list}>
{eventStatusOptions.map((option) => (
<li key={option.value}>
<strong>{option.label}:</strong> {option.description}
</li>
))}
</ul>

<div className={styles.status_help_actions}>
<Button theme="blue" rounded={true} onClick={() => setIsStatusInfoOpen(false)}>
{t(KEY.common_close)}
</Button>
</div>
</Modal>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
];
6 changes: 6 additions & 0 deletions frontend/src/PagesAdmin/EventCreatorAdminPage/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { DropdownOption } from '~/Components/Dropdown/Dropdown';
import type { EventStatus } from '~/types';

export type EventStatusOption = DropdownOption<EventStatus> & {
description: string;
};
2 changes: 2 additions & 0 deletions frontend/src/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ export type EventDto = {
};

export type EventWriteDto = {
status?: EventStatus;
Comment thread
BragonSB marked this conversation as resolved.

title_nb: string;
title_en: string;
description_long_nb: string;
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/i18n/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -332,6 +333,22 @@ 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',
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',

Expand Down Expand Up @@ -689,6 +706,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.
Expand Down
Loading
Loading