mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-06-02 22:26:57 -05:00
New event dates fix able to submit without one
This commit is contained in:
parent
53fa0147c7
commit
6a6f8a19e8
3
TODO.md
3
TODO.md
|
|
@ -23,9 +23,10 @@ Calendar
|
|||
|
||||
## Other
|
||||
|
||||
- [ ] date input take in account time zone difference between server and client
|
||||
- [x] User selector allow passing users from top level
|
||||
- [ ] Constants use CALENDAR_EVENT object
|
||||
- [ ] New unfriendly crash if omits dates
|
||||
- [x] New unfriendly crash if omits dates
|
||||
- [x] badges inside tag?
|
||||
- [x] Tags selector can remove
|
||||
- [x] Calendar new title
|
||||
|
|
|
|||
|
|
@ -87,13 +87,10 @@ const newCalendarEventActionSchema = z.object({
|
|||
falsyToNull,
|
||||
z.string().max(CALENDAR_EVENT_DESCRIPTION_MAX_LENGTH).nullable()
|
||||
),
|
||||
dates: z.preprocess(
|
||||
safeJSONParse,
|
||||
z
|
||||
.array(z.preprocess(date, z.date().min(MIN_DATE).max(MAX_DATE)))
|
||||
.min(1)
|
||||
.max(CALENDAR_EVENT_MAX_AMOUNT_OF_DATES)
|
||||
),
|
||||
date: z
|
||||
.array(z.preprocess(date, z.date().min(MIN_DATE).max(MAX_DATE)))
|
||||
.min(1)
|
||||
.max(CALENDAR_EVENT_MAX_AMOUNT_OF_DATES),
|
||||
bracketUrl: z.string().url().max(CALENDAR_EVENT_BRACKET_URL_MAX_LENGTH),
|
||||
discordInviteCode: z.preprocess(
|
||||
falsyToNull,
|
||||
|
|
@ -127,7 +124,7 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
const commonArgs = {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
startTimes: data.dates.map((date) => dateToDatabaseTimestamp(date)),
|
||||
startTimes: data.date.map((date) => dateToDatabaseTimestamp(date)),
|
||||
bracketUrl: data.bracketUrl,
|
||||
discordInviteCode: data.discordInviteCode,
|
||||
tags: data.tags
|
||||
|
|
@ -270,13 +267,8 @@ function DescriptionTextarea() {
|
|||
|
||||
function DatesInput() {
|
||||
const { eventToEdit } = useLoaderData<typeof loader>();
|
||||
const { i18n } = useTranslation();
|
||||
const [dateInputValue, setDateInputValue] = React.useState<string>();
|
||||
const [dates, setDates] = React.useState(
|
||||
(eventToEdit?.startTimes ?? []).map((startTime) => ({
|
||||
date: databaseTimestampToDate(startTime),
|
||||
id: String(Math.random()),
|
||||
}))
|
||||
const [datesCount, setDatesCount] = React.useState(
|
||||
eventToEdit?.startTimes.length ?? 1
|
||||
);
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
|
|
@ -286,85 +278,64 @@ function DatesInput() {
|
|||
|
||||
return (
|
||||
<div className="stack md items-start">
|
||||
{dates.length > 0 && (
|
||||
<input
|
||||
type="hidden"
|
||||
name="dates"
|
||||
value={JSON.stringify(dates.map(({ date }) => date.getTime()))}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<Label htmlFor="date" required>
|
||||
Dates
|
||||
</Label>
|
||||
<div className="stack horizontal sm items-center">
|
||||
<input
|
||||
id="date"
|
||||
type="datetime-local"
|
||||
value={dateInputValue ?? ""}
|
||||
onChange={(e) => setDateInputValue(e.target.value)}
|
||||
min={dateToYearMonthDayHourMinuteString(MIN_DATE)}
|
||||
max={dateToYearMonthDayHourMinuteString(MAX_DATE)}
|
||||
data-cy="date-input"
|
||||
/>
|
||||
<Button
|
||||
tiny
|
||||
disabled={!dateInputValue}
|
||||
onClick={() => {
|
||||
setDates(
|
||||
[
|
||||
...dates,
|
||||
{
|
||||
date: new Date(dateInputValue!),
|
||||
id: String(Math.random()),
|
||||
},
|
||||
].sort((a, b) => a.date.getTime() - b.date.getTime())
|
||||
);
|
||||
}}
|
||||
data-cy="add-date-button"
|
||||
>
|
||||
Add date
|
||||
</Button>
|
||||
<div className="stack sm">
|
||||
{new Array(datesCount).fill(null).map((_, i) => {
|
||||
const defaultStartTime = eventToEdit?.startTimes[i];
|
||||
|
||||
return (
|
||||
<div key={i} className="stack horizontal sm items-center">
|
||||
<input
|
||||
id="date"
|
||||
type="datetime-local"
|
||||
name="date"
|
||||
defaultValue={
|
||||
defaultStartTime
|
||||
? dateToYearMonthDayHourMinuteString(
|
||||
databaseTimestampToDate(defaultStartTime)
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
min={dateToYearMonthDayHourMinuteString(MIN_DATE)}
|
||||
max={dateToYearMonthDayHourMinuteString(MAX_DATE)}
|
||||
data-cy="date-input"
|
||||
required
|
||||
/>
|
||||
{i === datesCount - 1 && (
|
||||
<>
|
||||
<Button
|
||||
tiny
|
||||
disabled={
|
||||
datesCount === CALENDAR_EVENT_MAX_AMOUNT_OF_DATES
|
||||
}
|
||||
onClick={() => setDatesCount((count) => count + 1)}
|
||||
data-cy="add-date-button"
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
{datesCount > 1 && (
|
||||
<Button
|
||||
tiny
|
||||
onClick={() => setDatesCount((count) => count - 1)}
|
||||
data-cy="remove-date-button"
|
||||
variant="destructive"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<FormMessage type="info" className={clsx({ invisible: !isMounted })}>
|
||||
Times in your local time zone: {usersTimeZone}
|
||||
</FormMessage>
|
||||
</div>
|
||||
{dates.length > 0 && (
|
||||
<div className="calendar-new__dates-list">
|
||||
{dates.map(({ date, id }, i) => (
|
||||
<React.Fragment key={id}>
|
||||
<div
|
||||
className={clsx("text-lighter", {
|
||||
hidden: dates.length === 1,
|
||||
})}
|
||||
>
|
||||
Day {i + 1}
|
||||
</div>
|
||||
<div className={clsx({ invisible: !isMounted })}>
|
||||
{isMounted
|
||||
? date.toLocaleTimeString(i18n.language, {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
weekday: "long",
|
||||
})
|
||||
: "T"}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setDates(dates.filter((date) => date.id !== id))}
|
||||
className={clsx("mr-auto", { invisible: !isMounted })}
|
||||
icon={<TrashIcon />}
|
||||
variant="minimal-destructive"
|
||||
aria-label="Remove date"
|
||||
data-cy="date-delete-button"
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,3 @@
|
|||
.calendar-new__dates-list {
|
||||
display: grid;
|
||||
padding: 0;
|
||||
font-size: var(--fonts-sm);
|
||||
font-weight: var(--semi-bold);
|
||||
gap: var(--s-2);
|
||||
grid-template-columns: max-content max-content 1fr;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.calendar-new__select {
|
||||
max-width: 16rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,19 @@ export function weekNumberToDate({
|
|||
return result;
|
||||
}
|
||||
|
||||
/** Returns date as a string with the format YYYY-MM-DDThh:mm */
|
||||
/** Returns date as a string with the format YYYY-MM-DDThh:mm in user's time zone */
|
||||
export function dateToYearMonthDayHourMinuteString(date: Date) {
|
||||
return date.toISOString().slice(0, 16);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hour = date.getHours();
|
||||
const minute = date.getMinutes();
|
||||
|
||||
return `${year}-${prefixZero(month)}-${prefixZero(day)}T${prefixZero(
|
||||
hour
|
||||
)}:${prefixZero(minute)}`;
|
||||
}
|
||||
|
||||
function prefixZero(number: number) {
|
||||
return number < 10 ? `0${number}` : number;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user