Skip to content

Commit ee45878

Browse files
authored
Implement speaker availability in the CFP (#4182)
1 parent e8e1491 commit ee45878

File tree

10 files changed

+517
-306
lines changed

10 files changed

+517
-306
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {
2+
CardPart,
3+
Grid,
4+
Heading,
5+
Input,
6+
InputWrapper,
7+
MultiplePartsCard,
8+
Select,
9+
Text,
10+
} from "@python-italia/pycon-styleguide";
11+
import { FormattedMessage } from "react-intl";
12+
import { useTranslatedMessage } from "~/helpers/use-translated-message";
13+
14+
const SPEAKER_LEVEL_OPTIONS = [
15+
{
16+
value: "",
17+
disabled: true,
18+
messageId: "cfp.selectSpeakerLevel",
19+
},
20+
{
21+
disabled: false,
22+
value: "new",
23+
messageId: "cfp.speakerLevel.new",
24+
},
25+
{
26+
disabled: false,
27+
value: "intermediate",
28+
messageId: "cfp.speakerLevel.intermediate",
29+
},
30+
{
31+
disabled: false,
32+
value: "experienced",
33+
messageId: "cfp.speakerLevel.experienced",
34+
},
35+
];
36+
37+
export const AboutYouSection = ({ formOptions, getErrors }) => {
38+
const inputPlaceholder = useTranslatedMessage("input.placeholder");
39+
const { select, url } = formOptions;
40+
41+
return (
42+
<MultiplePartsCard>
43+
<CardPart contentAlign="left">
44+
<Heading size={3}>
45+
<FormattedMessage id="cfp.aboutYou" />
46+
</Heading>
47+
</CardPart>
48+
<CardPart background="milk" contentAlign="left">
49+
<Grid cols={1} gap="medium">
50+
<Text size={2}>
51+
<FormattedMessage id="cfp.aboutYouDescription" />
52+
</Text>
53+
<InputWrapper
54+
required={true}
55+
title={<FormattedMessage id="cfp.speakerLevel" />}
56+
description={<FormattedMessage id="cfp.speakerLevelDescription" />}
57+
>
58+
<Select
59+
{...select("speakerLevel")}
60+
required={true}
61+
errors={getErrors("validationSpeakerLevel")}
62+
>
63+
{SPEAKER_LEVEL_OPTIONS.map(({ value, disabled, messageId }) => (
64+
<FormattedMessage id={messageId} key={messageId}>
65+
{(copy) => (
66+
<option disabled={disabled} value={value}>
67+
{copy}
68+
</option>
69+
)}
70+
</FormattedMessage>
71+
))}
72+
</Select>
73+
</InputWrapper>
74+
75+
<InputWrapper
76+
title={<FormattedMessage id="cfp.previousTalkVideo" />}
77+
description={
78+
<FormattedMessage id="cfp.previousTalkVideoDescription" />
79+
}
80+
>
81+
<Input
82+
{...url("previousTalkVideo")}
83+
required={false}
84+
maxLength={2048}
85+
errors={getErrors("validationPreviousTalkVideo")}
86+
placeholder={inputPlaceholder}
87+
/>
88+
</InputWrapper>
89+
</Grid>
90+
</CardPart>
91+
</MultiplePartsCard>
92+
);
93+
};
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {
2+
CardPart,
3+
Heading,
4+
MultiplePartsCard,
5+
Spacer,
6+
Text,
7+
} from "@python-italia/pycon-styleguide";
8+
import { eachDayOfInterval, format, parseISO } from "date-fns";
9+
import { Fragment } from "react";
10+
import { FormattedMessage } from "react-intl";
11+
import { useCurrentLanguage } from "~/locale/context";
12+
import type { CfpFormQuery } from "~/types";
13+
14+
const CHOICES = ["available", "preferred", "unavailable"];
15+
const RANGES = ["am", "pm"];
16+
17+
type Props = {
18+
conferenceData: CfpFormQuery;
19+
selectedDuration: any;
20+
speakerAvailabilities: any;
21+
onChangeAvailability: any;
22+
};
23+
export const AvailabilitySection = ({
24+
conferenceData,
25+
selectedDuration,
26+
speakerAvailabilities,
27+
onChangeAvailability,
28+
}: Props) => {
29+
const language = useCurrentLanguage();
30+
const {
31+
conference: { start, end },
32+
} = conferenceData;
33+
const parsedStart = parseISO(start);
34+
const parsedEnd = parseISO(end);
35+
const daysBetween = eachDayOfInterval({ start: parsedStart, end: parsedEnd });
36+
const dateFormatter = new Intl.DateTimeFormat(language, {
37+
day: "2-digit",
38+
month: "long",
39+
});
40+
41+
return (
42+
<MultiplePartsCard>
43+
<CardPart contentAlign="left">
44+
<Heading size={3}>
45+
<FormattedMessage id="cfp.availability.title" />
46+
</Heading>
47+
</CardPart>
48+
<CardPart background="milk" contentAlign="left">
49+
<Text size={2}>
50+
<FormattedMessage id="cfp.availability.description" />
51+
</Text>
52+
<Spacer size="small" />
53+
<div
54+
className="grid gap-2 lg:gap-4 select-none"
55+
style={{
56+
gridTemplateColumns: `150px repeat(${daysBetween.length}, 1fr)`,
57+
}}
58+
>
59+
<div />
60+
61+
{daysBetween.map((day) => (
62+
<Text
63+
key={day.toISOString()}
64+
weight="strong"
65+
size={"label3"}
66+
align="center"
67+
>
68+
{dateFormatter.format(day)}
69+
</Text>
70+
))}
71+
72+
{RANGES.map((hour) => (
73+
<Fragment key={hour}>
74+
<div>
75+
<Text weight="strong" size={"label3"} as="p">
76+
{hour === "am" ? (
77+
<FormattedMessage id="cfp.availability.table.morning" />
78+
) : (
79+
<FormattedMessage id="cfp.availability.table.afternoon" />
80+
)}
81+
</Text>
82+
<Spacer size="thin" />
83+
<Text weight="strong" size={"label3"} as="p">
84+
{hour === "am" ? (
85+
<FormattedMessage id="cfp.availability.table.morning.range" />
86+
) : (
87+
<FormattedMessage id="cfp.availability.table.afternoon.range" />
88+
)}
89+
</Text>
90+
</div>
91+
{daysBetween.map((day) => {
92+
const availabilityDate = `${format(day, "yyyy-MM-dd")}@${hour}`;
93+
const currentChoice =
94+
speakerAvailabilities?.[availabilityDate] || "available";
95+
96+
return (
97+
<div
98+
className="w-full cursor-pointer text-center p-1 bg-grey-50 hover:bg-grey-100"
99+
onClick={(_) => {
100+
const nextChoice =
101+
CHOICES[
102+
(CHOICES.indexOf(currentChoice) + 1) % CHOICES.length
103+
];
104+
onChangeAvailability(availabilityDate, nextChoice);
105+
}}
106+
>
107+
{currentChoice === "available" && <>&nbsp;</>}
108+
{currentChoice === "preferred" && "✔️"}
109+
{currentChoice === "unavailable" && "❌"}
110+
</div>
111+
);
112+
})}
113+
</Fragment>
114+
))}
115+
</div>
116+
117+
<Spacer size="small" />
118+
119+
<Text size={3}>
120+
<FormattedMessage id="cfp.availability.explanation" />
121+
</Text>
122+
</CardPart>
123+
</MultiplePartsCard>
124+
);
125+
};

frontend/src/components/cfp-form/cfp-form.graphql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
query CfpForm($conference: String!) {
22
conference(code: $conference) {
33
id
4+
start
5+
end
46

57
durations {
68
name

0 commit comments

Comments
 (0)