This commit is contained in:
Generated
+21
@@ -20,6 +20,7 @@
|
||||
"iconsax-react": "^0.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"rc-rate": "^2.13.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
@@ -46,6 +47,7 @@
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/qrcode.react": "^1.0.5",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
@@ -2383,6 +2385,16 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qrcode.react": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.5.tgz",
|
||||
"integrity": "sha512-BghPtnlwvrvq8QkGa1H25YnN+5OIgCKFuQruncGWLGJYOzeSKiix/4+B9BtfKF2wf5ja8yfyWYA3OXju995G8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
|
||||
@@ -4987,6 +4999,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode.react": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
|
||||
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"iconsax-react": "^0.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"rc-rate": "^2.13.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
@@ -48,6 +49,7 @@
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/qrcode.react": "^1.0.5",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
|
||||
@@ -28,6 +28,22 @@ const validationSchema = yup.object({
|
||||
.min(1, 'حداقل یک شماره تماس الزامی است')
|
||||
.of(yup.string().required('شماره تماس الزامی است')),
|
||||
instagram: yup.string().url('لینک اینستاگرام معتبر نیست').nullable(),
|
||||
domain: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test('domain-or-url', 'آدرس وبسایت معتبر نیست', (value) => {
|
||||
if (!value) return true
|
||||
const trimmed = value.trim()
|
||||
if (/^https?:\/\//i.test(trimmed)) {
|
||||
try {
|
||||
new URL(trimmed)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(\/.*)?$/.test(trimmed)
|
||||
}),
|
||||
address: yup.string().required('آدرس الزامی است'),
|
||||
latitude: yup.number().required('انتخاب موقعیت روی نقشه الزامی است'),
|
||||
longitude: yup.number().required('انتخاب موقعیت روی نقشه الزامی است'),
|
||||
@@ -52,6 +68,7 @@ const GeneralSettings: FC = () => {
|
||||
menuColor: '#000000',
|
||||
phones: [''],
|
||||
instagram: '',
|
||||
domain: '',
|
||||
address: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
@@ -112,6 +129,7 @@ const GeneralSettings: FC = () => {
|
||||
? [restaurantData.phone]
|
||||
: [''],
|
||||
instagram: restaurantData.instagram || '',
|
||||
domain: restaurantData.domain || '',
|
||||
address: restaurantData.address || '',
|
||||
latitude: restaurantData.latitude || 0,
|
||||
longitude: restaurantData.longitude || 0,
|
||||
@@ -206,6 +224,22 @@ const GeneralSettings: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label='آدرس وبسایت (دامنه)'
|
||||
name='domain'
|
||||
value={formik.values.domain || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.domain && formik.errors.domain ? formik.errors.domain : undefined}
|
||||
placeholder='example.com یا https://example.com'
|
||||
isNotRequired
|
||||
/>
|
||||
<p className='mt-2 text-xs text-description'>
|
||||
در صورتی که دامنه خریداری کرده اید آدرس دامنه خریداری شده رو وارد کنید
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Textarea
|
||||
label='آدرس'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Tabs from "@/components/Tabs";
|
||||
import { Brush2, Money2, NotificationStatus, Setting4 } from "iconsax-react";
|
||||
import { Brush2, Money2, NotificationStatus, ScanBarcode, Setting4 } from "iconsax-react";
|
||||
import { useState, type FC } from "react";
|
||||
import NotificationsList from "../notifications/List";
|
||||
import DesignSettings from "./components/DesignSettings";
|
||||
import QrCodeSettings from "./components/QrCodeSettings";
|
||||
import ScoreSetting from "./components/ScoreSetting";
|
||||
import { SettingTabEnum } from "./enum/Enum";
|
||||
import GeneralSettings from "./GeneralSettings";
|
||||
@@ -37,6 +38,11 @@ const Setting: FC = () => {
|
||||
label: "تنظیمات طراحی",
|
||||
value: SettingTabEnum.DESIGN,
|
||||
},
|
||||
{
|
||||
icon: <ScanBarcode color={activeTab === SettingTabEnum.QR_CODE ? "black" : "#8C90A3"} size={24} />,
|
||||
label: "QR کد",
|
||||
value: SettingTabEnum.QR_CODE,
|
||||
},
|
||||
]}
|
||||
onChange={(value: string) => setActiveTab(value as SettingTabEnum)}
|
||||
active={activeTab}
|
||||
@@ -48,6 +54,7 @@ const Setting: FC = () => {
|
||||
{activeTab === SettingTabEnum.FINANCIAL && <ScoreSetting />}
|
||||
{activeTab === SettingTabEnum.NOTIFICATION && <NotificationsList />}
|
||||
{activeTab === SettingTabEnum.DESIGN && <DesignSettings />}
|
||||
{activeTab === SettingTabEnum.QR_CODE && <QrCodeSettings />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import Button from "@/components/Button";
|
||||
import { MENU_SITE_ORIGIN } from "@/config/menuPreview";
|
||||
import { DocumentDownload } from "iconsax-react";
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
import { useMemo, useRef, type FC } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useGetRestaurant } from "../hooks/useSettingData";
|
||||
|
||||
const normalizeMenuUrl = (domain?: string | null, slug?: string | null): string => {
|
||||
const trimmedDomain = domain?.trim();
|
||||
|
||||
if (trimmedDomain) {
|
||||
if (/^https?:\/\//i.test(trimmedDomain)) {
|
||||
return trimmedDomain.replace(/\/+$/, "");
|
||||
}
|
||||
return `https://${trimmedDomain.replace(/\/+$/, "")}`;
|
||||
}
|
||||
|
||||
if (slug) {
|
||||
return `${MENU_SITE_ORIGIN}/${slug}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const QrCodeSettings: FC = () => {
|
||||
const { data: restaurant, isLoading } = useGetRestaurant();
|
||||
const canvasWrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const menuUrl = useMemo(
|
||||
() => normalizeMenuUrl(restaurant?.data?.domain, restaurant?.data?.slug),
|
||||
[restaurant?.data?.domain, restaurant?.data?.slug],
|
||||
);
|
||||
|
||||
const handleDownload = () => {
|
||||
const canvas = canvasWrapperRef.current?.querySelector("canvas");
|
||||
if (!canvas) {
|
||||
toast.error("QR کد آماده نیست");
|
||||
return;
|
||||
}
|
||||
|
||||
const link = document.createElement("a");
|
||||
const fileName = restaurant?.data?.slug || restaurant?.data?.name || "menu";
|
||||
link.download = `qr-${fileName}.png`;
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
toast.success("QR کد با موفقیت دانلود شد");
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="bg-white rounded-4xl p-8">
|
||||
<div className="text-sm text-gray-500">در حال بارگذاری...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!menuUrl) {
|
||||
return (
|
||||
<div className="bg-white rounded-4xl p-8">
|
||||
<div className="text-lg font-light">QR کد منو</div>
|
||||
<p className="mt-4 text-sm text-gray-500">
|
||||
برای ساخت QR کد، ابتدا دامنه وبسایت را در تنظیمات عمومی وارد کنید یا از اسلاگ رستوران استفاده شود.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-4xl p-8">
|
||||
<div className="text-lg font-light">QR کد منو</div>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
این QR کد به آدرس منوی دیجیتال رستوران شما لینک میشود. میتوانید آن را دانلود و چاپ کنید.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col items-center gap-6">
|
||||
<div
|
||||
ref={canvasWrapperRef}
|
||||
className="rounded-2xl border border-gray-100 bg-white p-6 shadow-sm"
|
||||
>
|
||||
<QRCodeCanvas
|
||||
value={menuUrl}
|
||||
size={256}
|
||||
level="H"
|
||||
includeMargin
|
||||
bgColor="#FFFFFF"
|
||||
fgColor="#000000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md text-center">
|
||||
<div className="text-xs text-gray-400 mb-1">آدرس منو</div>
|
||||
<a
|
||||
href={menuUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary break-all hover:underline"
|
||||
dir="ltr"
|
||||
>
|
||||
{menuUrl}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Button type="button" className="w-fit px-6" onClick={handleDownload}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<DocumentDownload size={20} color="#fff" />
|
||||
<span>دانلود QR کد</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrCodeSettings;
|
||||
@@ -5,4 +5,5 @@ export const enum SettingTabEnum {
|
||||
DELIVERY = "delivery",
|
||||
NOTIFICATION = "notification",
|
||||
DESIGN = "design",
|
||||
QR_CODE = "qr_code",
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type UpdateRestaurantType = {
|
||||
longitude?: number;
|
||||
description?: string;
|
||||
establishedYear?: number | null;
|
||||
domain?: string;
|
||||
serviceArea?: GeoJSONPolygon;
|
||||
enableAiChat?: boolean;
|
||||
score?: {
|
||||
|
||||
Reference in New Issue
Block a user