Remove redundant files
This commit is contained in:
@@ -1,69 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
|
||||||
import { Button, Card, Flex, Popover, Space, Tooltip, Typography } from "antd";
|
|
||||||
import AccountSettingsPopoverContent from "./AccountSettingsPopoverContent";
|
|
||||||
import ThemeModeToggle from "./utils/ThemeModeToggle";
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function TopNav({
|
|
||||||
themeMode,
|
|
||||||
onThemeChange,
|
|
||||||
onHome,
|
|
||||||
userEmail,
|
|
||||||
onUserEmailChange,
|
|
||||||
}: {
|
|
||||||
themeMode: "light" | "dark";
|
|
||||||
onThemeChange: (mode: "light" | "dark") => void;
|
|
||||||
onHome: () => void;
|
|
||||||
userEmail: string;
|
|
||||||
onUserEmailChange: (nextUserEmail: string) => void | Promise<string>;
|
|
||||||
}) {
|
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card size="small">
|
|
||||||
<Flex justify="space-between" align="center" wrap="wrap" gap={12}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
onClick={onHome}
|
|
||||||
aria-label="Go to home"
|
|
||||||
style={{ paddingInline: 0 }}
|
|
||||||
>
|
|
||||||
<Text style={{ fontSize: 18 }} strong>
|
|
||||||
Lunchtime
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Space size={8} align="center">
|
|
||||||
<ThemeModeToggle
|
|
||||||
themeMode={themeMode}
|
|
||||||
onThemeChange={onThemeChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Popover
|
|
||||||
trigger="click"
|
|
||||||
placement="bottomRight"
|
|
||||||
open={isPopoverOpen}
|
|
||||||
onOpenChange={setIsPopoverOpen}
|
|
||||||
content={
|
|
||||||
<AccountSettingsPopoverContent
|
|
||||||
userEmail={userEmail}
|
|
||||||
onUserEmailChange={onUserEmailChange}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Tooltip title="Account settings">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
shape="circle"
|
|
||||||
icon={<UserOutlined />}
|
|
||||||
aria-label="User settings"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Popover>
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import { Alert, Button, Flex, Form, Input, Modal, Space, Typography } from "antd";
|
|
||||||
import ThemeModeToggle from "./utils/ThemeModeToggle";
|
|
||||||
|
|
||||||
type ThemeMode = "light" | "dark";
|
|
||||||
type EmailLookupState = "idle" | "checking" | "exists" | "new";
|
|
||||||
|
|
||||||
export default function WelcomeOnboardingModal({
|
|
||||||
open,
|
|
||||||
themeMode,
|
|
||||||
onThemeChange,
|
|
||||||
onCheckAccountExists,
|
|
||||||
onCreateAccount,
|
|
||||||
onMigrateAccount,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
themeMode: ThemeMode;
|
|
||||||
onThemeChange: (mode: ThemeMode) => void;
|
|
||||||
onCheckAccountExists: (email: string) => Promise<boolean>;
|
|
||||||
onCreateAccount: (email: string) => void | Promise<void>;
|
|
||||||
onMigrateAccount: (email: string) => void | Promise<void>;
|
|
||||||
}) {
|
|
||||||
const [form] = Form.useForm<{ email: string }>();
|
|
||||||
const [lookupState, setLookupState] = useState<EmailLookupState>("idle");
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [hasSentEmail, setHasSentEmail] = useState(false);
|
|
||||||
const lookupTimerRef = useRef<number | null>(null);
|
|
||||||
const lookupRequestIdRef = useRef(0);
|
|
||||||
|
|
||||||
const normalizeEmail = (email: string) => email.trim().toLowerCase();
|
|
||||||
|
|
||||||
const isValidEmail = (email: string) =>
|
|
||||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
||||||
|
|
||||||
const resolveAccountExists = async (email: string) => {
|
|
||||||
const normalized = normalizeEmail(email);
|
|
||||||
if (!normalized || !isValidEmail(normalized)) {
|
|
||||||
setLookupState("idle");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestId = ++lookupRequestIdRef.current;
|
|
||||||
setLookupState("checking");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const exists = await onCheckAccountExists(normalized);
|
|
||||||
if (requestId !== lookupRequestIdRef.current) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLookupState(exists ? "exists" : "new");
|
|
||||||
return exists;
|
|
||||||
} catch {
|
|
||||||
if (requestId === lookupRequestIdRef.current) {
|
|
||||||
setLookupState("idle");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
form.resetFields();
|
|
||||||
setLookupState("idle");
|
|
||||||
setIsSubmitting(false);
|
|
||||||
setHasSentEmail(false);
|
|
||||||
lookupRequestIdRef.current += 1;
|
|
||||||
if (lookupTimerRef.current) {
|
|
||||||
window.clearTimeout(lookupTimerRef.current);
|
|
||||||
lookupTimerRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [form, open]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
if (lookupTimerRef.current) {
|
|
||||||
window.clearTimeout(lookupTimerRef.current);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttonLabel = useMemo(() => {
|
|
||||||
if (hasSentEmail) {
|
|
||||||
return "Email sent";
|
|
||||||
}
|
|
||||||
if (lookupState === "exists") {
|
|
||||||
return "Migrate account";
|
|
||||||
}
|
|
||||||
if (lookupState === "checking") {
|
|
||||||
return "Checking account...";
|
|
||||||
}
|
|
||||||
if (lookupState === "new") {
|
|
||||||
return "Create new account";
|
|
||||||
}
|
|
||||||
return "Continue";
|
|
||||||
}, [hasSentEmail, lookupState]);
|
|
||||||
|
|
||||||
const isLookupResolved = lookupState === "exists" || lookupState === "new";
|
|
||||||
|
|
||||||
const helperText = useMemo(() => {
|
|
||||||
if (lookupState === "exists") {
|
|
||||||
return "Log in using this email.";
|
|
||||||
}
|
|
||||||
if (lookupState === "new") {
|
|
||||||
return "Register using this email.";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}, [lookupState]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
maskClosable={false}
|
|
||||||
keyboard={false}
|
|
||||||
closable={false}
|
|
||||||
footer={null}
|
|
||||||
centered
|
|
||||||
>
|
|
||||||
<Space direction="vertical" size={14} style={{ width: "100%" }}>
|
|
||||||
<Flex align="center" justify="space-between" style={{ width: "100%" }}>
|
|
||||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
|
||||||
Welcome to Lunchtime
|
|
||||||
</Typography.Title>
|
|
||||||
<ThemeModeToggle themeMode={themeMode} onThemeChange={onThemeChange} />
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Typography.Paragraph style={{ marginBottom: 6 }}>
|
|
||||||
Enter your account email to continue. We'll automatically detect whether to create a new account or migrate an existing one.
|
|
||||||
</Typography.Paragraph>
|
|
||||||
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
onValuesChange={(_changedValues, values) => {
|
|
||||||
if (hasSentEmail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentEmail = String(values.email || "");
|
|
||||||
if (lookupTimerRef.current) {
|
|
||||||
window.clearTimeout(lookupTimerRef.current);
|
|
||||||
lookupTimerRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
lookupTimerRef.current = window.setTimeout(() => {
|
|
||||||
void resolveAccountExists(currentEmail);
|
|
||||||
}, 350);
|
|
||||||
}}
|
|
||||||
onFinish={async (values) => {
|
|
||||||
if (hasSentEmail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedEmail = normalizeEmail(values.email);
|
|
||||||
setIsSubmitting(true);
|
|
||||||
try {
|
|
||||||
let exists = lookupState === "exists";
|
|
||||||
if (lookupState !== "exists" && lookupState !== "new") {
|
|
||||||
const checked = await resolveAccountExists(normalizedEmail);
|
|
||||||
exists = checked === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
await onMigrateAccount(normalizedEmail);
|
|
||||||
} else {
|
|
||||||
await onCreateAccount(normalizedEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHasSentEmail(true);
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label="Email"
|
|
||||||
name="email"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: "Email cannot be empty." },
|
|
||||||
{ type: "email", message: "Enter a valid email address." },
|
|
||||||
]}
|
|
||||||
extra={helperText || undefined}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="alex@example.com"
|
|
||||||
autoFocus
|
|
||||||
maxLength={320}
|
|
||||||
disabled={hasSentEmail}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{hasSentEmail ? (
|
|
||||||
<Alert
|
|
||||||
type="info"
|
|
||||||
showIcon
|
|
||||||
description="Email sent! Please check your inbox and spam folder for the confirmation email."
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{hasSentEmail ? (
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
onClick={() => {
|
|
||||||
setHasSentEmail(false);
|
|
||||||
setLookupState("idle");
|
|
||||||
}}
|
|
||||||
style={{ paddingInline: 0 }}
|
|
||||||
>
|
|
||||||
Change email
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
block
|
|
||||||
loading={!hasSentEmail && (isSubmitting || lookupState === "checking")}
|
|
||||||
disabled={hasSentEmail || !isLookupResolved}
|
|
||||||
>
|
|
||||||
{buttonLabel}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</Space>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user