diff --git a/package-lock.json b/package-lock.json
index b620551..1c0959d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-popover": "^1.1.14",
+ "@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@tanstack/react-query": "^5.81.5",
@@ -1031,6 +1032,12 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
@@ -1060,6 +1067,32 @@
}
}
},
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -1126,6 +1159,21 @@
}
}
},
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
@@ -1351,6 +1399,39 @@
}
}
},
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz",
+ "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
diff --git a/package.json b/package.json
index 0fb0360..b079fe2 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-popover": "^1.1.14",
+ "@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@tanstack/react-query": "^5.81.5",
diff --git a/src/app/[name]/rating/[orderId]/page.tsx b/src/app/[name]/rating/[orderId]/page.tsx
new file mode 100644
index 0000000..9b3760a
--- /dev/null
+++ b/src/app/[name]/rating/[orderId]/page.tsx
@@ -0,0 +1,130 @@
+'use client';
+
+import ToggleButton from '@/components/button/ToggleButton';
+import TabContainer, { TabContainerClassNames, TabContainerRenderType } from '@/components/tab/TabContainer';
+import { TabHeader } from '@/components/tab/TabHeader';
+import { Button } from '@/components/ui/button';
+import RateSelectionBar from '@/components/utils/RateSelectionBar';
+import clsx from 'clsx';
+import { useParams } from 'next/navigation';
+import React from 'react'
+
+type Props = object
+
+type Params = {
+ orderId: string;
+}
+
+function RatingOrderIndex({ }: Props) {
+ const params: Params = useParams();
+ console.log(params)
+
+ const badPoitns = () => {
+ return (
+ <>
+
+
+ اماده سازی زمانبر
+
+
+ بهداشت محیطی بد
+
+
+ کیفیت منو پایین
+
+
+ تنوع پایین منو
+
+
+ برخورد نامناسب کارکنان
+
+
+ قیمت نامناسب
+
+
+ >
+ )
+ }
+
+ const goodPoitns = () => {
+ return (
+ <>
+
+
+
+ اماده سازی سریع
+
+
+ بهداشت محیطی خوب
+
+
+ کیفیت منو خوب
+
+
+ تنوع بالا منو
+
+
+ برخورد مناسب کارکنان
+
+
+ قیمت مناسب
+
+
+
+ >
+ )
+ }
+
+ const submitForm = (formData: FormData) => {
+ console.log(formData.get('rating'))
+ }
+
+ return (
+
+ امتیاز شما به رستوران
+
+
+
+ )
+}
+
+export default RatingOrderIndex
\ No newline at end of file
diff --git a/src/components/button/ToggleButton.tsx b/src/components/button/ToggleButton.tsx
new file mode 100644
index 0000000..7bde18b
--- /dev/null
+++ b/src/components/button/ToggleButton.tsx
@@ -0,0 +1,128 @@
+import clsx from 'clsx';
+import { motion } from 'framer-motion';
+import { Refresh } from 'iconsax-react';
+import React, { useEffect, useRef, useState } from 'react';
+
+type Props = {
+ className?: ToggleButtonClassName;
+ pending?: boolean;
+ defaultChecked?: boolean;
+ onToggle?: (checked: boolean) => void;
+ name: string; // required for form data
+ children?: React.ReactNode;
+ disabled?: boolean;
+} & Omit, 'className' | 'onClick' | 'type'>;
+
+export type ToggleButtonClassName = {
+ wrapper?: string;
+ wrapperDeactive?: string;
+ wrapperActive?: string;
+ wrapperDisabled?: string;
+ wrapperEnabled?: string;
+ wrapperPending?: string;
+ icon?: string;
+ iconDeactive?: string;
+ iconActive?: string;
+ content?: string;
+ contentDeactive?: string;
+ contentActive?: string;
+};
+
+export const ToggleButtonClassNames: ToggleButtonClassName = {
+ wrapper:
+ 'relative transition-all border border-solid duration-200 cursor-pointer w-full rounded-normal p-3 font-normal text-sm overflow-clip',
+ wrapperDeactive: 'bg-white border-neutral-200',
+ wrapperActive: 'border-[#439C46]',
+ wrapperDisabled: 'bg-disabled text-disabled-text',
+ wrapperEnabled: 'bg-container text-white hover:bg-gray-100 active:brightness-110',
+ wrapperPending: 'bg-transparent text-transparent cursor-auto',
+ content: 'text-xs transition-all duration-150',
+ contentDeactive: 'text-foreground',
+ contentActive: 'text-[#439C46]',
+};
+
+function ToggleButton({
+ className = ToggleButtonClassNames,
+ disabled = false,
+ pending = false,
+ defaultChecked = false,
+ onToggle,
+ name,
+ children,
+ ...rest
+}: Props) {
+ const [checked, setChecked] = useState(defaultChecked);
+ const inputRef = useRef(null);
+
+ const handleClick = () => {
+ if (disabled || pending) return;
+ const newChecked = !checked;
+ setChecked(newChecked);
+ onToggle?.(newChecked);
+ };
+
+ useEffect(() => {
+ const form = inputRef.current?.form;
+ if (!form) return;
+
+ const onReset = () => setChecked(defaultChecked);
+ form.addEventListener('reset', onReset);
+ return () => form.removeEventListener('reset', onReset);
+ }, [defaultChecked]);
+
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+export default ToggleButton;
diff --git a/src/components/tab/TabContainer.tsx b/src/components/tab/TabContainer.tsx
index 395f5e0..5306895 100644
--- a/src/components/tab/TabContainer.tsx
+++ b/src/components/tab/TabContainer.tsx
@@ -11,8 +11,15 @@ type Props = {
itemRenderer?: (child: ReactNode, index: number) => ReactElement;
children: ReactNode;
className?: TabContainerClassName;
+ changeType?: TabContainerRenderType;
+ defaultIndex?: number;
} & Omit, 'className'>;
+export enum TabContainerRenderType {
+ RENDERER,
+ VISIBILITY,
+}
+
export type TabContainerClassName = {
wrapper?: string;
scrollView?: string;
@@ -41,9 +48,9 @@ export const TabContainerClassNames: TabContainerClassName = {
titleActive: 'text-black'
}
-function TabContainer({ className = TabContainerClassNames, itemRenderer, children, ...restProps }: Props) {
+function TabContainer({ className = TabContainerClassNames, changeType = TabContainerRenderType.RENDERER, defaultIndex = 0, itemRenderer, children, ...restProps }: Props) {
- const [selectedTab, setSelectedTab] = useState(0);
+ const [selectedTab, setSelectedTab] = useState(defaultIndex);
const updateTab = useCallback((index: number) => {
setSelectedTab(index);
@@ -99,9 +106,24 @@ function TabContainer({ className = TabContainerClassNames, itemRenderer, childr
})}
-
- {items[selectedTab]?.props?.viewRenderer ??
}
-
+ {changeType === TabContainerRenderType.RENDERER &&
+
+ {items[selectedTab]?.props?.viewRenderer ??
}
+
+ }
+ {changeType === TabContainerRenderType.VISIBILITY &&
+ <>
+ {items.map((item, index) => {
+ return (
+
+ {item.props.viewRenderer}
+
+ )
+ })}
+ >
+ }
)
}
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx
new file mode 100644
index 0000000..d80f415
--- /dev/null
+++ b/src/components/ui/slider.tsx
@@ -0,0 +1,63 @@
+"use client"
+
+import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider"
+
+import { cn } from "@/lib/utils"
+
+function Slider({
+ className,
+ defaultValue,
+ value,
+ min = 0,
+ max = 100,
+ ...props
+}: React.ComponentProps) {
+ const _values = React.useMemo(
+ () =>
+ Array.isArray(value)
+ ? value
+ : Array.isArray(defaultValue)
+ ? defaultValue
+ : [min, max],
+ [value, defaultValue, min, max]
+ )
+
+ return (
+
+
+
+
+ {Array.from({ length: _values.length }, (_, index) => (
+
+ ))}
+
+ )
+}
+
+export { Slider }
diff --git a/src/components/utils/RateSelectionBar.tsx b/src/components/utils/RateSelectionBar.tsx
new file mode 100644
index 0000000..f632b68
--- /dev/null
+++ b/src/components/utils/RateSelectionBar.tsx
@@ -0,0 +1,43 @@
+import { Star, Star1 } from 'iconsax-react'
+import React from 'react'
+import { Slider } from '../ui/slider';
+
+type Props = {
+ content?: Array<{ value: string, icon: React.ReactElement }>;
+ className?: string;
+ name?: string;
+}
+
+const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
+ { value: '5', icon: },
+ { value: '4', icon: },
+ { value: '3', icon: },
+ { value: '2', icon: },
+ { value: '1', icon: },
+]
+
+function RateSelectionBar({ name, content = defaultContent, className = '' }: Props) {
+ return (
+
+
+
+
+
+ {content?.map((value, index) => {
+ return (
+
+
+ {value.value}
+
+ )
+ })}
+
+
+ )
+}
+
+export default RateSelectionBar
\ No newline at end of file