Skip to content
Merged
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
21 changes: 20 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function MyComponent() {
}
```

Translation keys are defined in locale files and should be used instead of hardcoded strings.
Translation keys are defined in `locales/en.json` and should always be used instead of hardcoded strings in React components. Never use literal strings in JSX or passed as props — always use `t('some.key')`. For JSX with mixed content (text + components), use the `<Trans>` component.

## Authentication Context

Expand Down Expand Up @@ -414,6 +414,25 @@ yarn test
**Cause**: Incorrect usage of AuthenticityTokensManager.
**Solution**: Use `AuthenticityTokensContext` with `useContext` hook instead.

## Pull Request Labels

When creating PRs, apply two kinds of labels:

**Category labels** (used by release-drafter to group changes in release notes):

- `enhancement` or `feature` — new features
- `bug`, `bugfix`, or `fix` — bug fixes
- `chore` — maintenance/refactoring with no user-facing change
- `dependencies` — dependency updates

**Version bump labels** (used by release-drafter to determine the next version number):

- `major` — breaking changes (bumps major version, e.g. 1.x.x → 2.0.0)
- `minor` — non-breaking new features (bumps minor version, e.g. 1.2.x → 1.3.0)
- `patch` — bug fixes and small improvements (bumps patch version, e.g. 1.2.3 → 1.2.4)

Every PR should have one category label and one version bump label. If no version bump label is set, release-drafter defaults to `patch`.

## Getting Help

- Check existing patterns in similar files
Expand Down
2 changes: 1 addition & 1 deletion app/graphql/graphql_operations_generated.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions app/graphql/types/convention_input_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ class Types::ConventionInputType < Types::BaseInputObject
about OpenGraph, see https://ogp.me/.
MARKDOWN
end
argument :queue_no_ticket_reminder_advance_seconds, Integer, required: false, camelize: false do
description <<~MARKDOWN
How many seconds before the first automated signup round to send a reminder to attendees who have ranked choices
queued but no ticket. If null, no reminders will be sent.
MARKDOWN
end
argument :root_page_id, ID, required: false, camelize: true do
description "The ID of the Page to serve at the root path (/) of this convention site."
end
Expand Down
6 changes: 6 additions & 0 deletions app/graphql/types/convention_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ class Types::ConventionType < Types::BaseObject # rubocop:disable Metrics/ClassL
description "If true, only returns products that provide the buyer a ticket to this convention."
end
end
field :queue_no_ticket_reminder_advance_seconds, Integer, null: true do
description <<~MARKDOWN
How many seconds before the first automated signup round to send a reminder to attendees who have ranked choices
queued but no ticket. If null, no reminders will be sent.
MARKDOWN
end
field :reports, Types::ConventionReportsType, null: false do
description "A sub-object containing various reports that can be generated for this convention."
authorize_action :view_reports
Expand Down
29 changes: 28 additions & 1 deletion app/javascript/ConventionAdmin/ConventionFormEventsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BooleanInput, MultipleChoiceInput, usePropertySetters } from '@neintera

import type { ConventionFormConvention } from './ConventionForm';
import { ShowSchedule, SignupAutomationMode, SignupMode } from '../graphqlTypes.generated';
import { Trans } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';

export type ConventionFormEventsSectionProps = {
convention: ConventionFormConvention;
Expand All @@ -16,13 +16,24 @@ function ConventionFormEventsSection({
setConvention,
disabled,
}: ConventionFormEventsSectionProps): React.JSX.Element {
const { t } = useTranslation();

const queueNoTicketReminderOptions = [
{ value: '', label: t('admin.convention.queueNoTicketReminder.disabled') },
{ value: '86400', label: t('admin.convention.queueNoTicketReminder.oneDay') },
{ value: '259200', label: t('admin.convention.queueNoTicketReminder.threeDays') },
{ value: '604800', label: t('admin.convention.queueNoTicketReminder.oneWeek') },
{ value: '1209600', label: t('admin.convention.queueNoTicketReminder.twoWeeks') },
];

const [
setSignupMode,
setSignupAutomationMode,
setSignupRequestsOpen,
setAcceptingProposals,
setShowEventList,
setShowSchedule,
setQueueNoTicketReminderAdvanceSeconds,
] = usePropertySetters(
setConvention,
'signup_mode',
Expand All @@ -31,6 +42,7 @@ function ConventionFormEventsSection({
'accepting_proposals',
'show_event_list',
'show_schedule',
'queue_no_ticket_reminder_advance_seconds',
);

return (
Expand Down Expand Up @@ -72,6 +84,21 @@ function ConventionFormEventsSection({
disabled={disabled}
/>

<MultipleChoiceInput
name="queue_no_ticket_reminder_advance_seconds"
caption={t('admin.convention.queueNoTicketReminder.caption')}
choices={queueNoTicketReminderOptions}
value={
convention.queue_no_ticket_reminder_advance_seconds == null
? ''
: String(convention.queue_no_ticket_reminder_advance_seconds)
}
onChange={(newValue: string) =>
setQueueNoTicketReminderAdvanceSeconds(newValue === '' ? null : Number(newValue))
}
disabled={disabled}
/>

<BooleanInput
name="signup_requests_open"
caption="Signup requests open"
Expand Down
1 change: 1 addition & 0 deletions app/javascript/ConventionAdmin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const action: ActionFunction<RouterContextProvider> = async ({ context, r
'ticket_mode',
'clickwrap_agreement',
'language',
'queue_no_ticket_reminder_advance_seconds',
]),
defaultLayoutId: convention.defaultLayout?.id.toString(),
rootPageId: convention.rootPage.id.toString(),
Expand Down
Loading
Loading