Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
40 changes: 35 additions & 5 deletions src/apis/attsrv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@
readonly status: RegistrationStatus
}

// DTO for changing attendee status (e.g., for self-cancellation)
export interface AttendeeStatusChangeDto {
readonly status: RegistrationStatus
readonly comment: string
}

/* eslint-disable @typescript-eslint/no-magic-numbers */
enum Weekdays {
Monday = 1,
Expand Down Expand Up @@ -151,7 +157,7 @@
}

// eslint-disable-next-line complexity
const attendeeDtoFromRegistrationInfo = (registrationInfo: RegistrationInfo): AttendeeDto => {

Check warning on line 160 in src/apis/attsrv.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Arrow function has too many statements (22). Maximum allowed is 10
const packagesMap = new Map<string, number>()

// first copy all original packages
Expand All @@ -171,12 +177,12 @@
packagesMap.set('sponsor2', registrationInfo.ticketLevel.level === 'super-sponsor' ? 1 : 0)
packagesMap.set('stage',
registrationInfo.ticketLevel.level // it cannot be null here
&& !(config.ticketLevels[registrationInfo.ticketLevel.level].includes?.includes('stage-pass') ?? false)
&& registrationInfo.ticketLevel.addons['stage-pass'].selected ? 1 : 0)
&& !(config.ticketLevels[registrationInfo.ticketLevel.level].includes?.includes('stage-pass') ?? false)
&& registrationInfo.ticketLevel.addons['stage-pass'].selected ? 1 : 0)
packagesMap.set('tshirt',
registrationInfo.ticketLevel.level // it cannot be null here
&& !(config.ticketLevels[registrationInfo.ticketLevel.level].includes?.includes('tshirt') ?? false)
&& registrationInfo.ticketLevel.addons.tshirt.selected ? 1 : 0)
&& !(config.ticketLevels[registrationInfo.ticketLevel.level].includes?.includes('tshirt') ?? false)
&& registrationInfo.ticketLevel.addons.tshirt.selected ? 1 : 0)
packagesMap.set('early', registrationInfo.ticketLevel.addons.early.selected ? 1 : 0)
packagesMap.set('late', registrationInfo.ticketLevel.addons.late.selected ? 1 : 0)
// linter is wrong, undefined can happen if the field has been removed due to a level switch, because then benefactor isn't available
Expand All @@ -188,7 +194,7 @@
packagesMap.set('fursuitadd', registrationInfo.ticketLevel.addons.fursuitadd?.selected ? countAsNumber(registrationInfo.ticketLevel.addons.fursuitadd.options.count) : 0)

const packagesList = Array.from(packagesMap.entries())
.filter(([,c]) => c > 0)
.filter(([, c]) => c > 0)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([n, c]) => ({
name: n,
Expand Down Expand Up @@ -234,7 +240,7 @@
}

// eslint-disable-next-line complexity
const registrationInfoFromAttendeeDto = (attendeeDto: AttendeeDto): RegistrationInfo => {

Check warning on line 243 in src/apis/attsrv.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Arrow function has too many statements (12). Maximum allowed is 10
const packagesMap = new Map(attendeeDto.packages_list.map(entry => [entry.name, entry.count]))
const flags = new Set(attendeeDto.flags.split(','))
const options = new Set(attendeeDto.options.split(','))
Expand Down Expand Up @@ -453,6 +459,30 @@
method: 'GET',
})

/*
* POST /attendees/{id}/status creates a status change for an attendee.
*
* id should come from the list returned by findMyRegistrations. Then a 400, 403, 404 should not occur.
*
* Returns no body and status 204, or ErrorDto and an error status.
*
* 400: Invalid ID or body supplied.
* 401: The user's token has expired, and you need to redirect them to the auth start to refresh it.
* 403: You do not have permission to see this attendee, or you do not have permission to perform this status change at all.
* Note that situational limitations (e.g. cannot check in an unpaid attendee) result in 409 instead.
* If you get 403, you do not have permissions for this status transition, ever.
* 404: No such attendee.
* 409: The current situation does not allow this status change, even though you generally have permission to do it.
* Maybe you are trying to check in an attendee who hasn't fully paid, etc. See message and details fields of the error.
* 500: It is important to communicate the ErrorDto's requestid field to the user, so they can give it to us, so we can look in the logs.
* 502: The update leads to an update in the payment service which failed, or the mail service failed to send an email as part of the update.
*/
export const changeRegistrationStatus = (id: number, dto: AttendeeStatusChangeDto) => apiCall({
path: `/attendees/${id}/status`,
method: 'POST',
body: dto,
})

/*
* PUT /attendees/{id} overwrites the data for an attendee. Used during edit mode.
*
Expand Down
41 changes: 41 additions & 0 deletions src/components/funnels/funnels/register/steps/self-cancel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Button } from '@eurofurence/reg-component-library'
import { Localized } from '@fluent/react'
import { navigate } from 'gatsby'
import { StaticImage } from 'gatsby-plugin-image'
import ReactMarkdown from 'react-markdown'
import { changeRegistrationStatus } from '~/apis/attsrv'
import SplashFunnelLayout from '~/components/funnels/layout/splash'
import { useAppSelector } from '~/hooks/redux'
import { getRegistrationId } from '~/state/selectors/register'
import type { ReadonlyRouteComponentProps } from '~/util/readonly-types'

const SelfCancel = (_: ReadonlyRouteComponentProps) => {
const returnLink = `/register/summary`

const registrationId = useAppSelector(getRegistrationId())!

const doCancel = async (id: number): Promise<void> => {
changeRegistrationStatus(id, { status: 'cancelled', comment: 'self cancellation' })
await navigate(returnLink)
}

const abortCancel = async (): Promise<void> => {
await navigate(returnLink)
}

return <SplashFunnelLayout image={<StaticImage src="../../../../../images/con-cats/self-cancel.png" alt="Self Cancel" />}>
<Localized id="register-self-cancel-title"><h1>Self Cancellation!</h1></Localized>
<Localized id="register-self-cancel-sure"><h2>Are you sure?</h2></Localized>
<Localized id="register-self-cancel-content">
<ReactMarkdown>
If you click on yes, you will cancel your unpaid registration! Be sure you mean it!
You will receive an email to confirm the cancellation. If you have cancelled by accident, you can
reply to the email to ask for your registration to be reinstated, subject to availability.
</ReactMarkdown>
</Localized>
<Localized id="register-self-cancel-yes"><Button onClick={() => doCancel(registrationId)}>Yes</Button></Localized>
<Localized id="register-self-cancel-no"><Button onClick={() => abortCancel()}>No</Button></Localized>
</SplashFunnelLayout>
}

export default SelfCancel
10 changes: 8 additions & 2 deletions src/components/funnels/funnels/register/steps/summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import type { RegistrationStatus } from '~/state/models/register'
import { getContactInfo, getOptionalInfo, getPersonalInfo, getRegistrationId, getStatus, isEditMode } from '~/state/selectors/register'
import langmap from 'langmap'
import { Link } from 'gatsby'
import { Link, navigate } from 'gatsby'
import { css } from '@emotion/react'
import { useCurrentLocale } from '~/localization'
import { useFunnelForm } from '~/hooks/funnels/form'
import { Checkbox, ErrorMessage, Form } from '@eurofurence/reg-component-library'
import { Checkbox, ErrorMessage, Form, Button } from '@eurofurence/reg-component-library'
import config from '~/config'

interface PropertyDefinition {
Expand Down Expand Up @@ -127,7 +127,7 @@
}

// eslint-disable-next-line max-statements
const Summary = (_: ReadonlyRouteComponentProps) => {

Check warning on line 130 in src/components/funnels/funnels/register/steps/summary.tsx

View workflow job for this annotation

GitHub Actions / build-and-test

Arrow function has a complexity of 12. Maximum allowed is 10
const registrationId = useAppSelector(getRegistrationId())!
const personalInfo = useAppSelector(getPersonalInfo())!
const contactInfo = useAppSelector(getContactInfo())!
Expand Down Expand Up @@ -155,6 +155,12 @@
<RegistrationId>Badge number: {registrationId}</RegistrationId>
</Localized> : undefined }

{ registrationId && (status === 'new' || status === 'waiting' || status === 'approved')
? <Localized id="register-summary-self-cancel">
<Button onClick={() => navigate(`/register/self-cancel`)}>Self Cancel!</Button>
</Localized>
: undefined }

<Section id="personal" editLink="/register/personal-info" properties={[
{ id: 'nickname', value: personalInfo.nickname },
{ id: 'full-name', value: `${personalInfo.firstName} ${personalInfo.lastName}` },
Expand Down Expand Up @@ -188,7 +194,7 @@
rules: <a target="_blank" rel="noreferrer noopener" href={config.websiteLinks.rules}/>,
conditions: <a target="_blank" rel="noreferrer noopener" href={config.websiteLinks.terms}/>,
}}>
<span>I accept the <a target="_blank" rel="noreferrer noopener" href={config.websiteLinks.rules}>rules</a> and <a target="_blank" rel="noreferrer noopener" href={config.websiteLinks.terms}>conditions</a>.</span>

Check warning on line 197 in src/components/funnels/funnels/register/steps/summary.tsx

View workflow job for this annotation

GitHub Actions / build-and-test

This line has a length of 231. Maximum allowed is 200
</Localized>
</Checkbox>
{errors.rulesAndConditionsAccepted?.message === undefined ? undefined : <ErrorMessage>{errors.rulesAndConditionsAccepted.message}</ErrorMessage>}
Expand Down
Binary file added src/images/con-cats/self-cancel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/localizations/de-DE.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,17 @@ register-not-open-yet-content =
Schau wieder rein wenn die Registration öffnet!


# Register self cancellation page
register-self-cancel-title = Anmeldung stornieren!
register-self-cancel-sure = Bis du sicher?
register-self-cancel-content =
Wenn du jetzt auf "Ja" klickst, dann wird deine unbezahlte Anmeldung storniert!
Sei dir sicher, dass du das auch möchtest. Du erhältst eine Email zur Bestätigung.
Falls du irrtümlich storniert hast, kannst du auf die Mail antworten und uns
darum bitten, deine Anmeldung wieder zu aktivieren. Es gibt aber keine Garantie, dass
noch Plätze frei sind.
register-self-cancel-yes = Ja! Ich möchte stornieren!
register-self-cancel-no = Nein! Zurück!



Expand Down
10 changes: 10 additions & 0 deletions src/localizations/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,16 @@ register-not-open-yet-content =



# Register self cancellation page
register-self-cancel-title = Self Cancellation!
register-self-cancel-sure = Are you sure?
register-self-cancel-content =
If you click on yes, you will cancel your unpaid registration! Be sure you mean it!
You will receive an email to confirm the cancellation. If you have cancelled by accident, you can
reply to the email to ask for your registration to be reinstated, subject to availability.
register-self-cancel-yes = Yes! Cancel my registration!
register-self-cancel-no = No! Go Back!



# Common hotel booking messages
Expand Down
2 changes: 2 additions & 0 deletions src/navigation/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Contact from '~/components/funnels/funnels/register/steps/contact'
import Optional from '~/components/funnels/funnels/register/steps/optional'
import Summary from '~/components/funnels/funnels/register/steps/summary'
import ThankYou from '~/components/funnels/funnels/register/steps/thank-you'
import SelfCancel from '~/components/funnels/funnels/register/steps/self-cancel'
import Room from '~/components/funnels/funnels/hotel-booking/steps/room'
import Guests from '~/components/funnels/funnels/hotel-booking/steps/guests'
import AdditionalInfo from '~/components/funnels/funnels/hotel-booking/steps/additional-info'
Expand All @@ -29,6 +30,7 @@ export const RegisterRouter = () => {
<Optional path={`/${ROUTES.REGISTER_OPTIONAL}`} />
<Summary default={isEdit} path={`/${ROUTES.REGISTER_SUMMARY}`} />
<ThankYou path={`/${ROUTES.REGISTER_THANK_YOU}`} />
<SelfCancel path={`/${ROUTES.REGISTER_SELF_CANCEL}`} />
</Router>
}

Expand Down
1 change: 1 addition & 0 deletions src/navigation/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const REGISTER_CONTACT = 'contact-info'
export const REGISTER_OPTIONAL = 'optional-info'
export const REGISTER_SUMMARY = 'summary'
export const REGISTER_THANK_YOU = 'thank-you'
export const REGISTER_SELF_CANCEL = 'self-cancel'

// Hotel booking flow
export const HOTEL_BOOKING = 'hotel-booking'
Expand Down
Loading