diff --git a/package-lock.json b/package-lock.json
index 57dd7d8..b620551 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"next": "15.3.4",
"next-usequerystate": "^1.20.0",
"react": "^19.0.0",
+ "react-day-picker": "^9.8.0",
"react-dom": "^19.0.0",
"react-virtualized": "^9.22.6",
"tailwind-merge": "^3.3.1",
@@ -80,6 +81,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@date-fns/tz": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz",
+ "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==",
+ "license": "MIT"
+ },
"node_modules/@emnapi/core": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
@@ -3179,6 +3186,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.1.0-0",
+ "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
+ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -5974,6 +5997,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-day-picker": {
+ "version": "9.8.0",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.8.0.tgz",
+ "integrity": "sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-fns/tz": "1.2.0",
+ "date-fns": "4.1.0",
+ "date-fns-jalali": "4.1.0-0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
diff --git a/package.json b/package.json
index e1105d5..0fb0360 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"next": "15.3.4",
"next-usequerystate": "^1.20.0",
"react": "^19.0.0",
+ "react-day-picker": "^9.8.0",
"react-dom": "^19.0.0",
"react-virtualized": "^9.22.6",
"tailwind-merge": "^3.3.1",
diff --git a/src/app/[name]/[id]/page.tsx b/src/app/[name]/[id]/page.tsx
index f659c1c..d61d91f 100644
--- a/src/app/[name]/[id]/page.tsx
+++ b/src/app/[name]/[id]/page.tsx
@@ -18,7 +18,7 @@ function FoodPage({ }: Props) {
return (
-
+
{
-
-
(new Date());
+ const [selectedGender, setSelectedGender] = useState("");
+ const [showCalendar, setShowCalendar] = useState(false);
+ const dateLib = getDateLib()
+
+ const changeGender = (e: React.MouseEvent, index: number) => {
+ setSelectedGender(() => String(index));
+ }
+
+ // const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
+
+ // const { mutate, data, isPending } = useProfile();
+
+ // useEffect(() => {
+ // if (isAuthenticated) {
+ // mutate();
+ // }
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, [isAuthenticated])
+
+ const onChange = (e: ChangeEvent) => {
+ if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_FULLNAME) {
+ setFullName(() => e.target.value)
+ }
+ else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_EMAIL) {
+ setEmail(() => e.target.value)
+ }
+ else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_PHONE) {
+ setPhoneNumber(() => e.target.value)
+ }
+ // else if (e.target.name == PORFILE_EDIT_PAGE_ELEMENT.INPUT_DOB) {
+ // setDateOfBirth(() => e.target.value)
+ // }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {dateOfBirth ? dateLib.format(dateOfBirth, "yyyy/MM/dd") : "انتخاب کنید"}
+
+ تاریخ تولد
+
+
+
+ {
+ setDateOfBirth(date)
+ setShowCalendar(false)
+ }}
+ />
+
+
+
+
+
+
+
+ جنسیت
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default ProfileIndex
\ No newline at end of file
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
index 7051f2f..03fb787 100644
--- a/src/app/profile/page.tsx
+++ b/src/app/profile/page.tsx
@@ -5,11 +5,13 @@ import Button from '@/components/button/PrimaryButton';
// import { useAuthStore } from '@/zustand/authStore';
import { ArrowLeft, Calendar2, CallCalling, Profile, Sms, Verify } from 'iconsax-react';
import Image from 'next/image';
+import Link from 'next/link';
import React from 'react'
type Props = object
function ProfileIndex({ }: Props) {
+
// const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
// const { mutate, data, isPending } = useProfile();
@@ -30,7 +32,7 @@ function ProfileIndex({ }: Props) {
-
+
@@ -78,7 +80,9 @@ function ProfileIndex({ }: Props) {
-
+
+
+
>
diff --git a/src/components/combobox/Combobox.tsx b/src/components/combobox/Combobox.tsx
index b7e8d25..55ecd0e 100644
--- a/src/components/combobox/Combobox.tsx
+++ b/src/components/combobox/Combobox.tsx
@@ -1,91 +1,153 @@
-"use client"
-
-import * as React from "react"
-import { Check } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover"
-import ArrowDownIcon from "../icons/ArrowDownIcon"
+import React, { useState, useEffect, useRef } from 'react';
+import ArrowDownIcon from '../icons/ArrowDownIcon';
+import { motion, Variants } from 'framer-motion';
+import { SearchNormal } from 'iconsax-react';
export interface DropdownOption {
id: string;
- label?: string;
title: string;
+ label?: string;
}
type Props = {
title: string;
options: Array
;
- value: string;
- setValue: React.Dispatch>
+ expanded?: boolean;
+ selectedId: string;
+ searchable?: boolean;
+ onSelectionChange: (e: React.MouseEvent, index: number) => void,
} & React.HTMLAttributes
-export function Combobox({ options, value, title, setValue, }: Props) {
- const [open, setOpen] = React.useState(false)
+function ComboBox({ title, options, expanded, selectedId, searchable = true, onSelectionChange, ...props }: Props) {
+ const [expand, setExpand] = useState(expanded);
+ const [searchValue, setSearchValue] = useState('');
+ const boxRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (boxRef.current && !boxRef.current.contains(event.target as Node)) {
+ setExpand(false);
+ }
+ };
+
+ if (expand) {
+ document.addEventListener('mousedown', handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [expand]);
+
+ const toggleExpand = () => {
+ setExpand((prev) => !prev);
+ setSearchValue('');
+ };
+
+ const searchChanged = (e: React.ChangeEvent) => {
+ setSearchValue(e.target.value);
+ };
+
+ const setSelection = (e: React.MouseEvent, index: number) => {
+ e.stopPropagation(); // prevent toggle
+ onSelectionChange(e, index);
+ setExpand(false);
+ };
+
+ const variants: Variants = {
+ collapse: {
+ opacity: 0,
+ scale: 0.95,
+ pointerEvents: 'none', // disable clicks
+ transition: {
+ duration: 0.1,
+ ease: 'easeInOut'
+ }
+ },
+ expand: {
+ opacity: 1,
+ scale: 1,
+ pointerEvents: 'auto', // re-enable clicks
+ transition: {
+ duration: 0.1,
+ ease: 'easeInOut'
+ }
+ }
+ };
+
return (
-
-
-
+ );
}
+
+
+export default ComboBox
\ No newline at end of file
diff --git a/src/components/combobox/SearchComboBox.tsx b/src/components/combobox/SearchComboBox.tsx
deleted file mode 100644
index 691e2c2..0000000
--- a/src/components/combobox/SearchComboBox.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react';
-import ArrowDownIcon from '../icons/ArrowDownIcon';
-import { motion, Variants } from 'framer-motion';
-import { SearchNormal } from 'iconsax-react';
-
-export interface DropdownOption {
- id: string;
- title: string;
- label?: string;
-}
-
-type Props = {
- title: string;
- options: Array
;
- expanded?: boolean;
- selectedId: string;
- onSelectionChange: (e: React.MouseEvent, index: number) => void,
-} & React.HTMLAttributes
-
-function SearchComboBox({ title, options, expanded, selectedId, onSelectionChange, ...props }: Props) {
- const [expand, setExpand] = useState(expanded);
- const [searchValue, setSearchValue] = useState('');
- const boxRef = useRef(null);
-
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (boxRef.current && !boxRef.current.contains(event.target as Node)) {
- setExpand(false);
- }
- };
-
- if (expand) {
- document.addEventListener('mousedown', handleClickOutside);
- }
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [expand]);
-
- const toggleExpand = () => {
- setExpand((prev) => !prev);
- setSearchValue('');
- };
-
- const searchChanged = (e: React.ChangeEvent) => {
- setSearchValue(e.target.value);
- };
-
- const setSelection = (e: React.MouseEvent, index: number) => {
- e.stopPropagation(); // prevent toggle
- onSelectionChange(e, index);
- setExpand(false);
- };
-
- const variants: Variants = {
- collapse: {
- opacity: 0,
- scale: 0.95,
- pointerEvents: 'none', // disable clicks
- transition: {
- duration: 0.1,
- ease: 'easeInOut'
- }
- },
- expand: {
- opacity: 1,
- scale: 1,
- pointerEvents: 'auto', // re-enable clicks
- transition: {
- duration: 0.1,
- ease: 'easeInOut'
- }
- }
- };
-
-
- return (
-
-
-
-
-
- {options.find((x) => x.id === selectedId)?.title ?? ''}
-
-
-
-
-
-
e.stopPropagation()}
- initial="collapse"
- animate={expand ? "expand" : "collapse"}
- variants={variants}
- >
-
-
-
-
-
-
- {options
- .filter((v) => v.title?.includes(searchValue))
- .map((v, i) => (
-
setSelection(e, i)}
- key={v.id}
- data-selected={v.id === selectedId}
- className="flex items-center justify-end px-2 py-1.5 cursor-pointer hover:bg-gray-50 rounded-md last:rounded-b-xl"
- tabIndex={0}
- role="option"
- aria-selected
- >
- {v.title}
-
- ))}
-
-
-
- );
-}
-
-
-export default SearchComboBox
\ No newline at end of file
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..a6d9bac
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,226 @@
+"use client"
+
+import * as React from "react"
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react"
+import { DayButton, getDefaultClassNames } from "react-day-picker"
+import { DayPicker, faIR } from "react-day-picker/persian"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+
+export function CalendarHijri() {
+ const [date, setDate] = React.useState(
+ new Date(2025, 5, 12)
+ )
+
+ return (
+
+ )
+}
+
+// ----------------------------------------------------------------------------
+// The code below is for this example only.
+// For your own calendar, you would edit the calendar.tsx component directly.
+// ----------------------------------------------------------------------------
+export default function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"]
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center px-3 h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn("absolute inset-0 opacity-0 ", defaultClassNames.dropdown),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground px-2 rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "rounded-l-md bg-accent",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-disabled aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "bg-red-500 opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+