update pay function

This commit is contained in:
2025-11-22 08:57:37 +08:00
parent 295f179147
commit 43446f8034
210 changed files with 8236 additions and 5345 deletions

View File

@@ -0,0 +1,63 @@
import { useState } from "react";
import Switch from "@/components/Switch";
import Button from "@/components/Button";
const DataControls = ({}) => {
const [improve, setImprove] = useState(true);
return (
<div className="">
<div className="flex justify-between gap-6 mb-3 pb-3 border-b border-stroke-soft-200">
<div className="max-w-101">
<div className="text-label-md">
Improve the model for everyone
</div>
<div className="text-sub-600">
Allow your content to be used to train our models, which
makes AI better for you and everyone who uses it. We
take steps to protect your privacy.{" "}
<button className="underline text-blue-500 hover:no-underline">
Learn more
</button>
</div>
</div>
<Switch checked={improve} onChange={setImprove} isSmall />
</div>
<div className="flex justify-between items-center gap-6 mb-3 pb-3 border-b border-stroke-soft-200">
<div className="text-label-md">Export Data</div>
<Button
className="!h-10 !rounded-[0.625rem] !bg-weak-50"
isStroke
>
Export
</Button>
</div>
<div className="flex justify-between items-center gap-6 mb-3 pb-3 border-b border-stroke-soft-200">
<div className="text-label-md">Shared Links</div>
<Button
className="!h-10 !rounded-[0.625rem] !bg-weak-50"
isStroke
>
Manage
</Button>
</div>
<div className="flex justify-between items-center gap-6 mb-3 pb-3 border-b border-stroke-soft-200">
<div className="text-label-md">Archive all chats</div>
<Button
className="!h-10 !rounded-[0.625rem] !bg-weak-50"
isStroke
>
Archive all
</Button>
</div>
<div className="flex justify-between items-center gap-6">
<div className="text-label-md">Delete Account</div>
<Button className="!h-10 !rounded-[0.625rem]" isRed>
Delete Account
</Button>
</div>
</div>
);
};
export default DataControls;

View File

@@ -0,0 +1,76 @@
import { useState, useRef } from "react";
import Image from "@/components/Image";
import Button from "@/components/Button";
import Icon from "@/components/Icon";
const UploadImage = ({}) => {
const [preview, setPreview] = useState<string | null>(
"/images/avatar-1.png"
);
const inputRef = useRef<HTMLInputElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const objectUrl = URL.createObjectURL(file);
setPreview(objectUrl);
}
};
const handleRemove = () => {
setPreview(null);
};
return (
<div className="">
<div className="flex items-center gap-3">
<div className="relative flex justify-center items-center bg-soft-200 size-11.5 rounded-full overflow-hidden">
{preview ? (
<Image
className="size-full opacity-100"
src={preview}
width={48}
height={48}
alt="avatar"
/>
) : (
<Icon
className="size-6 fill-strong-950"
name="profile"
/>
)}
</div>
<div className="relative">
<input
className="absolute inset-0 opacity-0 cursor-pointer z-10 object-cover"
ref={inputRef}
type="file"
onChange={handleChange}
accept="image/*"
/>
<Button className="!h-9 rounded-lg" isStroke>
Upload image
</Button>
</div>
<Button
className="!w-9 !h-9 !px-0 rounded-lg"
isStroke
onClick={handleRemove}
>
<Image
className="size-6 opacity-100"
src="/images/trash.svg"
width={24}
height={24}
alt=""
/>
</Button>
</div>
<div className="mt-1.5 text-soft-400 max-md:text-p-xs">
We only support JPG, JPEG, or ,PNG file. 1MB max.
</div>
</div>
);
};
export default UploadImage;

View File

@@ -0,0 +1,80 @@
import { useState } from "react";
import Field from "@/components/Field";
import Icon from "@/components/Icon";
import Button from "@/components/Button";
import UploadImage from "./UploadImage";
const General = ({}) => {
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
return (
<div className="-mt-5 max-md:mt-0">
<div className="flex items-center mb-3 pb-3 border-b border-stroke-soft-200 max-md:flex-col max-md:items-start max-md:gap-3">
<div className="mr-auto">
<div className="text-label-md">Avatar</div>
<div className="text-sub-600">Update full name</div>
</div>
<UploadImage />
</div>
<div className="mb-3 pb-3 border-b border-stroke-soft-200">
<div className="mb-3">
<div className="text-label-md">Personal Information</div>
<div className="text-sub-600">
Edit your personal information
</div>
</div>
<Field
className="mb-3"
label="Full name"
placeholder="Enter full name"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
isSmall
/>
<Field
className="mb-1"
label="Email"
placeholder="Enter email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
isSmall
/>
<button className="inline-flex items-center gap-2 text-label-xs text-sub-600 transition-opacity hover:opacity-80">
<Icon className="!size-4" name="plus" />
Add another
</button>
</div>
<div className="mb-3">
<div className="mb-3">
<div className="text-label-md">Phone number</div>
<div className="text-sub-600">Update your phone number</div>
</div>
<Field
className=""
label="Phone number"
placeholder="Enter phone number"
type="tel"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
required
isSmall
/>
</div>
<div className="flex justify-end gap-3">
<Button className="!h-10 !px-4.5 !bg-weak-50" isStroke>
Discard
</Button>
<Button className="!h-10 !px-4.5" isBlack>
Save Changes
</Button>
</div>
</div>
);
};
export default General;

View File

@@ -0,0 +1,34 @@
import { useState } from "react";
import Switch from "@/components/Switch";
const Speech = ({}) => {
const [responses, setResponses] = useState(true);
const [push, setPush] = useState(true);
const [email, setEmail] = useState(false);
return (
<div className="">
<div className="flex items-center mb-3 pb-3 border-b border-stroke-soft-200">
<div className="mr-auto">
<div className="text-label-md">Responses</div>
<div className="text-sub-600">Ai response Push</div>
</div>
<Switch checked={responses} onChange={setResponses} isSmall />
</div>
<div className="flex items-center mb-3 pb-3 border-b border-stroke-soft-200">
<div className="mr-auto">
<div className="text-label-md">Push</div>
</div>
<Switch checked={push} onChange={setPush} isSmall />
</div>
<div className="flex items-center mb-3 pb-3 border-b border-stroke-soft-200">
<div className="mr-auto">
<div className="text-label-md">Email</div>
</div>
<Switch checked={email} onChange={setEmail} isSmall />
</div>
</div>
);
};
export default Speech;

View File

@@ -0,0 +1,47 @@
import { useState } from "react";
import Switch from "@/components/Switch";
import Button from "@/components/Button";
const Security = ({}) => {
const [authentication, setAuthentication] = useState(true);
return (
<div className="">
<div className="flex justify-between gap-6 mb-3 pb-3 border-b border-stroke-soft-200">
<div className="max-w-101">
<div className="text-label-md">
Mlti-factor authentication
</div>
<div className="text-sub-600">
Require an extra security challenge when logging in. If
you are unable to pass this challenge, you will have the
option to recover your account via email.
</div>
</div>
<Switch
checked={authentication}
onChange={setAuthentication}
isSmall
/>
</div>
<div className="flex justify-between gap-6 max-md:flex-col max-md:gap-3">
<div className="max-w-101">
<div className="text-label-md">Log out of all devices</div>
<div className="text-sub-600">
Log out of all active sessions across all devices,
including your current session. It may take up to 30
minutes for other devices to be logged out.
</div>
</div>
<Button
className="shrink-0 !h-10 !rounded-[0.625rem] !bg-weak-50"
isStroke
>
Log out all
</Button>
</div>
</div>
);
};
export default Security;

View File

@@ -0,0 +1,62 @@
import { useState } from "react";
import Button from "@/components/Button";
import Icon from "@/components/Icon";
import Select from "@/components/Select";
const voiceOptions = [
{ id: 1, name: "Default" },
{ id: 2, name: "Echo" },
{ id: 3, name: "Tempo" },
];
const languages = [
{ id: 1, name: "English" },
{ id: 2, name: "French" },
{ id: 3, name: "Spanish" },
];
const Notifications = ({}) => {
const [voice, setVoice] = useState(voiceOptions[0]);
const [language, setLanguage] = useState(languages[0]);
return (
<div className="">
<div className="flex items-center mb-3 pb-3 border-b border-stroke-soft-200">
<div className="mr-auto">
<div className="text-label-md">Voice</div>
<div className="text-sub-600">Choose your ai voice.</div>
</div>
<div className="flex gap-1.5">
<Button
className="w-10 !h-10 !px-0 !rounded-[0.625rem] !bg-weak-50"
isStroke
>
<Icon className="fill-icon-sub-600" name="volume" />
</Button>
<Select
classButton="h-10 bg-weak-50 rounded-[0.625rem]"
value={voice}
onChange={setVoice}
options={voiceOptions}
/>
</div>
</div>
<div className="flex items-center gap-4">
<div className="mr-auto">
<div className="text-label-md">Main Language</div>
<div className="text-sub-600">
For best results, select the language you mainly speak.
</div>
</div>
<Select
classButton="h-10 bg-weak-50 rounded-[0.625rem]"
value={language}
onChange={setLanguage}
options={languages}
/>
</div>
</div>
);
};
export default Notifications;

View File

@@ -0,0 +1,97 @@
import { useTheme } from "next-themes";
import Image from "@/components/Image";
const Theme = ({}) => {
const { theme, setTheme } = useTheme();
const themes = [
{
id: 1,
name: "Light mode",
image: "/images/theme-light.png",
value: "light",
},
{
id: 2,
name: "Dark mode",
image: "/images/theme-dark.png",
value: "dark",
},
{
id: 3,
name: "System Preference",
image: "/images/theme-preference.png",
value: "system",
},
];
return (
<div className="">
<div className="text-label-md">Themes</div>
<div className="mb-3 text-sub-600">
Choose your style or customize your theme
</div>
<div className="flex gap-3 max-md:flex-col">
{themes.map((button) => (
<div
className={`flex flex-col flex-1 border rounded-lg bg-white-0 overflow-hidden cursor-pointer transition-colors hover:border-blue-500 ${
theme === button.value
? "border-blue-500 dark:text-static-black"
: "border-stroke-soft-200"
}`}
key={button.id}
onClick={() => setTheme(button.value)}
>
<div className="p-2.5 py-4">
<div className="border border-stroke-soft-200 rounded-lg overflow-hidden">
<Image
className="w-full opacity-100"
src={button.image}
width={152}
height={95}
alt=""
/>
</div>
</div>
<div
className={`flex items-center grow gap-2.5 h-11 px-3 transition-colors ${
theme === button.value
? "bg-blue-50"
: "bg-weak-50"
}`}
>
<div
className={`flex justify-center items-center size-4.5 rounded-full border ${
theme === button.value
? "border-blue-500 bg-blue-500"
: "border-stroke-soft-200 bg-white-0"
}`}
>
<svg
className={`fill-static-white transition-opacity ${
theme === button.value
? "opacity-100"
: "opacity-0"
}`}
xmlns="http://www.w3.org/2000/svg"
width="9"
height="8"
fill="none"
viewBox="0 0 9 8"
>
<path
fillRule="evenodd"
d="M8.252 2.16a.79.79 0 1 0-1.167-1.07l-3.795 4.14-1.394-1.394a.79.79 0 1 0-1.12 1.12l1.979 1.979a.79.79 0 0 0 1.143-.025l4.354-4.75z"
/>
</svg>
</div>
<div className="text-label-sm">{button.name}</div>
</div>
</div>
))}
</div>
</div>
);
};
export default Theme;

View File

@@ -0,0 +1,90 @@
import { useState } from "react";
import Modal from "@/components/Modal";
import Icon from "@/components/Icon";
import General from "./General";
import Notifications from "./Notifications";
import Speech from "./Speech";
import Theme from "./Theme";
import Security from "./Security";
import DataControls from "./DataControls";
import { menu } from "./menu";
type Props = {
open: boolean;
onClose: () => void;
};
const Settings = ({ open, onClose }: Props) => {
const [activeId, setActiveId] = useState(0);
return (
<>
<Modal
classWrapper="flex flex-col max-w-199.5 min-h-180 max-md:min-h-[calc(100svh-6rem)]"
open={open}
onClose={onClose}
>
<div className="mb-4.5 pb-2 border-b border-stroke-soft-200 max-md:mb-3">
<div className="text-label-xl max-md:text-label-lg">
Settings
</div>
<div className="mt-2 text-label-md text-sub-600 max-md:hidden">
People with link will be able to view conversations and
ideas in this board.Changes you make after creating the
link will remain private.
</div>
</div>
<div className="flex grow max-md:block">
<div className="flex flex-col gap-2 shrink-0 w-50 pr-4 border-r border-stroke-soft-200 max-lg:w-40 max-md:flex-row max-md:gap-4 max-md:w-auto max-md:mb-4 max-md:overflow-auto max-md:scrollbar-none max-md:-mx-4 max-md:px-4 max-md:border-0">
{menu.map((item) => (
<button
className={`group flex items-center gap-2 h-10 transition-colors hover:text-strong-950 max-md:shrink-0 ${
activeId === item.id
? "!text-blue-500"
: "text-sub-600"
}`}
onClick={() => setActiveId(item.id)}
key={item.id}
>
<Icon
className={`transition-colors group-hover:fill-strong-950 ${
activeId === item.id
? "!fill-blue-500"
: "fill-sub-600"
}`}
name={item.icon}
/>
{item.name}
</button>
))}
</div>
<div className="grow pl-4 max-md:pl-0">
{menu
.filter((item) => item.id === activeId)
.map((item) => (
<div
className="flex items-center gap-2.5 mb-8 text-label-lg max-md:hidden"
key={item.id}
>
<Icon
className="!size-7 fill-strong-950"
name={item.icon}
/>
{item.name}
</div>
))}
{activeId === 0 && <General />}
{activeId === 1 && <Notifications />}
{activeId === 2 && <Speech />}
{activeId === 3 && <Theme />}
{activeId === 4 && <Security />}
{activeId === 5 && <DataControls />}
</div>
</div>
</Modal>
</>
);
};
export default Settings;

View File

@@ -0,0 +1,32 @@
export const menu = [
{
id: 0,
name: "General",
icon: "folder",
},
{
id: 1,
name: "Notifications",
icon: "bell",
},
{
id: 2,
name: "Speech",
icon: "voice",
},
{
id: 3,
name: "Theme",
icon: "document",
},
{
id: 4,
name: "Security",
icon: "security",
},
{
id: 5,
name: "Data Controls",
icon: "database",
},
];