Skip to content

Commit

Permalink
Extract Form logic of "Create Competition" Form to its own folder/com…
Browse files Browse the repository at this point in the history
…ponent (#9185)

* Create separate form provider

* In-progress hooking the form provider into existing form

* Generalize EditForm reducer

* Hook up data bindings

* Wire up form inputs

* Wire up input dispatch correctly

* Replace old Store dispatchers

* Split FormContext and actual Form into separate code files

* Move FormInputs to their own separate file

* Rename package to FormBuilder

* Reorganize Header and Footer in Comp Form

* Fix section not propagating in standard update action

* Use more React-friendly initializers
  • Loading branch information
gregorbg authored May 7, 2024
1 parent c5237ca commit c153328
Show file tree
Hide file tree
Showing 35 changed files with 639 additions and 478 deletions.
27 changes: 19 additions & 8 deletions app/webpacker/components/CompetitionForm/AnnouncementActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Segment,
} from 'semantic-ui-react';
import React, { useMemo } from 'react';
import { DateTime } from 'luxon';
import I18n from '../../lib/i18n';
import { useStore } from '../../lib/providers/StoreProvider';
import useLoadedData from '../../lib/hooks/useLoadedData';
Expand All @@ -18,6 +19,11 @@ import {
import Loading from '../Requests/Loading';
import ConfirmProvider, { useConfirm } from '../../lib/providers/ConfirmProvider';
import useSaveAction from '../../lib/hooks/useSaveAction';
import {
useFormContext,
useFormErrorHandler,
useFormInitialObject,
} from '../wca/FormBuilder/provider/FormObjectProvider';

function AnnounceAction({
competitionId,
Expand All @@ -44,10 +50,15 @@ function AnnounceAction({
});
};

const announcedAtLuxon = DateTime.fromISO(announcedAt);

if (isAnnounced) {
return (
<List.Item>
{I18n.t('competitions.announced_by_html', { announcer_name: announcedBy, date_time: announcedAt })}
{I18n.t('competitions.announced_by_html', {
announcer_name: announcedBy,
date_time: announcedAtLuxon.toLocaleString(DateTime.DATETIME_FULL),
})}
</List.Item>
);
}
Expand Down Expand Up @@ -127,14 +138,15 @@ function CloseRegistrationAction({
competitionId,
data,
sync,
onError,
}) {
const {
isRegistrationPast,
isRegistrationFull,
canCloseFullRegistration,
} = data;

const onError = useFormErrorHandler();

const { save } = useSaveAction();
const confirm = useConfirm();

Expand Down Expand Up @@ -174,11 +186,11 @@ function CloseRegistrationAction({
);
}

export default function AnnouncementActions({
onError,
disabled = false,
}) {
const { isAdminView, initialCompetition: { competitionId } } = useStore();
export default function AnnouncementActions() {
const { isAdminView } = useStore();

const { competitionId } = useFormInitialObject();
const { unsavedChanges: disabled } = useFormContext();

const dataUrl = useMemo(() => competitionAnnouncementDataUrl(competitionId), [competitionId]);

Expand All @@ -205,7 +217,6 @@ export default function AnnouncementActions({
competitionId={competitionId}
data={data}
sync={sync}
onError={onError}
/>
</List>
</Dimmer.Dimmable>
Expand Down
54 changes: 26 additions & 28 deletions app/webpacker/components/CompetitionForm/ConfirmationActions.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
import { Button } from 'semantic-ui-react';
import React, { useMemo } from 'react';
import I18n from '../../lib/i18n';
import { useDispatch, useStore } from '../../lib/providers/StoreProvider';
import { useStore } from '../../lib/providers/StoreProvider';
import useLoadedData from '../../lib/hooks/useLoadedData';
import {
competitionConfirmationDataUrl,
competitionUrl,
confirmCompetitionUrl, homepageUrl,
confirmCompetitionUrl,
homepageUrl,
} from '../../lib/requests/routes.js.erb';
import Loading from '../Requests/Loading';
import ConfirmProvider, { useConfirm } from '../../lib/providers/ConfirmProvider';
import useSaveAction from '../../lib/hooks/useSaveAction';
import { changesSaved, updateFormValue } from './store/actions';
import { useFormCommitAction, useFormUpdateAction } from '../wca/FormBuilder/EditForm';
import { useFormErrorHandler, useFormInitialObject } from '../wca/FormBuilder/provider/FormObjectProvider';

export function CreateOrUpdateButton({
createComp,
updateComp,
saveObject,
}) {
const { isPersisted } = useStore();

if (isPersisted) {
return (
<Button primary onClick={updateComp}>
{I18n.t('competitions.competition_form.submit_update_value')}
</Button>
);
}

return (
<Button primary onClick={createComp}>
{I18n.t('competitions.competition_form.submit_create_value')}
<Button primary onClick={saveObject}>
{
isPersisted
? I18n.t('competitions.competition_form.submit_update_value')
: I18n.t('competitions.competition_form.submit_create_value')
}
</Button>
);
}
Expand All @@ -38,14 +35,16 @@ function ConfirmButton({
competitionId,
data,
sync,
onError,
}) {
const { canConfirm } = data;

const onError = useFormErrorHandler();

const { save } = useSaveAction();
const confirm = useConfirm();

const dispatch = useDispatch();
const updateFormObject = useFormUpdateAction();
const commitFormObject = useFormCommitAction();

const confirmCompetition = () => {
confirm({
Expand All @@ -56,8 +55,8 @@ function ConfirmButton({

// mark the competition as announced and commit immediately.
// (we do not want the announce button to trigger the "there are unsaved changes" alert)
dispatch(updateFormValue('isConfirmed', true, ['admin']));
dispatch(changesSaved());
updateFormObject('isConfirmed', true, ['admin']);
commitFormObject();
}, {
body: null,
method: 'PUT',
Expand Down Expand Up @@ -112,19 +111,18 @@ function DeleteButton({
}

export default function ConfirmationActions({
createComp,
updateComp,
onError,
saveObject,
}) {
const {
isAdminView,
isPersisted,
initialCompetition: {
competitionId,
admin: { isConfirmed },
},
} = useStore();

const {
competitionId,
admin: { isConfirmed },
} = useFormInitialObject();

const dataUrl = useMemo(() => competitionConfirmationDataUrl(competitionId), [competitionId]);

const {
Expand All @@ -138,9 +136,9 @@ export default function ConfirmationActions({
return (
<ConfirmProvider>
<Button.Group>
<CreateOrUpdateButton createComp={createComp} updateComp={updateComp} />
<CreateOrUpdateButton saveObject={saveObject} />
{isPersisted && !isAdminView && !isConfirmed && (
<ConfirmButton competitionId={competitionId} data={data} sync={sync} onError={onError} />
<ConfirmButton competitionId={competitionId} data={data} sync={sync} />
)}
{isPersisted && !isConfirmed && (
<DeleteButton competitionId={competitionId} data={data} />
Expand Down
29 changes: 29 additions & 0 deletions app/webpacker/components/CompetitionForm/Footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { Message } from 'semantic-ui-react';
import { useStore } from '../../lib/providers/StoreProvider';
import { useFormContext } from '../wca/FormBuilder/provider/FormObjectProvider';
import ConfirmationActions, { CreateOrUpdateButton } from './ConfirmationActions';

export default function Footer({
saveObject,
}) {
const { isPersisted } = useStore();
const { unsavedChanges } = useFormContext();

if (isPersisted && !unsavedChanges) {
return (
<ConfirmationActions saveObject={saveObject} />
);
}

return (
<>
{unsavedChanges && (
<Message info>
You have unsaved changes. Please save the competition before taking any other action.
</Message>
)}
<CreateOrUpdateButton saveObject={saveObject} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import SubSection from './SubSection';
import { InputBoolean } from '../Inputs/FormInputs';
import { InputBoolean } from '../../wca/FormBuilder/input/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import SubSection from '../../wca/FormBuilder/SubSection';

export default function Admin() {
const { isAdminView, isPersisted } = useStore();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from 'react';
import { Form } from 'semantic-ui-react';
import {
InputDate,
} from '../Inputs/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import { InputDate } from '../../wca/FormBuilder/input/FormInputs';
import I18n from '../../../lib/i18n';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

function daysUntil(date) {
if (!date) return null;
Expand All @@ -21,7 +19,7 @@ function daysUntil(date) {
}

export default function CompDates() {
const { competition: { startDate, endDate } } = useStore();
const { startDate, endDate } = useFormObject();

return (
<Form.Group widths="equal">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react';
import SubSection from './SubSection';
import { InputBooleanSelect, InputNumber, InputTextArea } from '../Inputs/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import { InputBooleanSelect, InputNumber, InputTextArea } from '../../wca/FormBuilder/input/FormInputs';
import ConditionalSection from './ConditionalSection';
import SubSection from '../../wca/FormBuilder/SubSection';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

export default function CompetitorLimit() {
const {
competition: { competitorLimit },
} = useStore();

const hasLimit = competitorLimit.enabled;
competitorLimit: {
enabled: hasLimit,
},
} = useFormObject();

return (
<SubSection section="competitorLimit">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import React, { useMemo } from 'react';
import { Divider } from 'semantic-ui-react';
import SubSection from './SubSection';
import {
InputBoolean,
InputBooleanSelect,
InputNumber,
InputSelect,
InputTextArea,
} from '../Inputs/FormInputs';
} from '../../wca/FormBuilder/input/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import ConditionalSection from './ConditionalSection';
import SubSection from '../../wca/FormBuilder/SubSection';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

export default function EventRestrictions() {
const {
competition: {
eventRestrictions: {
forbidNewcomers,
earlyPuzzleSubmission,
qualificationResults,
eventLimitation,
},
},
usesV2Registrations,
isCloning,
isPersisted,
storedEvents,
} = useStore();

const {
eventRestrictions: {
forbidNewcomers,
earlyPuzzleSubmission,
qualificationResults,
eventLimitation,
},
} = useFormObject();

const mainEventOptions = useMemo(() => {
const storedEventOptions = storedEvents.map((event) => ({
key: event.id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from 'react';
import { InputString } from '../Inputs/FormInputs';
import { InputString } from '../../wca/FormBuilder/input/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import { competitionMaxShortNameLength } from '../../../lib/wca-data.js.erb';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

export default function NameDetails() {
const { competition: { name }, isPersisted, isAdminView } = useStore();
const { isPersisted, isAdminView } = useStore();

const { name } = useFormObject();

const nameAlreadyShort = !name || name.length <= competitionMaxShortNameLength;
const disableIdAndShortName = !isAdminView && nameAlreadyShort;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import React from 'react';
import { Form } from 'semantic-ui-react';
import { InputDate } from '../Inputs/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
import SubSection from './SubSection';
import { InputDate } from '../../wca/FormBuilder/input/FormInputs';
import RegistrationCollisions from '../Tables/RegistrationCollisions';
import SubSection from '../../wca/FormBuilder/SubSection';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

export default function RegistrationDates() {
const {
competition: {
registration: {
openingDateTime,
closingDateTime,
},
registration: {
openingDateTime,
closingDateTime,
},
} = useStore();
} = useFormObject();

return (
<SubSection section="registration">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React from 'react';
import SubSection from './SubSection';
import {
InputBoolean,
InputBooleanSelect,
InputDate,
InputMarkdown,
InputNumber, InputRadio, InputSelect,
} from '../Inputs/FormInputs';
import { useStore } from '../../../lib/providers/StoreProvider';
InputNumber,
InputRadio,
InputSelect,
} from '../../wca/FormBuilder/input/FormInputs';
import ConditionalSection from './ConditionalSection';
import I18n from '../../../lib/i18n';
import SubSection from '../../wca/FormBuilder/SubSection';
import { useFormObject } from '../../wca/FormBuilder/provider/FormObjectProvider';

const guestsEnabledOptions = [true, false].map((bool) => ({
value: bool,
Expand All @@ -23,7 +25,7 @@ const guestMessageOptions = ['unclear', 'free', 'restricted'].map((status) => ({
}));

export default function RegistrationDetails() {
const { competition: { entryFees, registration } } = useStore();
const { entryFees, registration } = useFormObject();

const guestsGoFree = entryFees?.guestEntryFee === 0;
const guestsRestricted = guestsGoFree && registration?.guestEntryStatus === 'restricted';
Expand Down
Loading

0 comments on commit c153328

Please sign in to comment.