feat: cafe owner registeration

This commit is contained in:
Afra ‌ 2026-06-20 15:29:17 +03:30
parent cf26ec0b13
commit c2382b23f1
5 changed files with 301 additions and 2 deletions

View file

@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'
import Home from './pages/Home' import Home from './pages/Home'
import CafeDetail from './pages/CafeDetail' import CafeDetail from './pages/CafeDetail'
import PaymentSuccess from './pages/PaymentSuccess' import PaymentSuccess from './pages/PaymentSuccess'
import CafeOwnerRegister from './pages/CafeOwnerRegister'
function ScrollToTop() { function ScrollToTop() {
const { pathname } = useLocation() const { pathname } = useLocation()
@ -22,6 +23,7 @@ export default function App() {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/cafe/:id" element={<CafeDetail />} /> <Route path="/cafe/:id" element={<CafeDetail />} />
<Route path="/payment/success" element={<PaymentSuccess />} /> <Route path="/payment/success" element={<PaymentSuccess />} />
<Route path="/owner/register" element={<CafeOwnerRegister />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
) )

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" stroke="white" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View file

@ -117,7 +117,7 @@ export default function ReservationModal({
value={fullName} value={fullName}
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
placeholder="نام و نام خانوادگی" placeholder="نام و نام خانوادگی"
className="w-full rounded-2xl border border-gray-200 px-4 py-3 text-right" className="w-full rounded-2xl border border-gray-200 px-4 py-3 text-right placeholder:font-light placeholder:text-[#999999]"
/> />
</div> </div>
@ -131,7 +131,7 @@ export default function ReservationModal({
value={mockPhone} value={mockPhone}
readOnly readOnly
placeholder="مثال: ۰۹۱۲۳۴۵۶۷۸۹" placeholder="مثال: ۰۹۱۲۳۴۵۶۷۸۹"
className="w-full rounded-2xl border border-gray-200 bg-gray-50 px-4 py-3 text-right" className="w-full rounded-2xl border border-gray-200 bg-gray-50 px-4 py-3 text-right placeholder:font-light placeholder:text-[#999999]"
/> />
</div> </div>

View file

@ -0,0 +1,12 @@
export interface CafeFeature {
id: string
label: string
}
export const mockCafeFeatures: CafeFeature[] = [
{ id: 'open-space', label: 'فضای باز' },
{ id: 'family-friendly', label: 'فضای مناسب خانواده' },
{ id: 'smoking-allowed', label: 'امکان کشیدن سیگار' },
{ id: 'parking', label: 'پارکینگ' },
{ id: 'delivery', label: 'امکان سفارش' },
]

View file

@ -0,0 +1,282 @@
import { ReactNode, useState } from "react";
import InfoIcon from "../components/InfoIcon";
import { mockCafeFeatures } from "../data/mockCafeFeatures";
function ArrowIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
);
}
function CheckIcon() {
return (
<svg
className="h-3 w-3 text-white"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" />
</svg>
);
}
function StepCircle({
active,
children,
}: {
active: boolean;
children: ReactNode;
}) {
return (
<span
className={`mx-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold ${
active
? "bg-[#223D5D] text-white"
: "border border-gray-300 text-gray-400"
}`}
>
{children}
</span>
);
}
function Stepper({ currentStep }: { currentStep: 1 | 2 }) {
return (
<div className="flex items-center px-4 py-7">
<span
className={`whitespace-nowrap ${currentStep === 1 ? "font-medium text-gray-900" : "text-gray-400"}`}
>
اطلاعات نماینده
</span>
<StepCircle active={currentStep === 1}>۱</StepCircle>
<span className="mx-1 h-px flex-1 bg-gray-200" />
<StepCircle active={currentStep === 2}>۲</StepCircle>
<span
className={`whitespace-nowrap ${currentStep === 2 ? "font-medium text-gray-900" : "text-gray-400"}`}
>
اطلاعات کافه
</span>
</div>
);
}
function FormField({
label,
placeholder,
value,
onChange,
}: {
label: string;
placeholder: string;
value: string;
onChange: (value: string) => void;
}) {
return (
<div className="mt-5">
<label className="mb-3 block text-right text-lg font-normal text-gray-900">
{label}
</label>
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="w-full rounded-2xl border border-gray-200 px-4 py-3 text-right h-14 placeholder:font-light placeholder:text-[#999999]"
/>
</div>
);
}
function UploadBox({
label,
buttonLabel,
}: {
label: string;
buttonLabel: string;
}) {
return (
<div className="mt-5">
<label className="mb-3 block text-right text-lg font-normal text-gray-900">
{label}
</label>
<button
type="button"
className="flex h-36 w-full items-center justify-center rounded-2xl border border-dashed border-gray-300 text-[#666666] bg-[#F8F8F8]"
>
{buttonLabel}
</button>
</div>
);
}
function FeatureChip({
label,
checked,
onToggle,
}: {
label: string;
checked: boolean;
onToggle: () => void;
}) {
return (
<button
type="button"
onClick={onToggle}
className={`flex items-center gap-2 whitespace-nowrap rounded-2xl border border-[#F2F2F2] px-3 py-2 text-sm text-gray-900 ${
checked ? "bg-[#ECF2F8]" : "bg-transparent"
}`}
>
<span
className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-md border border-[#223D5D] ${
checked ? "bg-[#223D5D]" : "bg-transparent"
}`}
>
{checked && <CheckIcon />}
</span>
<span>{label}</span>
</button>
);
}
export default function CafeOwnerRegister() {
const [step, setStep] = useState<1 | 2>(1);
const [fullName, setFullName] = useState("");
const [phone, setPhone] = useState("");
const [nationalId, setNationalId] = useState("");
const [cardNumber, setCardNumber] = useState("");
const [cafeName, setCafeName] = useState("");
const [address, setAddress] = useState("");
const [cafePhone, setCafePhone] = useState("");
const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);
const toggleFeature = (feature: string) => {
setSelectedFeatures((prev) =>
prev.includes(feature)
? prev.filter((f) => f !== feature)
: [...prev, feature],
);
};
return (
<div className="min-h-screen bg-white pb-24">
<div className="h-16 w-full bg-gray-200" />
<Stepper currentStep={step} />
<div className="px-4">
{step === 1 ? (
<>
<div className="flex items-center justify-end gap-2 rounded-2xl bg-[#ECF2F8] px-4 py-4">
<InfoIcon />
<p className="text-right text-md leading-relaxed text-gray-600">
لطفاً یک{" "}
<span className="font-semibold text-gray-900">نماینده</span>{" "}
برای کافه معرفی کنید. نماینده باید در اپلیکیشن ثبت‌نام کند و
پرداختها و واریز به کیف پول یا واریز می‌شود. شماره موبایل، کد
ملی و کارت بانکی باید متعلق به یک نفر باشند.
</p>
</div>
<FormField
label="نام و نام خانوادگی"
placeholder="نام و نام خانوادگی نماینده را وارد کنید"
value={fullName}
onChange={setFullName}
/>
<FormField
label="شماره تماس نماینده"
placeholder="مثال: ۰۹۱۲۳۴۵۶۷۸۹"
value={phone}
onChange={setPhone}
/>
<FormField
label="کد ملی"
placeholder="کد ملی نماینده را وارد کنید"
value={nationalId}
onChange={setNationalId}
/>
<FormField
label="شماره کارت"
placeholder="شماره کارت نماینده را وارد کنید"
value={cardNumber}
onChange={setCardNumber}
/>
</>
) : (
<>
<FormField
label="نام کافه"
placeholder="نام کافه را وارد کنید"
value={cafeName}
onChange={setCafeName}
/>
<FormField
label="آدرس"
placeholder="موقعیت کافه را با جزئیات وارد کنید"
value={address}
onChange={setAddress}
/>
<FormField
label="شماره تماس کافه (اختیاری)"
placeholder="مثال: ۰۹۱۲۳۴۵۶۷۸۹"
value={cafePhone}
onChange={setCafePhone}
/>
<UploadBox label="تصاویر (اختیاری)" buttonLabel="افزودن تصویر" />
<UploadBox label="مجوز کسب و کار" buttonLabel="افزودن تصویر مجوز" />
<div className="mt-5">
<label className="mb-3 block text-right text-lg font-normal text-gray-900">
ویژگی‌های کافه
</label>
<div className="flex flex-wrap gap-4">
{mockCafeFeatures.map((feature) => (
<FeatureChip
key={feature.id}
label={feature.label}
checked={selectedFeatures.includes(feature.id)}
onToggle={() => toggleFeature(feature.id)}
/>
))}
</div>
</div>
</>
)}
</div>
<div className="fixed inset-x-0 bottom-0 flex items-center justify-between gap-3 bg-white px-4 py-4">
<button
type="button"
className="rounded-full bg-[#223D5D] px-5 py-3 font-semibold text-white"
>
پشتیبانی
</button>
<button
type="button"
onClick={() => step === 1 && setStep(2)}
className="flex items-center gap-2 rounded-full bg-[#223D5D] px-6 py-3 font-semibold text-white"
>
ادامه
<ArrowIcon className="h-4 w-4" />
</button>
</div>
</div>
);
}