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
28 changes: 1 addition & 27 deletions frontend/src/html/popups.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,33 +120,7 @@
<button>Report</button>
</div>
</dialog>
<dialog id="streakHourOffsetModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Set streak hour offset</div>
<div class="text">
Streaks reset at midnight UTC by default. If this is not convenient for
you (for example if it means that streaks reset in the middle of the day),
you can change the hour offset here.
<br />
<br />
This will not take daylight savings time into consideration!
<br />
<br />
<span class="red">You can only do this once!</span>
</div>
<div class="group">
<button class="decreaseOffset">
<i class="fas fa-fw fa-chevron-left"></i>
</button>
<input type="number" min="-11" max="12" value="0" step="0.5" />
<button class="increaseOffset">
<i class="fas fa-fw fa-chevron-right"></i>
</button>
</div>
<div class="preview"></div>
<button class="submit">set</button>
</div>
</dialog>

<dialog id="editResultTagsModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Edit result tags</div>
Expand Down
38 changes: 0 additions & 38 deletions frontend/src/styles/popups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -302,41 +302,3 @@ body.darkMode {
}
}
}

#streakHourOffsetModal {
.modal {
max-width: 500px;
.red {
color: var(--error-color);
}
.group {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 0.5rem;
justify-items: center;
align-items: center;
font-size: 2em;
button {
width: 100%;
}
input {
text-align: center;
}
}
.preview {
& > div:first-child {
margin-bottom: 1rem;
}
.row {
display: flex;
gap: 1rem;
}
div:first-child {
flex-grow: 1;
}
div:last-child {
align-self: center;
}
}
}
}
2 changes: 2 additions & 0 deletions frontend/src/ts/components/modals/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { QuoteSearchModal } from "./QuoteSearchModal";
import { RegisterCaptchaModal } from "./RegisterCaptchaModal";
import { ShareTestSettings } from "./ShareTestSettings";
import { SimpleModal } from "./SimpleModal";
import { StreakHourOffsetModal } from "./StreakHourOffsetModal";
import { SupportModal } from "./SupportModal";
import { VersionHistoryModal } from "./VersionHistoryModal";

Expand All @@ -40,6 +41,7 @@ export function Modals(): JSXElement {
<EditPresetModal />
<ViewApeKeyModal />
<LastSignedOutResultModal />
<StreakHourOffsetModal />
</>
);
}
174 changes: 174 additions & 0 deletions frontend/src/ts/components/modals/StreakHourOffsetModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { StreakHourOffsetSchema } from "@monkeytype/schemas/users";
import { createForm } from "@tanstack/solid-form";

import Ape from "../../ape";
import { Snapshot } from "../../constants/default-snapshot";
import { getSnapshot, setSnapshot } from "../../db";
import { hideLoaderBar, showLoaderBar } from "../../states/loader-bar";
import { hideModal } from "../../states/modals";
import {
showErrorNotification,
showNoticeNotification,
showSuccessNotification,
} from "../../states/notifications";
import { AnimatedModal } from "../common/AnimatedModal";
import { Button } from "../common/Button";
import { InputField } from "../ui/form/InputField";
import { SubmitButton } from "../ui/form/SubmitButton";
import { allFieldsMandatory, fromSchema } from "../ui/form/utils";

export function StreakHourOffsetModal() {
const form = createForm(() => ({
defaultValues: {
offset: "0.0",
},
onSubmitInvalid: () => {
showNoticeNotification("Please fill in all fields");
},
validators: {
onChange: allFieldsMandatory(),
},
onSubmit: async ({ value }) => {
const { offset } = value;
const hourOffset = parseFloat(offset);

showLoaderBar();

const response = await Ape.users.setStreakHourOffset({
body: { hourOffset },
});
hideLoaderBar();

if (response.status !== 200) {
showErrorNotification("Failed to set streak hour offset", { response });
hideModal("StreakHourOffset");
return;
}
showSuccessNotification("Streak hour offset set");
const snap = getSnapshot() as Snapshot;

snap.streakHourOffset = hourOffset;
setSnapshot(snap);
hideModal("StreakHourOffset");
},
}));

return (
<AnimatedModal
id="StreakHourOffset"
title="Set streak hour offset"
modalClass="max-w-lg"
>
<p>
Streaks reset at midnight UTC by default. If this is not convenient for
you (for example if it means that streaks reset in the middle of the
day), you can change the hour offset here.
<br />
<br />
This will not take daylight savings time into consideration!
<br />
<br />
<span class="text-error">You can only do this once!</span>
</p>
<form
class="flex flex-col justify-center gap-4"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
void form.handleSubmit();
}}
>
<div class="grid grid-cols-3 items-center justify-items-center gap-2 text-2xl">
<form.Field
name="offset"
validators={{
onChange: ({ value }) => {
const val = parseFloat(String(value));
if (isNaN(val)) {
return "Must be a number";
}
return fromSchema(StreakHourOffsetSchema)({
value: val,
});
},
}}
children={(field) => (
<>
<Button
fa={{ icon: "fa-chevron-left" }}
class="w-full"
disabled={clampOffset(field().state.value) <= -11}
onClick={() => {
const current = clampOffset(form.getFieldValue("offset"));
const newVal = (current - 0.5).toFixed(1);
form.setFieldValue("offset", newVal);
}}
/>
<InputField
field={field}
type="number"
class="text-center"
min={-11}
max={12}
step={0.5}
alwaysShowFieldIndicator={true}
/>
<Button
fa={{ icon: "fa-chevron-right" }}
class="w-full"
disabled={clampOffset(field().state.value) >= 12}
onClick={() => {
const current = clampOffset(form.getFieldValue("offset"));
const newVal = (current + 0.5).toFixed(1);
form.setFieldValue("offset", newVal);
}}
/>
</>
)}
/>
</div>

<div class="grid grid-cols-[1fr_auto] gap-2">
<div>Current local reset time:</div>
<div>{getDate()}</div>

<div>New local reset time:</div>
<form.Field
name="offset"
children={(field) => <div>{getNewDate(field().state.value)}</div>}
/>
</div>

<SubmitButton form={form} text="set" skipUnchangedCheck />
</form>
</AnimatedModal>
);
}

function clampOffset(value: string): number {
const num = Number.parseFloat(value);
if (isNaN(num)) return 0;
return Math.max(-11, Math.min(12, num));
}

function getNewDate(offset: string): string {
const inputValue = Number.parseFloat(offset);
if (isNaN(inputValue)) return "-";
const newDate = new Date();
newDate.setUTCHours(0);
newDate.setUTCMinutes(0);
newDate.setUTCSeconds(0);
newDate.setUTCMilliseconds(0);

newDate.setHours(newDate.getHours() - -1 * Math.floor(inputValue)); //idk why, but it only works when i subtract (so i have to negate inputValue)
newDate.setMinutes(
newDate.getMinutes() - -1 * ((((inputValue % 1) + 1) % 1) * 60),
);
return newDate.toLocaleTimeString();
}

function getDate(): string {
const date = new Date();
date.setUTCHours(0, 0, 0, 0);
return date.toLocaleTimeString();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Show } from "solid-js";

import Ape from "../../../ape";
import * as StreakHourOffsetModal from "../../../modals/streak-hour-offset";
import { showLoaderBar } from "../../../states/loader-bar";
import { showModal } from "../../../states/modals";
import { showErrorNotification } from "../../../states/notifications";
import { getSnapshot } from "../../../states/snapshot";
import { Button } from "../../common/Button";
Expand Down Expand Up @@ -126,7 +126,7 @@ function UpdateStreakOffset() {
</>
button={{
text: "update hour offset",
onClick: () => StreakHourOffsetModal.show(),
onClick: () => showModal("StreakHourOffset"),
}}
disabled={getSnapshot()?.streakHourOffset !== undefined}
disabledDescription=<>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/ts/components/ui/form/FieldIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LoadingCircle } from "../../common/LoadingCircle";

export type FieldIndicatorProps = {
field: AnyFieldApi;
alwaysShow?: boolean;
};

export function FieldIndicator(props: FieldIndicatorProps) {
Expand Down Expand Up @@ -45,9 +46,10 @@ export function FieldIndicator(props: FieldIndicatorProps) {
</Match>
<Match
when={
props.field.state.meta.isTouched &&
props.field.state.meta.isValid &&
!props.field.state.meta.isDefaultValue
(props.alwaysShow === true ||
(props.field.state.meta.isTouched &&
!props.field.state.meta.isDefaultValue))
}
>
<Fa icon="fa-check" class="text-main" fixedWidth />
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/ts/components/ui/form/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function InputField(props: {
min?: number;
max?: number;
step?: string | number;
alwaysShowFieldIndicator?: boolean;
}): JSXElement {
const [shake, setShake] = createSignal(false);

Expand Down Expand Up @@ -105,7 +106,10 @@ export function InputField(props: {
step={props.step?.toString()}
/>
<Show when={props.field().options.validators}>
<FieldIndicator field={props.field()} />
<FieldIndicator
field={props.field()}
alwaysShow={props.alwaysShowFieldIndicator}
/>
</Show>
</div>
);
Expand Down
Loading
Loading