366 lines
13 KiB
TypeScript
366 lines
13 KiB
TypeScript
"use client";
|
||
|
||
import { ChangeEvent, FormEvent, useRef, useState } from "react";
|
||
import ClothesShell from "@/components/clothes/ClothesShell";
|
||
import ImageCropper from "@/components/ui/ImageCropper";
|
||
|
||
type AddressItem = {
|
||
id: string;
|
||
label: string;
|
||
province: string;
|
||
city: string;
|
||
fullAddress: string;
|
||
landline: string;
|
||
};
|
||
|
||
const DEFAULT_AVATAR = "/images/ryanwar-hanif-98XENwtPMCc-unsplash.jpg";
|
||
|
||
const provinces = [
|
||
"تهران",
|
||
"اصفهان",
|
||
"فارس",
|
||
"خراسان رضوی",
|
||
"آذربایجان شرقی",
|
||
"آذربایجان غربی",
|
||
"گیلان",
|
||
"مازندران",
|
||
"خوزستان",
|
||
"کرمان",
|
||
"یزد",
|
||
"قم",
|
||
"البرز",
|
||
"همدان",
|
||
"کردستان",
|
||
"کرمانشاه",
|
||
"هرمزگان",
|
||
"بوشهر",
|
||
"سیستان و بلوچستان",
|
||
"گلستان",
|
||
"قزوین",
|
||
"زنجان",
|
||
"اردبیل",
|
||
"لرستان",
|
||
"مرکزی",
|
||
"سمنان",
|
||
"چهارمحال و بختیاری",
|
||
"کهگیلویه و بویراحمد",
|
||
"ایلام",
|
||
"خراسان شمالی",
|
||
"خراسان جنوبی",
|
||
];
|
||
|
||
function emptyAddress(): AddressItem {
|
||
return {
|
||
id: `addr-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||
label: "",
|
||
province: "",
|
||
city: "",
|
||
fullAddress: "",
|
||
landline: "",
|
||
};
|
||
}
|
||
|
||
export default function ProfilePage() {
|
||
const fileRef = useRef<HTMLInputElement>(null);
|
||
const [avatar, setAvatar] = useState(DEFAULT_AVATAR);
|
||
const [nameFa, setNameFa] = useState("سارا محمدی");
|
||
const [nameEn, setNameEn] = useState("Sara Mohammadi");
|
||
const [phone] = useState("09121234567");
|
||
const [email, setEmail] = useState("sara@manacollection.com");
|
||
const [instagram, setInstagram] = useState("@manacollection");
|
||
const [about, setAbout] = useState(
|
||
"علاقهمند به طراحی مینیمال و استایلهای آرام روزمره.",
|
||
);
|
||
const [addresses, setAddresses] = useState<AddressItem[]>([
|
||
{
|
||
id: "addr-1",
|
||
label: "منزل",
|
||
province: "تهران",
|
||
city: "تهران",
|
||
fullAddress: "خیابان ولیعصر، بالاتر از پارک وی، پلاک ۱۲",
|
||
landline: "02122001122",
|
||
},
|
||
]);
|
||
|
||
function handleAvatarChange(event: ChangeEvent<HTMLInputElement>) {
|
||
const file = event.target.files?.[0];
|
||
if (!file) return;
|
||
setAvatar(URL.createObjectURL(file));
|
||
}
|
||
|
||
function updateAddress(
|
||
id: string,
|
||
field: keyof Omit<AddressItem, "id">,
|
||
value: string,
|
||
) {
|
||
setAddresses((prev) =>
|
||
prev.map((item) =>
|
||
item.id === id ? { ...item, [field]: value } : item,
|
||
),
|
||
);
|
||
}
|
||
|
||
function addAddress() {
|
||
setAddresses((prev) => [...prev, emptyAddress()]);
|
||
}
|
||
|
||
function removeAddress(id: string) {
|
||
setAddresses((prev) =>
|
||
prev.length <= 1 ? prev : prev.filter((item) => item.id !== id),
|
||
);
|
||
}
|
||
|
||
function handleSubmit(event: FormEvent) {
|
||
event.preventDefault();
|
||
}
|
||
|
||
return (
|
||
<ClothesShell
|
||
eyebrow="Profile"
|
||
title="پروفایل"
|
||
description="اطلاعات شخصی، تصویر آواتار و آدرسهای خود را مدیریت کنید."
|
||
breadcrumbs={[
|
||
{ label: "داشبورد", href: "/" },
|
||
{ label: "پروفایل" },
|
||
]}
|
||
>
|
||
<form
|
||
onSubmit={handleSubmit}
|
||
className="grid grid-cols-1 gap-3 lg:grid-cols-[260px_1fr]"
|
||
>
|
||
<aside className="dash-enter rounded-lg border border-line bg-surface p-4">
|
||
<p className="mb-2 text-sm text-ink">آواتار</p>
|
||
<p className="mb-3 text-xs leading-6 text-muted">نسبت ۱:۱</p>
|
||
<ImageCropper
|
||
src={avatar}
|
||
aspect="1:1"
|
||
alt="آواتار پروفایل"
|
||
className="profile-avatar-cropper"
|
||
/>
|
||
<input
|
||
ref={fileRef}
|
||
type="file"
|
||
accept="image/*"
|
||
className="sr-only"
|
||
onChange={handleAvatarChange}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="mt-3 w-full border border-line bg-white px-3 py-2 text-sm text-ink transition-colors hover:border-ink"
|
||
onClick={() => fileRef.current?.click()}
|
||
>
|
||
انتخاب تصویر
|
||
</button>
|
||
</aside>
|
||
|
||
<div className="space-y-3 dash-enter" style={{ animationDelay: "70ms" }}>
|
||
<section className="grid grid-cols-1 gap-3 rounded-lg border border-line bg-surface p-4 sm:grid-cols-12">
|
||
<div className="clothes-field sm:col-span-6">
|
||
<label htmlFor="profile-name-fa">نام فارسی</label>
|
||
<input
|
||
id="profile-name-fa"
|
||
value={nameFa}
|
||
onChange={(e) => setNameFa(e.target.value)}
|
||
placeholder="نام و نام خانوادگی"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-6">
|
||
<label htmlFor="profile-name-en">نام انگلیسی</label>
|
||
<input
|
||
id="profile-name-en"
|
||
dir="ltr"
|
||
className="!text-end font-montserrat"
|
||
value={nameEn}
|
||
onChange={(e) => setNameEn(e.target.value)}
|
||
placeholder="Full name"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-6">
|
||
<label htmlFor="profile-phone">شماره موبایل</label>
|
||
<input
|
||
id="profile-phone"
|
||
dir="ltr"
|
||
className="!text-end font-montserrat profile-input-locked"
|
||
value={phone}
|
||
readOnly
|
||
aria-readonly="true"
|
||
title="شماره موبایل قابل تغییر نیست"
|
||
/>
|
||
<span className="text-[0.65rem] text-muted">قابل تغییر نیست</span>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-6">
|
||
<label htmlFor="profile-email">ایمیل</label>
|
||
<input
|
||
id="profile-email"
|
||
type="email"
|
||
dir="ltr"
|
||
className="!text-end font-montserrat"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
placeholder="name@example.com"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-12">
|
||
<label htmlFor="profile-instagram">اینستاگرام</label>
|
||
<input
|
||
id="profile-instagram"
|
||
dir="ltr"
|
||
className="!text-end font-montserrat"
|
||
value={instagram}
|
||
onChange={(e) => setInstagram(e.target.value)}
|
||
placeholder="@username"
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-12">
|
||
<label htmlFor="profile-about">درباره من</label>
|
||
<textarea
|
||
id="profile-about"
|
||
value={about}
|
||
onChange={(e) => setAbout(e.target.value)}
|
||
placeholder="چند خط درباره خودتان..."
|
||
className="!min-h-[6rem]"
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="rounded-lg border border-line bg-surface p-4">
|
||
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
|
||
<div>
|
||
<p className="text-sm text-ink">آدرسها</p>
|
||
<p className="text-xs text-muted">
|
||
میتوانید چند آدرس اضافه کنید.
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={addAddress}
|
||
className="border border-ink bg-ink px-3 py-1.5 text-xs text-white transition-colors hover:bg-white hover:text-ink"
|
||
>
|
||
+ آدرس جدید
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
{addresses.map((address, index) => (
|
||
<div key={address.id} className="address-block">
|
||
<div className="mb-3 flex items-center justify-between gap-2">
|
||
<p className="text-sm text-ink">
|
||
آدرس {new Intl.NumberFormat("fa-IR").format(index + 1)}
|
||
</p>
|
||
<button
|
||
type="button"
|
||
className="text-xs text-muted transition-colors hover:text-ink disabled:opacity-30"
|
||
disabled={addresses.length <= 1}
|
||
onClick={() => removeAddress(address.id)}
|
||
>
|
||
حذف
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12">
|
||
<div className="clothes-field sm:col-span-4">
|
||
<label htmlFor={`addr-label-${address.id}`}>برچسب</label>
|
||
<input
|
||
id={`addr-label-${address.id}`}
|
||
value={address.label}
|
||
onChange={(e) =>
|
||
updateAddress(address.id, "label", e.target.value)
|
||
}
|
||
placeholder="منزل، محل کار..."
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-4">
|
||
<label htmlFor={`addr-province-${address.id}`}>
|
||
استان
|
||
</label>
|
||
<select
|
||
id={`addr-province-${address.id}`}
|
||
value={address.province}
|
||
onChange={(e) =>
|
||
updateAddress(address.id, "province", e.target.value)
|
||
}
|
||
>
|
||
<option value="" disabled>
|
||
انتخاب استان
|
||
</option>
|
||
{provinces.map((province) => (
|
||
<option key={province} value={province}>
|
||
{province}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-4">
|
||
<label htmlFor={`addr-city-${address.id}`}>شهر</label>
|
||
<input
|
||
id={`addr-city-${address.id}`}
|
||
value={address.city}
|
||
onChange={(e) =>
|
||
updateAddress(address.id, "city", e.target.value)
|
||
}
|
||
placeholder="نام شهر"
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-8">
|
||
<label htmlFor={`addr-full-${address.id}`}>
|
||
آدرس کامل
|
||
</label>
|
||
<textarea
|
||
id={`addr-full-${address.id}`}
|
||
value={address.fullAddress}
|
||
onChange={(e) =>
|
||
updateAddress(
|
||
address.id,
|
||
"fullAddress",
|
||
e.target.value,
|
||
)
|
||
}
|
||
placeholder="خیابان، کوچه، پلاک، واحد..."
|
||
className="!min-h-[4.5rem]"
|
||
/>
|
||
</div>
|
||
<div className="clothes-field sm:col-span-4">
|
||
<label htmlFor={`addr-landline-${address.id}`}>
|
||
تلفن ثابت
|
||
</label>
|
||
<input
|
||
id={`addr-landline-${address.id}`}
|
||
dir="ltr"
|
||
className="!text-end font-montserrat"
|
||
inputMode="tel"
|
||
value={address.landline}
|
||
onChange={(e) =>
|
||
updateAddress(address.id, "landline", e.target.value)
|
||
}
|
||
placeholder="021..."
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<div className="flex justify-end gap-2">
|
||
<button
|
||
type="button"
|
||
className="border border-line px-5 py-2.5 text-sm text-muted hover:text-ink"
|
||
>
|
||
انصراف
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="auth-button !mt-0 !max-w-none !w-auto px-6"
|
||
>
|
||
<span>ذخیره پروفایل</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</ClothesShell>
|
||
);
|
||
}
|