UI consolidation
This commit is contained in:
@@ -2,8 +2,6 @@ import { createSignal, Show, createEffect } from "solid-js";
|
||||
import { Title, Meta } from "@solidjs/meta";
|
||||
import { useNavigate, redirect, query, createAsync } from "@solidjs/router";
|
||||
import { getEvent } from "vinxi/http";
|
||||
import Eye from "~/components/icons/Eye";
|
||||
import EyeSlash from "~/components/icons/EyeSlash";
|
||||
import XCircle from "~/components/icons/XCircle";
|
||||
import GoogleLogo from "~/components/icons/GoogleLogo";
|
||||
import GitHub from "~/components/icons/GitHub";
|
||||
@@ -14,6 +12,9 @@ import { validatePassword, isValidEmail } from "~/lib/validation";
|
||||
import { TerminalSplash } from "~/components/TerminalSplash";
|
||||
import { VALIDATION_CONFIG } from "~/config";
|
||||
import { api } from "~/lib/api";
|
||||
import Input from "~/components/ui/Input";
|
||||
import PasswordInput from "~/components/ui/PasswordInput";
|
||||
import Button from "~/components/ui/Button";
|
||||
|
||||
import type { UserProfile } from "~/types/user";
|
||||
import PasswordStrengthMeter from "~/components/PasswordStrengthMeter";
|
||||
@@ -87,10 +88,6 @@ export default function AccountPage() {
|
||||
const [passwordDeletionError, setPasswordDeletionError] = createSignal(false);
|
||||
const [newPassword, setNewPassword] = createSignal("");
|
||||
|
||||
const [showOldPasswordInput, setShowOldPasswordInput] = createSignal(false);
|
||||
const [showPasswordInput, setShowPasswordInput] = createSignal(false);
|
||||
const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false);
|
||||
|
||||
const [showImageSuccess, setShowImageSuccess] = createSignal(false);
|
||||
const [showEmailSuccess, setShowEmailSuccess] = createSignal(false);
|
||||
const [showDisplayNameSuccess, setShowDisplayNameSuccess] =
|
||||
@@ -662,25 +659,19 @@ export default function AccountPage() {
|
||||
JavaScript required to update email
|
||||
</div>
|
||||
</noscript>
|
||||
<div class="input-group mx-4">
|
||||
<input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
required
|
||||
disabled={
|
||||
emailButtonLoading() ||
|
||||
(userProfile().email !== null &&
|
||||
!userProfile().emailVerified)
|
||||
}
|
||||
placeholder=" "
|
||||
title="Please enter a valid email address"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">
|
||||
{userProfile().email ? "Update Email" : "Add Email"}
|
||||
</label>
|
||||
</div>
|
||||
<Input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
required
|
||||
disabled={
|
||||
emailButtonLoading() ||
|
||||
(userProfile().email !== null &&
|
||||
!userProfile().emailVerified)
|
||||
}
|
||||
title="Please enter a valid email address"
|
||||
label={userProfile().email ? "Update Email" : "Add Email"}
|
||||
containerClass="input-group mx-4"
|
||||
/>
|
||||
<Show
|
||||
when={
|
||||
userProfile().provider !== "email" &&
|
||||
@@ -739,22 +730,15 @@ export default function AccountPage() {
|
||||
JavaScript required to update display name
|
||||
</div>
|
||||
</noscript>
|
||||
<div class="input-group mx-4">
|
||||
<input
|
||||
ref={displayNameRef}
|
||||
type="text"
|
||||
required
|
||||
disabled={displayNameButtonLoading()}
|
||||
placeholder=" "
|
||||
title="Please enter your display name"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">
|
||||
Set {userProfile().displayName ? "New " : ""}Display
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<Input
|
||||
ref={displayNameRef}
|
||||
type="text"
|
||||
required
|
||||
disabled={displayNameButtonLoading()}
|
||||
title="Please enter your display name"
|
||||
label={`Set ${userProfile().displayName ? "New " : ""}Display Name`}
|
||||
containerClass="input-group mx-4"
|
||||
/>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
@@ -806,136 +790,40 @@ export default function AccountPage() {
|
||||
</Show>
|
||||
|
||||
<Show when={userProfile().hasPassword}>
|
||||
<div class="input-group relative mx-4 mb-6">
|
||||
<input
|
||||
ref={oldPasswordRef}
|
||||
type={showOldPasswordInput() ? "text" : "password"}
|
||||
required
|
||||
minlength={VALIDATION_CONFIG.MIN_PASSWORD_LENGTH}
|
||||
disabled={passwordChangeLoading()}
|
||||
placeholder=" "
|
||||
title="Password must be at least 8 characters"
|
||||
class="underlinedInput w-full bg-transparent pr-10"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Old Password</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setShowOldPasswordInput(!showOldPasswordInput())
|
||||
}
|
||||
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
|
||||
>
|
||||
<Show
|
||||
when={showOldPasswordInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
<PasswordInput
|
||||
ref={oldPasswordRef}
|
||||
required
|
||||
minlength={VALIDATION_CONFIG.MIN_PASSWORD_LENGTH}
|
||||
disabled={passwordChangeLoading()}
|
||||
title="Password must be at least 8 characters"
|
||||
label="Old Password"
|
||||
containerClass="input-group relative mx-4 mb-6"
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<div class="input-group relative mx-4 mb-2">
|
||||
<input
|
||||
ref={newPasswordRef}
|
||||
type={showPasswordInput() ? "text" : "password"}
|
||||
required
|
||||
minlength="8"
|
||||
onInput={handleNewPasswordChange}
|
||||
onBlur={handlePasswordBlur}
|
||||
disabled={passwordChangeLoading()}
|
||||
placeholder=" "
|
||||
title="Password must be at least 8 characters"
|
||||
class="underlinedInput w-full bg-transparent pr-10"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">New Password</label>
|
||||
<div class="pt-1">
|
||||
<PasswordStrengthMeter password={newPassword()} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setShowPasswordInput(!showPasswordInput())
|
||||
}
|
||||
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group relative mx-4 mb-2">
|
||||
<input
|
||||
ref={newPasswordConfRef}
|
||||
type={showPasswordConfInput() ? "text" : "password"}
|
||||
required
|
||||
minlength="8"
|
||||
onInput={handlePasswordConfChange}
|
||||
disabled={passwordChangeLoading()}
|
||||
placeholder=" "
|
||||
title="Password must be at least 8 characters"
|
||||
class="underlinedInput w-full bg-transparent pr-10"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">
|
||||
New Password Confirmation
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setShowPasswordConfInput(!showPasswordConfInput())
|
||||
}
|
||||
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordConfInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
<PasswordInput
|
||||
ref={newPasswordRef}
|
||||
required
|
||||
minlength="8"
|
||||
onInput={handleNewPasswordChange}
|
||||
onBlur={handlePasswordBlur}
|
||||
disabled={passwordChangeLoading()}
|
||||
title="Password must be at least 8 characters"
|
||||
label="New Password"
|
||||
showStrength
|
||||
passwordValue={newPassword()}
|
||||
containerClass="input-group relative mx-4 mb-2"
|
||||
/>
|
||||
<PasswordInput
|
||||
ref={newPasswordConfRef}
|
||||
required
|
||||
minlength="8"
|
||||
onInput={handlePasswordConfChange}
|
||||
disabled={passwordChangeLoading()}
|
||||
title="Password must be at least 8 characters"
|
||||
label="New Password Confirmation"
|
||||
containerClass="input-group relative mx-4 mb-2"
|
||||
/>
|
||||
|
||||
<Show
|
||||
when={
|
||||
@@ -1039,22 +927,15 @@ export default function AccountPage() {
|
||||
>
|
||||
<form onSubmit={deleteAccountTrigger}>
|
||||
<div class="flex w-full justify-center">
|
||||
<div class="input-group delete mx-4">
|
||||
<input
|
||||
ref={deleteAccountPasswordRef}
|
||||
type="password"
|
||||
required
|
||||
minlength={VALIDATION_CONFIG.MIN_PASSWORD_LENGTH}
|
||||
disabled={deleteAccountButtonLoading()}
|
||||
placeholder=" "
|
||||
title="Enter your password to confirm account deletion"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">
|
||||
Enter Password
|
||||
</label>
|
||||
</div>
|
||||
<PasswordInput
|
||||
ref={deleteAccountPasswordRef}
|
||||
required
|
||||
minlength={VALIDATION_CONFIG.MIN_PASSWORD_LENGTH}
|
||||
disabled={deleteAccountButtonLoading()}
|
||||
title="Enter your password to confirm account deletion"
|
||||
label="Enter Password"
|
||||
containerClass="input-group delete mx-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -852,3 +852,6 @@ pre {
|
||||
scrollbar-color: var(--color-text) transparent;
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-height: 50vh !important;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getClientCookie, setClientCookie } from "~/lib/cookies.client";
|
||||
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import RevealDropDown from "~/components/RevealDropDown";
|
||||
import Input from "~/components/ui/Input";
|
||||
import type { UserProfile } from "~/types/user";
|
||||
import { getCookie, setCookie } from "vinxi/http";
|
||||
import { z } from "zod";
|
||||
@@ -379,32 +380,26 @@ export default function ContactPage() {
|
||||
>
|
||||
<div class="flex w-full flex-col justify-evenly">
|
||||
<div class="mx-auto w-full justify-evenly md:flex md:flex-row">
|
||||
<div class="input-group md:mx-4">
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
name="name"
|
||||
value={user()?.displayName ?? ""}
|
||||
placeholder=" "
|
||||
title="Please enter your name"
|
||||
class="underlinedInput w-full bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Name</label>
|
||||
</div>
|
||||
<div class="input-group md:mx-4">
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
name="email"
|
||||
value={user()?.email ?? ""}
|
||||
placeholder=" "
|
||||
title="Please enter a valid email address"
|
||||
class="underlinedInput w-full bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Email</label>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
required
|
||||
name="name"
|
||||
value={user()?.displayName ?? ""}
|
||||
title="Please enter your name"
|
||||
label="Name"
|
||||
containerClass="input-group md:mx-4"
|
||||
class="w-full"
|
||||
/>
|
||||
<Input
|
||||
type="email"
|
||||
required
|
||||
name="email"
|
||||
value={user()?.email ?? ""}
|
||||
title="Please enter a valid email address"
|
||||
label="Email"
|
||||
containerClass="input-group md:mx-4"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="mx-auto w-full pt-6 md:pt-12">
|
||||
<div class="textarea-group">
|
||||
|
||||
@@ -2,9 +2,29 @@ import { Title, Meta } from "@solidjs/meta";
|
||||
import { A } from "@solidjs/router";
|
||||
import { createSignal, onMount, onCleanup } from "solid-js";
|
||||
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
|
||||
import { glitchText } from "~/lib/client-utils";
|
||||
|
||||
const DownloadButton = ({
|
||||
onClick,
|
||||
children
|
||||
}: {
|
||||
onClick: () => void;
|
||||
children: Element | string;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
class="bg-green hover:bg-green/90 cursor-pointer rounded-md px-6 py-3 font-mono text-base font-semibold shadow-lg transition-all duration-200 ease-out hover:scale-105 active:scale-95"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DownloadsPage() {
|
||||
const [glitchText, setGlitchText] = createSignal("$ downloads");
|
||||
const [LaLText, setLaLText] = createSignal("Life and Lineage");
|
||||
const [SwAText, setSwAText] = createSignal("Shapes with Abigail!");
|
||||
const [corkText, setCorkText] = createSignal("Cork");
|
||||
|
||||
const download = (assetName: string) => {
|
||||
fetch(`/api/downloads/public/${assetName}`)
|
||||
@@ -17,30 +37,14 @@ export default function DownloadsPage() {
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const originalText = "$ downloads";
|
||||
const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
|
||||
|
||||
const glitchInterval = setInterval(() => {
|
||||
if (Math.random() > 0.9) {
|
||||
let glitched = "";
|
||||
for (let i = 0; i < originalText.length; i++) {
|
||||
if (Math.random() > 0.8) {
|
||||
glitched +=
|
||||
glitchChars[Math.floor(Math.random() * glitchChars.length)];
|
||||
} else {
|
||||
glitched += originalText[i];
|
||||
}
|
||||
}
|
||||
setGlitchText(glitched);
|
||||
|
||||
setTimeout(() => {
|
||||
setGlitchText(originalText);
|
||||
}, 80);
|
||||
}
|
||||
}, 300);
|
||||
const lalInterval = glitchText(LaLText(), setLaLText);
|
||||
const swaInterval = glitchText(SwAText(), setSwAText);
|
||||
const corkInterval = glitchText(corkText(), setCorkText);
|
||||
|
||||
onCleanup(() => {
|
||||
clearInterval(glitchInterval);
|
||||
clearInterval(lalInterval);
|
||||
clearInterval(swaInterval);
|
||||
clearInterval(corkInterval);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,18 +69,11 @@ export default function DownloadsPage() {
|
||||
</div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<div class="text-text mb-12 font-mono text-3xl tracking-wider">
|
||||
<span class="text-green">freno@downloads</span>
|
||||
<span class="text-subtext1">:</span>
|
||||
<span class="text-blue">~</span>
|
||||
<span class="text-subtext1 ml-2">{glitchText()}</span>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-5xl space-y-16">
|
||||
{/* Life and Lineage */}
|
||||
<div class="border-overlay0 rounded-lg border p-6 md:p-8">
|
||||
<h2 class="text-text mb-6 font-mono text-2xl">
|
||||
<span class="text-yellow">{">"}</span> Life and Lineage
|
||||
<span class="text-yellow">{">"}</span> {LaLText()}
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-col gap-8 md:flex-row md:justify-around">
|
||||
@@ -84,12 +81,9 @@ export default function DownloadsPage() {
|
||||
<span class="text-subtext0 font-mono text-sm">
|
||||
platform: android
|
||||
</span>
|
||||
<button
|
||||
onClick={() => download("lineage")}
|
||||
class="bg-green hover:bg-green/90 rounded-md px-6 py-3 font-mono text-base font-semibold shadow-lg transition-all duration-200 ease-out hover:scale-105 active:scale-95"
|
||||
>
|
||||
<DownloadButton onClick={() => download("lineage")}>
|
||||
download.apk
|
||||
</button>
|
||||
</DownloadButton>
|
||||
<span class="text-subtext1 max-w-xs text-center text-xs italic">
|
||||
# android build not optimized
|
||||
</span>
|
||||
@@ -112,7 +106,7 @@ export default function DownloadsPage() {
|
||||
{/* Shapes with Abigail */}
|
||||
<div class="border-overlay0 rounded-lg border p-6 md:p-8">
|
||||
<h2 class="text-text mb-6 font-mono text-2xl">
|
||||
<span class="text-yellow">{">"}</span> Shapes with Abigail!
|
||||
<span class="text-yellow">{">"}</span> {SwAText()}
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-col gap-8 md:flex-row md:justify-around">
|
||||
@@ -120,12 +114,11 @@ export default function DownloadsPage() {
|
||||
<span class="text-subtext0 font-mono text-sm">
|
||||
platform: android
|
||||
</span>
|
||||
<button
|
||||
<DownloadButton
|
||||
onClick={() => download("shapes-with-abigail")}
|
||||
class="bg-green hover:bg-green/90 rounded-md px-6 py-3 font-mono text-base font-semibold shadow-lg transition-all duration-200 ease-out hover:scale-105 active:scale-95"
|
||||
>
|
||||
download.apk
|
||||
</button>
|
||||
</DownloadButton>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
@@ -145,19 +138,16 @@ export default function DownloadsPage() {
|
||||
{/* Cork */}
|
||||
<div class="border-overlay0 rounded-lg border p-6 md:p-8">
|
||||
<h2 class="text-text mb-6 font-mono text-2xl">
|
||||
<span class="text-yellow">{">"}</span> Cork
|
||||
<span class="text-yellow">{">"}</span> {corkText()}
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<span class="text-subtext0 font-mono text-sm">
|
||||
platform: macOS (13+)
|
||||
</span>
|
||||
<button
|
||||
onClick={() => download("cork")}
|
||||
class="bg-green hover:bg-green/90 rounded-md px-6 py-3 font-mono text-base font-semibold shadow-lg transition-all duration-200 ease-out hover:scale-105 active:scale-95"
|
||||
>
|
||||
<DownloadButton onClick={() => download("cork")}>
|
||||
download.zip
|
||||
</button>
|
||||
</DownloadButton>
|
||||
<span class="text-subtext1 text-xs">
|
||||
# unzip → drag to /Applications
|
||||
</span>
|
||||
|
||||
@@ -10,14 +10,14 @@ import { Title, Meta } from "@solidjs/meta";
|
||||
import { getEvent } from "vinxi/http";
|
||||
import GoogleLogo from "~/components/icons/GoogleLogo";
|
||||
import GitHub from "~/components/icons/GitHub";
|
||||
import Eye from "~/components/icons/Eye";
|
||||
import EyeSlash from "~/components/icons/EyeSlash";
|
||||
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import PasswordStrengthMeter from "~/components/PasswordStrengthMeter";
|
||||
import { isValidEmail, validatePassword } from "~/lib/validation";
|
||||
import { getClientCookie } from "~/lib/cookies.client";
|
||||
import { env } from "~/env/client";
|
||||
import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config";
|
||||
import Input from "~/components/ui/Input";
|
||||
import PasswordInput from "~/components/ui/PasswordInput";
|
||||
|
||||
const checkAuth = query(async () => {
|
||||
"use server";
|
||||
@@ -49,8 +49,6 @@ export default function LoginPage() {
|
||||
const [emailSent, setEmailSent] = createSignal(false);
|
||||
const [showPasswordError, setShowPasswordError] = createSignal(false);
|
||||
const [showPasswordSuccess, setShowPasswordSuccess] = createSignal(false);
|
||||
const [showPasswordInput, setShowPasswordInput] = createSignal(false);
|
||||
const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false);
|
||||
const [passwordsMatch, setPasswordsMatch] = createSignal(false);
|
||||
const [password, setPassword] = createSignal("");
|
||||
const [passwordConf, setPasswordConf] = createSignal("");
|
||||
@@ -402,116 +400,47 @@ export default function LoginPage() {
|
||||
|
||||
<form onSubmit={formHandler} class="flex flex-col px-2 py-4">
|
||||
<div class="flex justify-center">
|
||||
<div class="input-group mx-4">
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
ref={emailRef}
|
||||
placeholder=" "
|
||||
title="Please enter a valid email address"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Email</label>
|
||||
</div>
|
||||
<Input
|
||||
type="email"
|
||||
required
|
||||
ref={emailRef}
|
||||
title="Please enter a valid email address"
|
||||
label="Email"
|
||||
containerClass="input-group mx-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Show when={usePassword() || register()}>
|
||||
<div class="-mt-4 flex justify-center">
|
||||
<div class="input-group mx-4 flex">
|
||||
<input
|
||||
type={showPasswordInput() ? "text" : "password"}
|
||||
required
|
||||
minLength={8}
|
||||
ref={passwordRef}
|
||||
onInput={register() ? handlePasswordChange : undefined}
|
||||
placeholder=" "
|
||||
title="Password must be at least 8 characters"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Password</label>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPasswordInput(!showPasswordInput());
|
||||
passwordRef?.focus();
|
||||
}}
|
||||
class="absolute mt-14 ml-60"
|
||||
type="button"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={register()}>
|
||||
<div class="mx-auto flex justify-center px-4 py-2">
|
||||
<PasswordStrengthMeter password={password()} />
|
||||
<PasswordInput
|
||||
required
|
||||
minLength={8}
|
||||
ref={passwordRef}
|
||||
onInput={register() ? handlePasswordChange : undefined}
|
||||
title="Password must be at least 8 characters"
|
||||
label="Password"
|
||||
containerClass="input-group mx-4 flex"
|
||||
showStrength={register()}
|
||||
passwordValue={register() ? password() : undefined}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={register()}>
|
||||
<div class="flex justify-center">
|
||||
<div class="input-group mx-4">
|
||||
<input
|
||||
type={showPasswordConfInput() ? "text" : "password"}
|
||||
required
|
||||
minLength={8}
|
||||
ref={passwordConfRef}
|
||||
onInput={handlePasswordConfChange}
|
||||
placeholder=" "
|
||||
title="Password must be at least 8 characters and match the password above"
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Confirm Password</label>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPasswordConfInput(!showPasswordConfInput());
|
||||
passwordConfRef?.focus();
|
||||
}}
|
||||
class="absolute mt-14 ml-60"
|
||||
type="button"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordConfInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
<PasswordInput
|
||||
required
|
||||
minLength={8}
|
||||
ref={passwordConfRef}
|
||||
onInput={handlePasswordConfChange}
|
||||
title="Password must be at least 8 characters and match the password above"
|
||||
label="Confirm Password"
|
||||
containerClass="input-group mx-4"
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={register()}>
|
||||
<div
|
||||
class={`${
|
||||
!passwordsMatch() &&
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createSignal, createEffect, Show } from "solid-js";
|
||||
import { A, useNavigate, useSearchParams } from "@solidjs/router";
|
||||
import { Title, Meta } from "@solidjs/meta";
|
||||
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import Eye from "~/components/icons/Eye";
|
||||
import EyeSlash from "~/components/icons/EyeSlash";
|
||||
import { validatePassword } from "~/lib/validation";
|
||||
import { api } from "~/lib/api";
|
||||
import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config";
|
||||
import PasswordInput from "~/components/ui/PasswordInput";
|
||||
import PasswordStrengthMeter from "~/components/PasswordStrengthMeter";
|
||||
|
||||
export default function PasswordResetPage() {
|
||||
const navigate = useNavigate();
|
||||
@@ -22,8 +22,7 @@ export default function PasswordResetPage() {
|
||||
const [showRequestNewEmail, setShowRequestNewEmail] = createSignal(false);
|
||||
const [countDown, setCountDown] = createSignal(false);
|
||||
const [error, setError] = createSignal("");
|
||||
const [showPasswordInput, setShowPasswordInput] = createSignal(false);
|
||||
const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false);
|
||||
const [newPassword, setNewPassword] = createSignal("");
|
||||
|
||||
let newPasswordRef: HTMLInputElement | undefined;
|
||||
let newPasswordConfRef: HTMLInputElement | undefined;
|
||||
@@ -121,6 +120,7 @@ export default function PasswordResetPage() {
|
||||
|
||||
const handleNewPasswordChange = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
setNewPassword(target.value);
|
||||
checkPasswordLength(target.value);
|
||||
if (newPasswordConfRef) {
|
||||
checkForMatch(target.value, newPasswordConfRef.value);
|
||||
@@ -169,49 +169,19 @@ export default function PasswordResetPage() {
|
||||
>
|
||||
<div class="flex w-full max-w-md flex-col justify-center px-4">
|
||||
<div class="flex justify-center">
|
||||
<div class="input-group mx-4 flex">
|
||||
<input
|
||||
ref={newPasswordRef}
|
||||
name="newPassword"
|
||||
type={showPasswordInput() ? "text" : "password"}
|
||||
required
|
||||
autofocus
|
||||
onInput={handleNewPasswordChange}
|
||||
onBlur={handlePasswordBlur}
|
||||
disabled={passwordChangeLoading()}
|
||||
placeholder=" "
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">New Password</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowPasswordInput(!showPasswordInput());
|
||||
newPasswordRef?.focus();
|
||||
}}
|
||||
class="absolute mt-14 ml-60"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
<PasswordInput
|
||||
ref={newPasswordRef}
|
||||
name="newPassword"
|
||||
required
|
||||
autofocus
|
||||
onInput={handleNewPasswordChange}
|
||||
onBlur={handlePasswordBlur}
|
||||
disabled={passwordChangeLoading()}
|
||||
label="New Password"
|
||||
containerClass="input-group mx-4 flex"
|
||||
showStrength
|
||||
passwordValue={newPassword()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -224,49 +194,15 @@ export default function PasswordResetPage() {
|
||||
</div>
|
||||
|
||||
<div class="-mt-4 flex justify-center">
|
||||
<div class="input-group mx-4 flex">
|
||||
<input
|
||||
ref={newPasswordConfRef}
|
||||
name="newPasswordConf"
|
||||
onInput={handlePasswordConfChange}
|
||||
type={showPasswordConfInput() ? "text" : "password"}
|
||||
required
|
||||
disabled={passwordChangeLoading()}
|
||||
placeholder=" "
|
||||
class="underlinedInput bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">
|
||||
Password Confirmation
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowPasswordConfInput(!showPasswordConfInput());
|
||||
newPasswordConfRef?.focus();
|
||||
}}
|
||||
class="absolute mt-14 ml-60"
|
||||
>
|
||||
<Show
|
||||
when={showPasswordConfInput()}
|
||||
fallback={
|
||||
<EyeSlash
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Eye
|
||||
height={24}
|
||||
width={24}
|
||||
strokeWidth={1}
|
||||
class="stroke-text"
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
<PasswordInput
|
||||
ref={newPasswordConfRef}
|
||||
name="newPasswordConf"
|
||||
onInput={handlePasswordConfChange}
|
||||
required
|
||||
disabled={passwordChangeLoading()}
|
||||
label="Password Confirmation"
|
||||
containerClass="input-group mx-4 flex"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -5,6 +5,7 @@ import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import { isValidEmail } from "~/lib/validation";
|
||||
import { getClientCookie } from "~/lib/cookies.client";
|
||||
import { COUNTDOWN_CONFIG } from "~/config";
|
||||
import Input from "~/components/ui/Input";
|
||||
|
||||
export default function RequestPasswordResetPage() {
|
||||
const navigate = useNavigate();
|
||||
@@ -136,20 +137,17 @@ export default function RequestPasswordResetPage() {
|
||||
class="mt-4 flex w-full justify-center"
|
||||
>
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="input-group mx-4">
|
||||
<input
|
||||
ref={emailRef}
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
disabled={loading()}
|
||||
placeholder=" "
|
||||
title="Please enter a valid email address"
|
||||
class="underlinedInput w-full bg-transparent"
|
||||
/>
|
||||
<span class="bar"></span>
|
||||
<label class="underlinedInputLabel">Enter Email</label>
|
||||
</div>
|
||||
<Input
|
||||
ref={emailRef}
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
disabled={loading()}
|
||||
title="Please enter a valid email address"
|
||||
label="Enter Email"
|
||||
containerClass="input-group mx-4"
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
<Show
|
||||
when={countDown() > 0}
|
||||
|
||||
Reference in New Issue
Block a user