Skip to content
Draft
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
9 changes: 9 additions & 0 deletions frontend/__tests__/components/common/AnimatedModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { render } from "@solidjs/testing-library";
import { describe, it, expect, vi, beforeEach } from "vitest";

import { AnimatedModal } from "../../../src/ts/components/common/AnimatedModal";
import { hideModal, ModalId, showModal } from "../../../src/ts/states/modals";

describe("AnimatedModal", () => {
beforeEach(() => {
showModal("Support" as ModalId);
vi.clearAllMocks();

// Mock dialog methods that don't exist in jsdom
Expand Down Expand Up @@ -57,6 +59,13 @@ describe("AnimatedModal", () => {
).toHaveTextContent("Test Content");
});

it("doesnt render children if not open", () => {
hideModal("Support" as ModalId);
const { modalDiv } = renderModal({});

expect(modalDiv).not.toBeInTheDocument();
});

it("has escape handler attached", () => {
const { dialog } = renderModal({});

Expand Down
45 changes: 0 additions & 45 deletions frontend/src/html/popups.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
<dialog id="forgotPasswordModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Forgot password</div>
<input type="text" placeholder="email" />
<div class="g-recaptcha"></div>
<!-- <button>send</button> -->
</div>
</dialog>

<dialog id="lastSignedOutResult" class="modalWrapper hidden">
<div class="modal">
<div class="title">Last signed out result</div>
Expand Down Expand Up @@ -122,42 +113,6 @@
</div>
</dialog>

<dialog id="googleSignUpModal" class="modalWrapper hidden">
<form class="modal">
<div class="title">Account name</div>
<div class="text">Please enter a username before continuing</div>
<input type="text" placeholder="username" />
<div class="captcha"></div>
<button>continue</button>
</form>
</dialog>
<dialog id="userReportModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Report a user</div>
<div class="text">
Please report users responsibly and add comments in English only. Misuse
may result in you losing access to this feature.
</div>
<label>user</label>
<div class="user"></div>
<label>reason</label>
<select name="report-reason" class="reason">
<option value="Inappropriate name">Inappropriate name</option>
<option value="Inappropriate bio">Inappropriate bio</option>
<option value="Inappropriate social links">
Inappropriate social links
</option>
<option value="Suspected cheating">Suspected cheating</option>
</select>
<label>comment</label>
<textarea class="comment" autocomplete="off"></textarea>
<div
class="g-recaptcha"
data-sitekey="6Lc-V8McAAAAAJ7s6LGNe7MBZnRiwbsbiWts87aj"
></div>
<button>Report</button>
</div>
</dialog>
<dialog id="streakHourOffsetModal" class="modalWrapper hidden">
<div class="modal">
<div class="title">Set streak hour offset</div>
Expand Down
81 changes: 0 additions & 81 deletions frontend/src/styles/popups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,6 @@ body.darkMode {
}
}

#forgotPasswordModal {
.modal {
max-width: 400px;
}
}

#practiseWordsModal {
.modal {
max-width: 400px;
Expand Down Expand Up @@ -114,54 +108,6 @@ body.darkMode {
}
}

#googleSignUpModal {
.modal {
max-width: 425px;
overflow: unset;
.captcha {
display: flex;
justify-content: center;
}
// .inputAndIndicator {
// input {
// width: 100%;
// }
// position: relative;
// .checkStatus {
// width: 2.25rem;
// height: 2.25rem;
// position: absolute;
// right: 0;
// top: 0;
// /* background: red; */
// display: grid;
// grid-template-columns: 2.25rem;
// grid-template-rows: 2.25rem;
// place-items: center center;
// cursor: pointer;

// .checking,
// .available,
// .unavailable,
// .taken {
// grid-column: 1/2;
// grid-row: 1/2;
// }
// .checking {
// color: var(--sub-color);
// }
// .available {
// color: var(--main-color);
// }
// .unavailable,
// .taken {
// color: var(--error-color);
// }
// }
// }
}
}

#lastSignedOutResult {
.modal {
max-width: 600px;
Expand Down Expand Up @@ -304,33 +250,6 @@ body.darkMode {
}
}

#userReportModal {
.modal {
max-width: 800px;

label {
color: var(--sub-color);
margin-bottom: -0.5rem;
}

// .text {
// // color: var(--sub-color);
// }

.user {
font-size: 1.5rem;
}

textarea {
resize: vertical;
width: 100%;
padding: 10px;
line-height: 1.2rem;
min-height: 5rem;
}
}
}

#editResultTagsModal {
.modal {
max-width: 600px;
Expand Down
32 changes: 17 additions & 15 deletions frontend/src/ts/components/common/AnimatedModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { JSXElement, ParentProps, Show, onCleanup } from "solid-js";
import { createEffectOn } from "../../hooks/effects";
import { useRefWithUtils } from "../../hooks/useRefWithUtils";
import {
hideModal as storeHideModal,
ModalId,
isModalOpen,
isModalChained,
isModalOpen,
hideModal as storeHideModal,
} from "../../states/modals";
import { cn } from "../../utils/cn";
import { applyReducedMotion } from "../../utils/misc";
Expand Down Expand Up @@ -328,19 +328,21 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement {
onKeyDown={handleKeyDown}
onMouseDown={handleBackdropClick}
>
<div
class={cn(
"modal pointer-events-auto grid h-max max-h-full w-full max-w-md gap-4 overflow-auto rounded-double bg-bg p-4 text-text ring-4 ring-sub-alt sm:p-8",
props.modalClass,
)}
ref={modalRef}
onScroll={(e) => props.onScroll?.(e)}
>
<Show when={props.title !== undefined && props.title !== ""}>
<div class="text-2xl text-sub">{props.title}</div>
</Show>
{props.children}
</div>
<Show when={isModalOpen(props.id)}>
<div
class={cn(
"modal pointer-events-auto grid h-max max-h-full w-full max-w-md gap-4 overflow-auto rounded-double bg-bg p-4 text-text ring-4 ring-sub-alt sm:p-8",
props.modalClass,
)}
ref={modalRef}
onScroll={(e) => props.onScroll?.(e)}
>
<Show when={props.title !== undefined && props.title !== ""}>
<div class="text-2xl text-sub">{props.title}</div>
</Show>
{props.children}
</div>
</Show>
</dialog>
);
}
96 changes: 96 additions & 0 deletions frontend/src/ts/components/modals/ForgotPasswordModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { UserEmailSchema } from "@monkeytype/schemas/users";
import { createForm } from "@tanstack/solid-form";

import Ape from "../../ape";
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 { Captcha } from "../ui/form/Captcha";
import { InputField } from "../ui/form/InputField";
import { SubmitButton } from "../ui/form/SubmitButton";
import { allFieldsMandatory, fromSchema } from "../ui/form/utils";

export function ForgotPasswordModal() {
const form = createForm(() => ({
defaultValues: {
email: "",
captcha: "",
},
onSubmitInvalid: () => {
showNoticeNotification("Please fill in all fields");
},
onSubmit: async ({ value }) => {
await apply(value);
form.reset();
},
validators: {
onChange: allFieldsMandatory(),
},
}));

return (
<AnimatedModal id="ForgotPasswordModal" title="Forgot password">
<form
class="flex flex-col justify-center gap-4"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
void form.handleSubmit();
}}
>
<form.Field
name="email"
validators={{
onChange: fromSchema(UserEmailSchema, {
convert: (it) => it?.trim(),
}),
}}
children={(field) => (
<InputField field={field} placeholder="email" type="email" />
)}
/>

<form.Field
name="captcha"
children={(field) => <Captcha field={field} />}
/>

<SubmitButton form={form} text="request password reset" />
</form>
</AnimatedModal>
);
}

async function apply(options: {
email: string;
captcha: string;
}): Promise<void> {
const { email, captcha } = options;

if (email === undefined || email === "") {
showNoticeNotification("Please enter your email address");
return;
}

showLoaderBar();
const result = await Ape.users.forgotPasswordEmail({
body: { email, captcha },
});

hideLoaderBar();
if (result.status !== 200) {
showErrorNotification(
`Failed to send password reset email: ${result.body.message}`,
);
return;
}

showSuccessNotification(result.body.message, { durationMs: 5000 });

hideModal("ForgotPasswordModal");
}
Loading
Loading