Skip to content

Commit

Permalink
Merge pull request #9104 from neinteractiveliterature/moderator-queue…
Browse files Browse the repository at this point in the history
…-view

Allow signup moderators to view users' ranked choices
  • Loading branch information
nbudin authored Jun 23, 2024
2 parents 74632a3 + 3353fb6 commit 413e394
Show file tree
Hide file tree
Showing 18 changed files with 608 additions and 331 deletions.
2 changes: 1 addition & 1 deletion app/graphql/graphql_operations_generated.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion app/graphql/types/user_con_profile_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ def self.personal_info_field(field_name, ...)
field :show_nickname_in_bio, Boolean, null: true do
description "Should this profile's bio use the nickname as part of their name?"
end
field :signup_requests, [Types::SignupRequestType], null: false do
field :signup_ranked_choices, [Types::SignupRankedChoiceType], null: false do
description "This user's ranked choice list for signups."
end
field :signup_requests, [Types::SignupRequestType], null: false do # rubocop:disable GraphQL/ExtractType
description "All the signup requests made by this profile."
end
field :signups, [Types::SignupType], null: false do
Expand Down Expand Up @@ -119,6 +122,7 @@ def site_admin
:orders,
:ranked_choice_user_constraints,
:signups,
:signup_ranked_choices,
:signup_requests,
:ticket,
:user
Expand Down
199 changes: 100 additions & 99 deletions app/javascript/EventsApp/MySignupQueue/NextRoundInfoBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,116 +11,117 @@ import humanize from '../../humanize';
import classNames from 'classnames';

const NextRoundInfoBox = LoadQueryWrapper(useMySignupQueueQuery, ({ data }) => {
{
const { t } = useTranslation();
const { ticketName } = useContext(AppRootContext);
const { t } = useTranslation();
const { ticketName, timezoneName } = useContext(AppRootContext);

const ticketStatus = useMemo(() => {
if (data.convention.my_profile?.ticket) {
if (data.convention.my_profile.ticket.ticket_type.allows_event_signups) {
return 'ok' as const;
} else {
return 'signupsNotAllowed';
}
const ticketStatus = useMemo(() => {
if (data.convention.my_profile?.ticket) {
if (data.convention.my_profile.ticket.ticket_type.allows_event_signups) {
return 'ok' as const;
} else {
return 'noTicket';
return 'signupsNotAllowed';
}
}, [data.convention.my_profile?.ticket]);
} else {
return 'noTicket';
}
}, [data.convention.my_profile?.ticket]);

const nextRound = useMemo(() => {
const parsedRounds = parseSignupRounds(data.convention.signup_rounds);
const now = DateTime.local();
const currentIndex = parsedRounds.findIndex((round) => round.timespan.includesTime(now));
const nextRound = useMemo(() => {
const parsedRounds = parseSignupRounds(data.convention.signup_rounds);
const now = DateTime.local();
const currentIndex = parsedRounds.findIndex((round) => round.timespan.includesTime(now));

if (currentIndex < parsedRounds.length - 1) {
return parsedRounds[currentIndex + 1];
}
}, [data.convention.signup_rounds]);
if (currentIndex < parsedRounds.length - 1) {
return parsedRounds[currentIndex + 1];
}
}, [data.convention.signup_rounds]);

const nextRoundActionDescription = useMemo(() => {
if (!nextRound) {
return undefined;
}
const nextRoundActionDescription = useMemo(() => {
if (!nextRound) {
return undefined;
}

if (nextRound.maximum_event_signups === 'unlimited') {
return t(
'signups.mySignupQueue.nextRoundAction.unlimited',
'At that time, players will be signed up for events automatically based on their queue selections until their schedule is full.',
);
}
if (nextRound.maximum_event_signups === 'unlimited') {
return t(
'signups.mySignupQueue.nextRoundAction.unlimited',
'At that time, players will be signed up for events automatically based on their queue selections until their schedule is full.',
);
}

if (typeof nextRound.maximum_event_signups === 'number') {
return t(
'signups.mySignupQueue.nextRoundAction.limited',
'At that time, players will be automatically signed up for up to {{ count }} event(s).',
{ count: nextRound.maximum_event_signups },
);
}
}, [nextRound, t]);
if (typeof nextRound.maximum_event_signups === 'number') {
return t(
'signups.mySignupQueue.nextRoundAction.limited',
'At that time, players will be automatically signed up for up to {{ count }} event(s).',
{ count: nextRound.maximum_event_signups },
);
}
}, [nextRound, t]);

return (
<>
{nextRound && (
<div
className={classNames('alert mb-4', {
'alert-info': ticketStatus === 'ok',
'alert-warning': ticketStatus === 'noTicket' || ticketStatus === 'signupsNotAllowed',
})}
>
<p>
<Trans i18nKey="signups.mySignupQueue.nextRoundInfo">
The next signup round starts at{' '}
<strong>
{{
nextRoundStart: nextRound.timespan.start
? formatLCM(nextRound.timespan.start, getDateTimeFormat('shortWeekdayDateTimeWithZone', t))
: '',
}}
</strong>
. {{ nextRoundActionDescription }}
</Trans>
</p>
return (
<>
{nextRound && (
<div
className={classNames('alert mb-4', {
'alert-info': ticketStatus === 'ok',
'alert-warning': ticketStatus === 'noTicket' || ticketStatus === 'signupsNotAllowed',
})}
>
<p>
<Trans i18nKey="signups.mySignupQueue.nextRoundInfo">
The next signup round starts at{' '}
<strong>
{{
nextRoundStart: nextRound.timespan.start
? formatLCM(
nextRound.timespan.start.setZone(timezoneName),
getDateTimeFormat('shortWeekdayDateTimeWithZone', t),
)
: '',
}}
</strong>
. {{ nextRoundActionDescription }}
</Trans>
</p>

<p className="mb-0">
{ticketStatus === 'ok' &&
t(
'signups.mySignupQueue.ticketStatus.ok',
'You have a {{ ticketName }} and will be signed up for events at that time.',
{ ticketName },
)}
{ticketStatus === 'signupsNotAllowed' &&
t(
'signups.mySignupQueue.ticketStatus.signupsNotAllowed',
'You have a {{ ticketName }} that does not allow event signups. You will not be able to sign up for games until you have arranged for a full {{ ticketName }}.',
{ ticketName },
)}
{ticketStatus === 'noTicket' &&
t(
'signups.mySignupQueue.ticketStatus.noTicket',
'You do not have a {{ ticketName }}. You will not be able to sign up for games until you have purchased a {{ ticketName }}.',
{ ticketName },
)}
</p>
<p className="mb-0">
{ticketStatus === 'ok' &&
t(
'signups.mySignupQueue.ticketStatus.ok',
'You have a {{ ticketName }} and will be signed up for events at that time.',
{ ticketName },
)}
{ticketStatus === 'signupsNotAllowed' &&
t(
'signups.mySignupQueue.ticketStatus.signupsNotAllowed',
'You have a {{ ticketName }} that does not allow event signups. You will not be able to sign up for games until you have arranged for a full {{ ticketName }}.',
{ ticketName },
)}
{ticketStatus === 'noTicket' &&
t(
'signups.mySignupQueue.ticketStatus.noTicket',
'You do not have a {{ ticketName }}. You will not be able to sign up for games until you have purchased a {{ ticketName }}.',
{ ticketName },
)}
</p>

{ticketStatus === 'noTicket' && (
<div className="mt-3">
<Link to="/ticket/new" className="btn btn-sm btn-warning">
<span className="d-inline d-md-none d-lg-inline">
{t('navigation.ticketPurchase.ctaLong', 'Buy a {{ ticketName }}!', { ticketName })}
</span>
<span className="d-none d-md-inline d-lg-none">
{t('navigation.ticketPurchase.ctaShort', '{{ ticketName }}!', {
ticketName: humanize(ticketName ?? 'ticket'),
})}
</span>
</Link>
</div>
)}
</div>
)}
</>
);
}
{ticketStatus === 'noTicket' && (
<div className="mt-3">
<Link to="/ticket/new" className="btn btn-sm btn-warning">
<span className="d-inline d-md-none d-lg-inline">
{t('navigation.ticketPurchase.ctaLong', 'Buy a {{ ticketName }}!', { ticketName })}
</span>
<span className="d-none d-md-inline d-lg-none">
{t('navigation.ticketPurchase.ctaShort', '{{ ticketName }}!', {
ticketName: humanize(ticketName ?? 'ticket'),
})}
</span>
</Link>
</div>
)}
</div>
)}
</>
);
});

export default NextRoundInfoBox;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import BucketAvailabilityDisplay from '../EventPage/BucketAvailabilityDisplay';

type ConstraintAvailabilityDisplayProps = {
constraint: Pick<RankedChoiceUserConstraint, 'start' | 'finish' | 'maximum_signups'>;
mySignups: MySignupQueueQueryData['convention']['my_signups'];
mySignups: NonNullable<MySignupQueueQueryData['convention']['my_profile']>['signups'];
};

function ConstraintAvailabilityDisplay({ constraint, mySignups }: ConstraintAvailabilityDisplayProps) {
Expand Down Expand Up @@ -81,7 +81,7 @@ function MaximumSignupsLimitSelect({ value, onValueChange, ...props }: MaximumSi

type SignupConstraintRowProps = {
constraint?: Pick<RankedChoiceUserConstraint, 'id' | 'start' | 'finish' | 'maximum_signups'>;
mySignups: MySignupQueueQueryData['convention']['my_signups'];
mySignups: NonNullable<MySignupQueueQueryData['convention']['my_profile']>['signups'];
label: React.ReactNode;
help?: React.ReactNode;
onChange: (newValue: number | null | undefined) => void;
Expand Down Expand Up @@ -252,7 +252,7 @@ const RankedChoiceUserSettings = LoadQueryWrapper(useMySignupQueueQuery, ({ data
)}
onChange={(newValue) => constraintChanged(totalSignupsConstraint, undefined, undefined, newValue)}
loading={loading}
mySignups={data.convention.my_signups}
mySignups={data.convention.my_profile?.signups ?? []}
/>

{conventionDays.map((conventionDay) => (
Expand Down Expand Up @@ -280,7 +280,7 @@ const RankedChoiceUserSettings = LoadQueryWrapper(useMySignupQueueQuery, ({ data
)
}
loading={loading}
mySignups={data.convention.my_signups}
mySignups={data.convention.my_profile?.signups ?? []}
/>
))}

Expand All @@ -305,7 +305,7 @@ const RankedChoiceUserSettings = LoadQueryWrapper(useMySignupQueueQuery, ({ data
)
}
loading={loading}
mySignups={data.convention.my_signups}
mySignups={data.convention.my_profile?.signups ?? []}
/>
))}
</tbody>
Expand Down
Loading

0 comments on commit 413e394

Please sign in to comment.