Skip to content

Commit 8958959

Browse files
committed
CFP availability
1 parent c854d3d commit 8958959

File tree

6 files changed

+523
-290
lines changed

6 files changed

+523
-290
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: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {
2+
CardPart,
3+
Heading,
4+
MultiplePartsCard,
5+
Spacer,
6+
Tag,
7+
Text,
8+
} from "@python-italia/pycon-styleguide";
9+
import clsx from "clsx";
10+
import {
11+
eachDayOfInterval,
12+
eachMinuteOfInterval,
13+
format,
14+
parseISO,
15+
setHours,
16+
setMinutes,
17+
} from "date-fns";
18+
import { Fragment } from "react";
19+
import { FormattedMessage } from "react-intl";
20+
import type { CfpFormQuery } from "~/types";
21+
22+
const CHOICES = ["available", "preferred", "unavailable"];
23+
24+
type Props = {
25+
conferenceData: CfpFormQuery;
26+
selectedDuration: any;
27+
selectedAvailabilities: any;
28+
onChangeAvailability: any;
29+
};
30+
export const AvailabilitySection = ({
31+
conferenceData,
32+
selectedDuration,
33+
selectedAvailabilities,
34+
onChangeAvailability,
35+
}: Props) => {
36+
const {
37+
conference: { start, end },
38+
} = conferenceData;
39+
const parsedStart = parseISO(start);
40+
const parsedEnd = parseISO(end);
41+
const daysBetween = eachDayOfInterval({ start: parsedStart, end: parsedEnd });
42+
const hoursBetween = eachMinuteOfInterval(
43+
{
44+
start: setHours(parsedStart, 9),
45+
end: setHours(parsedStart, 17),
46+
},
47+
{
48+
step: 30,
49+
},
50+
);
51+
52+
return (
53+
<MultiplePartsCard>
54+
<CardPart contentAlign="left">
55+
<Heading size={3}>
56+
<FormattedMessage id="cfp.availability.title" />
57+
</Heading>
58+
</CardPart>
59+
<CardPart background="milk" contentAlign="left">
60+
<Text size={2}>
61+
<FormattedMessage id="cfp.availability.description" />
62+
</Text>
63+
<Spacer size="small" />
64+
<div
65+
className="grid gap-2 lg:gap-4 select-none"
66+
style={{
67+
gridTemplateColumns: `70px repeat(${daysBetween.length}, 1fr)`,
68+
}}
69+
>
70+
<div />
71+
72+
{daysBetween.map((day) => (
73+
<Text
74+
key={day.toISOString()}
75+
weight="strong"
76+
size={"label3"}
77+
align="center"
78+
>
79+
{format(day, "EEEE d MMM")}
80+
</Text>
81+
))}
82+
83+
{hoursBetween.map((hour) => (
84+
<Fragment key={hour.toISOString()}>
85+
<div>
86+
<Text weight="strong" size={"label3"}>
87+
{format(hour, "HH:mm")}
88+
</Text>
89+
</div>
90+
{daysBetween.map((day) => {
91+
const mergedDate = setHours(
92+
setMinutes(day, hour.getMinutes()),
93+
hour.getHours(),
94+
);
95+
96+
const currentChoice =
97+
selectedAvailabilities?.[mergedDate.getTime()] || "available";
98+
99+
return (
100+
<div
101+
className={clsx("w-full cursor-pointer text-center p-1", {
102+
"bg-grey-50 hover:bg-grey-100": true,
103+
})}
104+
onClick={(_) => {
105+
const options = ["available", "preferred", "unavailable"];
106+
const nextChoice =
107+
options[
108+
(options.indexOf(currentChoice) + 1) % options.length
109+
];
110+
onChangeAvailability(mergedDate, nextChoice);
111+
}}
112+
>
113+
{currentChoice === "available" && <>&nbsp;</>}
114+
{currentChoice === "preferred" && "✔️"}
115+
{currentChoice === "unavailable" && "❌"}
116+
</div>
117+
);
118+
})}
119+
</Fragment>
120+
))}
121+
122+
<div />
123+
{daysBetween.map((day) => (
124+
<div className="flex items-center justify-center gap-1">
125+
<Text key={day.toISOString()} size={"label3"}>
126+
(Prefer all)
127+
</Text>
128+
/
129+
<Text key={day.toISOString()} size={"label3"}>
130+
(Unavailable all)
131+
</Text>
132+
</div>
133+
))}
134+
</div>
135+
136+
<Text size={3}>
137+
<FormattedMessage
138+
id="cfp.availability.explanation"
139+
values={{
140+
duration: selectedDuration ? (
141+
<Text size="inherit" weight="strong">
142+
({selectedDuration.name})
143+
</Text>
144+
) : null,
145+
}}
146+
/>
147+
</Text>
148+
</CardPart>
149+
</MultiplePartsCard>
150+
);
151+
};

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)