Fix floating promise lint errors and apply code formatting
- Add void operator to async calls in $effect() blocks to satisfy noFloatingPromises lint rule: - passkey/+page.svelte: void authenticate() - verify/+page.svelte: void verifyEmail() - Apply biome formatter import reorganization across auth files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { AlertCircle } from "@lucide/svelte";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
|
||||
interface Props {
|
||||
message: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { default as ErrorAlert } from "./error-alert.svelte";
|
||||
export { default as PasswordFormField } from "./password-form-field.svelte";
|
||||
export { default as PasswordInput } from "./password-input.svelte";
|
||||
export { default as PasswordStrength } from "./password-strength.svelte";
|
||||
export { default as PasswordFormField } from "./password-form-field.svelte";
|
||||
export { default as ErrorAlert } from "./error-alert.svelte";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import PasswordInput from "./password-input.svelte";
|
||||
import PasswordStrength from "./password-strength.svelte";
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Eye, EyeOff } from "@lucide/svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
|
||||
// Strength labels and colors
|
||||
const strengthConfig = [
|
||||
{ label: "Very weak", color: "bg-destructive", textColor: "text-destructive" },
|
||||
{
|
||||
label: "Very weak",
|
||||
color: "bg-destructive",
|
||||
textColor: "text-destructive",
|
||||
},
|
||||
{ label: "Weak", color: "bg-destructive", textColor: "text-destructive" },
|
||||
{ label: "Fair", color: "bg-warning", textColor: "text-warning" },
|
||||
{ label: "Good", color: "bg-success", textColor: "text-success" },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
|
||||
export const alertVariants = tv({
|
||||
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Root from "./alert.svelte";
|
||||
import Description from "./alert-description.svelte";
|
||||
import Title from "./alert-title.svelte";
|
||||
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
||||
|
||||
export { type AlertVariant, alertVariants } from "./alert.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import Root from "./loading-button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as LoadingButton,
|
||||
};
|
||||
export { Root, Root as LoadingButton };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonProps } from "$lib/components/ui/button";
|
||||
import { Loader2 } from "@lucide/svelte";
|
||||
import type { Snippet } from "svelte";
|
||||
import { Loader2 } from "@lucide/svelte";
|
||||
import { Button, type ButtonProps } from "$lib/components/ui/button";
|
||||
|
||||
interface Props extends ButtonProps {
|
||||
loading?: boolean;
|
||||
|
||||
@@ -61,13 +61,16 @@ export function hasActiveLoginFlow(): boolean {
|
||||
* Get masked email for display (e.g., "j***@example.com")
|
||||
*/
|
||||
export function getMaskedEmail(): string {
|
||||
if (!loginFlowState.email) return "";
|
||||
if (!loginFlowState.email) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const [local, domain] = loginFlowState.email.split("@");
|
||||
if (!domain) return loginFlowState.email;
|
||||
if (!domain) {
|
||||
return loginFlowState.email;
|
||||
}
|
||||
|
||||
const maskedLocal =
|
||||
local.length > 1 ? local[0] + "***" : local + "***";
|
||||
const maskedLocal = local.length > 1 ? `${local[0]}***` : `${local}***`;
|
||||
|
||||
return `${maskedLocal}@${domain}`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { parsePhoneNumberWithError, isValidPhoneNumber } from "libphonenumber-js";
|
||||
import {
|
||||
isValidPhoneNumber,
|
||||
parsePhoneNumberWithError,
|
||||
} from "libphonenumber-js";
|
||||
|
||||
/**
|
||||
* Validates email format using a simple regex pattern.
|
||||
@@ -12,8 +15,13 @@ export function isValidEmail(email: string): boolean {
|
||||
* Validates and formats phone numbers using libphonenumber-js.
|
||||
* Returns validation status and optionally the formatted number.
|
||||
*/
|
||||
export function validatePhone(value: string): { valid: boolean; formatted?: string } {
|
||||
if (!value) return { valid: true };
|
||||
export function validatePhone(value: string): {
|
||||
valid: boolean;
|
||||
formatted?: string;
|
||||
} {
|
||||
if (!value) {
|
||||
return { valid: true };
|
||||
}
|
||||
try {
|
||||
const phone = parsePhoneNumberWithError(value);
|
||||
if (isValidPhoneNumber(phone.number)) {
|
||||
@@ -30,8 +38,11 @@ export function validatePhone(value: string): { valid: boolean; formatted?: stri
|
||||
*/
|
||||
export function maskEmail(email: string): string {
|
||||
const [local, domain] = email.split("@");
|
||||
if (!domain) return email;
|
||||
const masked = local.length > 1
|
||||
if (!domain) {
|
||||
return email;
|
||||
}
|
||||
const masked =
|
||||
local.length > 1
|
||||
? `${local[0]}${"*".repeat(Math.min(local.length - 1, 3))}`
|
||||
: local;
|
||||
return `${masked}@${domain}`;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { AlertCircle, Loader2, Mail, RefreshCw } from "@lucide/svelte";
|
||||
import { createQuery } from "@tanstack/svelte-query";
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import {
|
||||
loginFlowState,
|
||||
clearLoginFlowState,
|
||||
getMaskedEmail,
|
||||
} from "$lib/stores/auth.svelte";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { Loader2, Mail, AlertCircle, RefreshCw } from "@lucide/svelte";
|
||||
import {
|
||||
clearLoginFlowState,
|
||||
getMaskedEmail,
|
||||
loginFlowState,
|
||||
} from "$lib/stores/auth.svelte";
|
||||
|
||||
let resendCooldown = $state(0);
|
||||
let isResending = $state(false);
|
||||
@@ -46,7 +46,7 @@
|
||||
$effect(() => {
|
||||
if (resendCooldown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
resendCooldown = resendCooldown - 1;
|
||||
resendCooldown -= 1;
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { CheckCircle2 } from "@lucide/svelte";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
@@ -6,7 +7,6 @@
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { CheckCircle2 } from "@lucide/svelte";
|
||||
|
||||
// Form state
|
||||
let email = $state("");
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
if (!isFormValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
error = "";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { setLoginFlowState } from "$lib/stores/auth.svelte";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { setLoginFlowState } from "$lib/stores/auth.svelte";
|
||||
|
||||
let email = $state("");
|
||||
let isLoading = $state(false);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { Fingerprint, KeyRound, Loader2 } from "@lucide/svelte";
|
||||
import { startAuthentication } from "@simplewebauthn/browser";
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { api } from "$lib/api/client";
|
||||
import { loginFlowState, getMaskedEmail } from "$lib/stores/auth.svelte";
|
||||
import { Fingerprint, KeyRound, Loader2 } from "@lucide/svelte";
|
||||
import { getMaskedEmail, loginFlowState } from "$lib/stores/auth.svelte";
|
||||
|
||||
/**
|
||||
* Passkey authentication page
|
||||
@@ -32,7 +32,9 @@ async function authenticate(): Promise<void> {
|
||||
const options = await api.auth.webauthn.createAuthenticationOptions();
|
||||
|
||||
// Trigger browser WebAuthn prompt
|
||||
const credential = await startAuthentication({ optionsJSON: options.options });
|
||||
const credential = await startAuthentication({
|
||||
optionsJSON: options.options,
|
||||
});
|
||||
|
||||
// Verify with server
|
||||
await api.auth.webauthn.verifyAuthentication({
|
||||
@@ -60,7 +62,7 @@ $effect(() => {
|
||||
// Auto-trigger authentication on mount
|
||||
$effect(() => {
|
||||
if (loginFlowState.email && !hasAttempted) {
|
||||
authenticate();
|
||||
void authenticate();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { loginFlowState, clearLoginFlowState } from "$lib/stores/auth.svelte";
|
||||
import { ErrorAlert, PasswordInput } from "$lib/components/auth";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { PasswordInput, ErrorAlert } from "$lib/components/auth";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { clearLoginFlowState, loginFlowState } from "$lib/stores/auth.svelte";
|
||||
|
||||
let password = $state("");
|
||||
let isLoading = $state(false);
|
||||
@@ -28,7 +28,10 @@
|
||||
// On success, redirect to confirm page for email verification
|
||||
goto("/auth/confirm");
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : "Invalid password. Please try again.";
|
||||
error =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Invalid password. Please try again.";
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { AlertCircle } from "@lucide/svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import zxcvbn from "zxcvbn";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import { api } from "$lib/api/client";
|
||||
import { PasswordInput, PasswordStrength, ErrorAlert } from "$lib/components/auth";
|
||||
import {
|
||||
ErrorAlert,
|
||||
PasswordInput,
|
||||
PasswordStrength,
|
||||
} from "$lib/components/auth";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { AlertCircle } from "@lucide/svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
// Get token from URL
|
||||
const token = $derived($page.url.searchParams.get("token") ?? "");
|
||||
@@ -24,7 +28,10 @@
|
||||
const passwordScore = $derived(newPassword ? zxcvbn(newPassword).score : 0);
|
||||
const passwordsMatch = $derived(newPassword === confirmPassword);
|
||||
const isPasswordValid = $derived(
|
||||
newPassword.length >= 8 && passwordScore >= 3 && passwordsMatch && confirmPassword.length > 0
|
||||
newPassword.length >= 8 &&
|
||||
passwordScore >= 3 &&
|
||||
passwordsMatch &&
|
||||
confirmPassword.length > 0,
|
||||
);
|
||||
|
||||
// Form validation
|
||||
@@ -32,7 +39,9 @@
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
if (!isFormValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
error = "";
|
||||
@@ -52,7 +61,8 @@
|
||||
if (err instanceof Error) {
|
||||
// Handle specific error cases
|
||||
if (err.message.includes("expired") || err.message.includes("invalid")) {
|
||||
error = "This password reset link has expired or is invalid. Please request a new one.";
|
||||
error =
|
||||
"This password reset link has expired or is invalid. Please request a new one.";
|
||||
} else {
|
||||
error = err.message;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { AlertCircle, Loader2 } from "@lucide/svelte";
|
||||
import { createQuery } from "@tanstack/svelte-query";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Alert, AlertDescription } from "$lib/components/ui/alert";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { validatePhone } from "$lib/utils/validation";
|
||||
|
||||
// Fetch current user to check if setup is needed
|
||||
@@ -48,12 +48,14 @@
|
||||
const isValid = $derived(
|
||||
displayName.trim().length >= 1 &&
|
||||
displayName.trim().length <= 100 &&
|
||||
(!phoneNumber || validatePhone(phoneNumber).valid)
|
||||
(!phoneNumber || validatePhone(phoneNumber).valid),
|
||||
);
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!isValid || isSubmitting) return;
|
||||
if (!isValid || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting = true;
|
||||
error = "";
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
browserSupportsWebAuthn,
|
||||
startRegistration,
|
||||
} from "@simplewebauthn/browser";
|
||||
import zxcvbn from "zxcvbn";
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { PasswordInput, PasswordStrength, ErrorAlert } from "$lib/components/auth";
|
||||
import {
|
||||
ErrorAlert,
|
||||
PasswordInput,
|
||||
PasswordStrength,
|
||||
} from "$lib/components/auth";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { browserSupportsWebAuthn, startRegistration } from "@simplewebauthn/browser";
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
// Form state
|
||||
let email = $state("");
|
||||
@@ -31,14 +38,17 @@
|
||||
const passwordScore = $derived(password ? zxcvbn(password, [email]).score : 0);
|
||||
const passwordsMatch = $derived(password === confirmPassword);
|
||||
const isPasswordValid = $derived(
|
||||
password.length >= 8 && passwordScore >= 3 && passwordsMatch && confirmPassword.length > 0
|
||||
password.length >= 8 &&
|
||||
passwordScore >= 3 &&
|
||||
passwordsMatch &&
|
||||
confirmPassword.length > 0,
|
||||
);
|
||||
|
||||
// Form validation
|
||||
const isFormValid = $derived(
|
||||
authMode === "passkey"
|
||||
? email.length > 0 && email.includes("@")
|
||||
: email.length > 0 && email.includes("@") && isPasswordValid
|
||||
: email.length > 0 && email.includes("@") && isPasswordValid,
|
||||
);
|
||||
|
||||
async function handlePasskeySignup() {
|
||||
@@ -47,10 +57,13 @@
|
||||
|
||||
try {
|
||||
// Step 1: Get registration options from server
|
||||
const { challengeId, options } = await api.auth.webauthn.createRegistrationOptions({ email });
|
||||
const { challengeId, options } =
|
||||
await api.auth.webauthn.createRegistrationOptions({ email });
|
||||
|
||||
// Step 2: Start WebAuthn registration
|
||||
const registrationResponse = await startRegistration({ optionsJSON: options });
|
||||
const registrationResponse = await startRegistration({
|
||||
optionsJSON: options,
|
||||
});
|
||||
|
||||
// Step 3: Complete signup with passkey info
|
||||
await api.auth.signup({
|
||||
@@ -104,7 +117,9 @@
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
if (!isFormValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (authMode === "passkey") {
|
||||
await handlePasskeySignup();
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { MapPin, Monitor, Shield, Smartphone, Tablet } from "@lucide/svelte";
|
||||
import { createQuery } from "@tanstack/svelte-query";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { goto } from "$app/navigation";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
@@ -6,10 +10,6 @@
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { LoadingButton } from "$lib/components/ui/loading-button";
|
||||
import { Monitor, Smartphone, Tablet, Shield, MapPin } from "@lucide/svelte";
|
||||
import { createQuery } from "@tanstack/svelte-query";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
|
||||
// Fetch device info from server
|
||||
// TanStack Query v6 with Svelte 5: options passed as thunk, results accessed directly
|
||||
@@ -37,10 +37,14 @@
|
||||
}
|
||||
});
|
||||
|
||||
const isValid = $derived(deviceName.trim().length >= 1 && deviceName.trim().length <= 100);
|
||||
const isValid = $derived(
|
||||
deviceName.trim().length >= 1 && deviceName.trim().length <= 100,
|
||||
);
|
||||
|
||||
async function handleTrust() {
|
||||
if (!isValid || isSubmitting) return;
|
||||
if (!isValid || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting = true;
|
||||
error = "";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import { goto } from "$app/navigation";
|
||||
import { CheckCircle2, Loader2, Mail, XCircle } from "@lucide/svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { api } from "$lib/api/client";
|
||||
import { ErrorAlert } from "$lib/components/auth";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { api } from "$lib/api/client";
|
||||
import { Mail, XCircle, CheckCircle2, Loader2 } from "@lucide/svelte";
|
||||
|
||||
/**
|
||||
* Email verification callback page
|
||||
@@ -42,7 +42,7 @@ async function verifyEmail(): Promise<void> {
|
||||
// Auto-verify on mount
|
||||
$effect(() => {
|
||||
if (token) {
|
||||
verifyEmail();
|
||||
void verifyEmail();
|
||||
} else {
|
||||
error = "No verification token provided";
|
||||
isVerifying = false;
|
||||
@@ -58,7 +58,8 @@ async function resendVerification(): Promise<void> {
|
||||
toast.success("Verification email sent! Check your inbox.");
|
||||
error = "";
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : "Failed to send verification email";
|
||||
const message =
|
||||
e instanceof Error ? e.message : "Failed to send verification email";
|
||||
toast.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user