ReactNode> = {
- instagram: InstagramChannelIcon,
- telegram: TelegramChannelIcon,
- twitter: TwitterChannelIcon,
- linkedin: LinkedinChannelIcon,
- youtube: YoutubeChannelIcon,
- website: WebsiteChannelIcon,
- support_phone: SupportPhoneChannelIcon,
-}
-
-interface ContactChannelIconProps {
- channel: string
- className?: string
-}
-
-const ContactChannelIcon = ({ channel, className = 'size-6 text-primary' }: ContactChannelIconProps) => {
- const Icon = CHANNEL_ICONS[channel] ?? WebsiteChannelIcon
-
- return
-}
-
-export default ContactChannelIcon
diff --git a/components/icons/CopyIcon.tsx b/components/icons/CopyIcon.tsx
deleted file mode 100644
index f3d2611..0000000
--- a/components/icons/CopyIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function CopyIcon({ className = 'size-4' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/FaqQuestionMarkIcon.tsx b/components/icons/FaqQuestionMarkIcon.tsx
deleted file mode 100644
index fbfbf47..0000000
--- a/components/icons/FaqQuestionMarkIcon.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/** Orange «؟» mark used on consumer event FAQ rows. */
-export default function FaqQuestionMarkIcon({ className = 'size-5' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/FireIcon.tsx b/components/icons/FireIcon.tsx
deleted file mode 100644
index 308f7b9..0000000
--- a/components/icons/FireIcon.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-export default function FireIcon({ className = 'size-[120px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/GoogleMapsLogoIcon.tsx b/components/icons/GoogleMapsLogoIcon.tsx
deleted file mode 100644
index ac8dad3..0000000
--- a/components/icons/GoogleMapsLogoIcon.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-export default function GoogleMapsLogoIcon({ className = 'size-6' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/HeartIcon.tsx b/components/icons/HeartIcon.tsx
deleted file mode 100644
index d99afc1..0000000
--- a/components/icons/HeartIcon.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export default function HeartIcon({ className = 'size-6' }) {
- return (
-
- )
-}
diff --git a/components/icons/HeatIcon.tsx b/components/icons/HeatIcon.tsx
deleted file mode 100644
index 161973e..0000000
--- a/components/icons/HeatIcon.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export default function HeatIcon({ className = 'size-6' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/HomeFillIcon.tsx b/components/icons/HomeFillIcon.tsx
deleted file mode 100644
index 1b9f1d5..0000000
--- a/components/icons/HomeFillIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function HomeFillIcon({ className = 'size-[21px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/HomeHashtagTwoToneIcon.tsx b/components/icons/HomeHashtagTwoToneIcon.tsx
deleted file mode 100644
index cbe32ea..0000000
--- a/components/icons/HomeHashtagTwoToneIcon.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { cn } from '@/lib/cn'
-
-interface HomeHashtagTwoToneIconProps {
- className?: string
- /** رنگ لایه اصلی — مثلاً `text-gray-800` */
- primaryClassName?: string
- /** رنگ لایه ثانویه — مثلاً `text-primary`؛ پیشفرض opacity-40 مثل Iconsax */
- secondaryClassName?: string
-}
-
-export default function HomeHashtagTwoToneIcon({
- className = 'size-6',
- primaryClassName,
- secondaryClassName,
-}: HomeHashtagTwoToneIconProps) {
- return (
-
- )
-}
diff --git a/components/icons/HomeTextIcon.tsx b/components/icons/HomeTextIcon.tsx
deleted file mode 100644
index b578b9e..0000000
--- a/components/icons/HomeTextIcon.tsx
+++ /dev/null
@@ -1,334 +0,0 @@
-export default function HomeTextIcon({ className = 'size-[300px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/IdentityIcon.tsx b/components/icons/IdentityIcon.tsx
deleted file mode 100644
index ff7e05f..0000000
--- a/components/icons/IdentityIcon.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-export default function IdentityIcon() {
- return (
-
- )
-}
diff --git a/components/icons/InfoIcon.tsx b/components/icons/InfoIcon.tsx
deleted file mode 100644
index 6f70979..0000000
--- a/components/icons/InfoIcon.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-export default function InfoIcon({ className = 'size-[15px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/LinkIcon.tsx b/components/icons/LinkIcon.tsx
deleted file mode 100644
index 8473046..0000000
--- a/components/icons/LinkIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function LinkIcon({ className = 'size-5' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/LoginTwoToneIcon.tsx b/components/icons/LoginTwoToneIcon.tsx
deleted file mode 100644
index f92302c..0000000
--- a/components/icons/LoginTwoToneIcon.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { cn } from '@/lib/cn'
-
-interface LoginTwoToneIconProps {
- className?: string
- /** رنگ لایه اصلی — مثلاً `text-gray-800` */
- primaryClassName?: string
- /** رنگ لایه ثانویه — مثلاً `text-primary`؛ پیشفرض opacity-40 مثل Iconsax */
- secondaryClassName?: string
-}
-
-export default function LoginTwoToneIcon({ className = 'size-6', primaryClassName, secondaryClassName }: LoginTwoToneIconProps) {
- return (
-
- )
-}
diff --git a/components/icons/LogoIcon.tsx b/components/icons/LogoIcon.tsx
deleted file mode 100644
index 830a843..0000000
--- a/components/icons/LogoIcon.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-export default function LogoIcon({ className = 'size-[200px]', color = 'currentColor' }: { className?: string; color?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/MessageIcon.tsx b/components/icons/MessageIcon.tsx
deleted file mode 100644
index 7fe204b..0000000
--- a/components/icons/MessageIcon.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-export default function MessageIcon({ className = 'size-[21.5px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/MessagesTwoToneIcon.tsx b/components/icons/MessagesTwoToneIcon.tsx
deleted file mode 100644
index 79adc61..0000000
--- a/components/icons/MessagesTwoToneIcon.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { cn } from '@/lib/cn'
-
-interface MessagesTwoToneIconProps {
- className?: string
- /** رنگ لایه اصلی — مثلاً `text-gray-800` */
- primaryClassName?: string
- /** رنگ لایه ثانویه — مثلاً `text-primary`؛ پیشفرض opacity-40 مثل Iconsax */
- secondaryClassName?: string
-}
-
-export default function MessagesTwoToneIcon({ className = 'size-6', primaryClassName, secondaryClassName }: MessagesTwoToneIconProps) {
- return (
-
- )
-}
diff --git a/components/icons/MuteIcon.tsx b/components/icons/MuteIcon.tsx
deleted file mode 100644
index 093432f..0000000
--- a/components/icons/MuteIcon.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export default function MuteIcon({ className = 'size-6' }) {
- return (
-
- )
-}
diff --git a/components/icons/NeshanLogoIcon.tsx b/components/icons/NeshanLogoIcon.tsx
deleted file mode 100644
index 2c79de7..0000000
--- a/components/icons/NeshanLogoIcon.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-export default function NeshanLogoIcon({ className = 'size-6' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/OverviewIcon.tsx b/components/icons/OverviewIcon.tsx
deleted file mode 100644
index ec78fd0..0000000
--- a/components/icons/OverviewIcon.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-export default function OverviewIcon({ className = 'size-6' }) {
- return (
-
- )
-}
diff --git a/components/icons/PaymentCardIcon.tsx b/components/icons/PaymentCardIcon.tsx
deleted file mode 100644
index 9bdbc07..0000000
--- a/components/icons/PaymentCardIcon.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export default function PaymentCardIcon({ className = 'size-4' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/QuestionMarkIcon.tsx b/components/icons/QuestionMarkIcon.tsx
deleted file mode 100644
index cb1de9f..0000000
--- a/components/icons/QuestionMarkIcon.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export default function QuestionMarkIcon({ className = 'size-6' }) {
- return (
-
- )
-}
diff --git a/components/icons/SemiColonIcon.tsx b/components/icons/SemiColonIcon.tsx
deleted file mode 100644
index a84dcb3..0000000
--- a/components/icons/SemiColonIcon.tsx
+++ /dev/null
@@ -1,234 +0,0 @@
-export default function SemiColonIcon() {
- return (
-
- )
-}
diff --git a/components/icons/SendIcon.tsx b/components/icons/SendIcon.tsx
deleted file mode 100644
index 91a8c90..0000000
--- a/components/icons/SendIcon.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-export default function SendIcon({ className = 'size-6' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/SettingsIcon.tsx b/components/icons/SettingsIcon.tsx
deleted file mode 100644
index 5813513..0000000
--- a/components/icons/SettingsIcon.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export default function SettingsIcon({ className = 'size-5' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/ShareIcon.tsx b/components/icons/ShareIcon.tsx
deleted file mode 100644
index 345c886..0000000
--- a/components/icons/ShareIcon.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-export default function ShareIcon({ className = 'size-6' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/TagIcon.tsx b/components/icons/TagIcon.tsx
deleted file mode 100644
index b4a92c5..0000000
--- a/components/icons/TagIcon.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-export default function TagIcon({ className = 'size-4' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/TicketDiscountIcon.tsx b/components/icons/TicketDiscountIcon.tsx
deleted file mode 100644
index d773176..0000000
--- a/components/icons/TicketDiscountIcon.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export default function TicketStarIcon({ className = 'h-[17.5px] w-[21.5px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/TicketDiscountTwoToneIcon.tsx b/components/icons/TicketDiscountTwoToneIcon.tsx
deleted file mode 100644
index 9be9792..0000000
--- a/components/icons/TicketDiscountTwoToneIcon.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { cn } from '@/lib/cn'
-
-interface TicketDiscountTwoToneIconProps {
- className?: string
- /** رنگ لایه اصلی — مثلاً `text-gray-800` */
- primaryClassName?: string
- /** رنگ لایه ثانویه — مثلاً `text-primary`؛ پیشفرض opacity-40 مثل Iconsax */
- secondaryClassName?: string
-}
-
-export default function TicketDiscountTwoToneIcon({
- className = 'size-6',
- primaryClassName,
- secondaryClassName,
-}: TicketDiscountTwoToneIconProps) {
- return (
-
- )
-}
diff --git a/components/icons/TicketStarIcon.tsx b/components/icons/TicketStarIcon.tsx
deleted file mode 100644
index a45b0e5..0000000
--- a/components/icons/TicketStarIcon.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-export default function TicketStarIcon({ className = 'h-[17.5px] w-[21.5px]' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/TriangleLeftIcon.tsx b/components/icons/TriangleLeftIcon.tsx
deleted file mode 100644
index d2d753f..0000000
--- a/components/icons/TriangleLeftIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function TriangleLeftIcon({ className = 'size-3' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/UserPlusIcon.tsx b/components/icons/UserPlusIcon.tsx
deleted file mode 100644
index d9f7c27..0000000
--- a/components/icons/UserPlusIcon.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-export default function UserPlusIcon({ className = 'size-4' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/UserTwoToneIcon.tsx b/components/icons/UserTwoToneIcon.tsx
deleted file mode 100644
index 7ec1e2f..0000000
--- a/components/icons/UserTwoToneIcon.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { cn } from '@/lib/cn'
-
-interface UserTwoToneIconProps {
- className?: string
- /** رنگ لایه اصلی — مثلاً `text-gray-800` */
- primaryClassName?: string
- /** رنگ لایه ثانویه — مثلاً `text-primary`؛ پیشفرض opacity-40 مثل Iconsax */
- secondaryClassName?: string
-}
-
-export default function UserTwoToneIcon({ className = 'size-6', primaryClassName, secondaryClassName }: UserTwoToneIconProps) {
- return (
-
- )
-}
diff --git a/components/icons/UsersIcon.tsx b/components/icons/UsersIcon.tsx
deleted file mode 100644
index 6d50dbd..0000000
--- a/components/icons/UsersIcon.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function UsersIcon({ className = 'size-4' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/VerifiedBadgeIcon.tsx b/components/icons/VerifiedBadgeIcon.tsx
deleted file mode 100644
index ddf0902..0000000
--- a/components/icons/VerifiedBadgeIcon.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-export default function VerifiedBadgeIcon({ className = 'size-5' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/icons/VerticalDotsIcon.tsx b/components/icons/VerticalDotsIcon.tsx
deleted file mode 100644
index ba91e07..0000000
--- a/components/icons/VerticalDotsIcon.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-export default function VerticalDotsIcon({ className = 'size-6' }) {
- return (
-
- )
-}
diff --git a/components/icons/WalletIcon.tsx b/components/icons/WalletIcon.tsx
deleted file mode 100644
index 84376d6..0000000
--- a/components/icons/WalletIcon.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-export default function AngleDownIcon({ className = 'size-10' }: { className?: string }) {
- return (
-
- )
-}
diff --git a/components/reviews/StarRating.tsx b/components/reviews/StarRating.tsx
deleted file mode 100644
index f2b40b1..0000000
--- a/components/reviews/StarRating.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-'use client'
-
-import Button from '@/components/formElements/Button'
-import HeatIcon from '@/components/icons/HeatIcon'
-import HeatIconFill from '@/components/icons/HeatIconFill'
-import { cn } from '@/lib/cn'
-import { texts, format } from '@/texts'
-
-interface StarRatingProps {
- value: number
- onChange?: (value: number) => void
- size?: 'sm' | 'md'
- className?: string
- /** Read-only display when onChange is omitted. */
- label?: string
-}
-
-const ICON_SIZE = {
- sm: 'size-3.5',
- md: 'size-5',
-} as const
-
-const StarRating = ({ value, onChange, size = 'md', className, label }: StarRatingProps) => {
- const interactive = typeof onChange === 'function'
- const iconClass = ICON_SIZE[size]
-
- return (
-
- {[1, 2, 3, 4, 5].map((star) => {
- const filled = star <= value
- const Icon = filled ? HeatIconFill : HeatIcon
- const colorClass = filled ? 'text-primary' : 'text-tertiary-900/35'
-
- if (!interactive) {
- return (
-
- )
- }
-
- return (
-
- )
- })}
-
- )
-}
-
-export default StarRating
diff --git a/config/enamad.ts b/config/enamad.ts
deleted file mode 100644
index 8047dd3..0000000
--- a/config/enamad.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/** Enamad trust seal id (panel) — must match footer badge. */
-export const ENAMAD_ID = '7400030'
-
-/** Enamad trust seal code — used in footer links/images only (not the meta tag). */
-export const ENAMAD_CODE = 'TRxYXZLDTi8LQuOIZe8Z56sZ3pS3baMp'
-
-export const ENAMAD_URL = `https://trustseal.enamad.ir/?id=${ENAMAD_ID}&Code=${ENAMAD_CODE}`
-
-/** Self-hosted badge (avoids ORB/blocks on trustseal logo.aspx). Link still goes to ENAMAD_URL. */
-export const ENAMAD_IMAGE_URL = '/images/enamad.png'
diff --git a/constants/contact.ts b/constants/contact.ts
deleted file mode 100644
index 4f5f5d9..0000000
--- a/constants/contact.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Single source of truth for the platform's public contact number.
- * Was previously hardcoded independently in 5 places (frontend/app/(public)/{layout.tsx,contact/page.tsx},
- * frontend/components/public/{ContactForm.tsx,PublicFooter.tsx}, frontend/e2e/shared/public-pages.spec.ts)
- * -- a number change would have needed a coordinated grep-and-replace across all of them.
- */
-export const SITE_PHONE = '+989153641196'
-
-/** Digits-only form, for `tel:`/`wa.me` links that don't want the leading `+`. */
-export const SITE_PHONE_DIGITS = SITE_PHONE.slice(1)
-
-export const SITE_WHATSAPP_URL = (text: string) => `https://wa.me/${SITE_PHONE_DIGITS}?text=${text}`
-
-/** Official support profiles — used in footer + Organization `sameAs`. */
-export const SITE_INSTAGRAM_URL = 'https://www.instagram.com/Ghabilee_support/'
-export const SITE_TELEGRAM_URL = 'https://t.me/Ghabilee_support'
-
-export const SITE_SOCIAL_SAME_AS = [SITE_INSTAGRAM_URL, SITE_TELEGRAM_URL] as const
diff --git a/hooks/useCopyToClipboard.ts b/hooks/useCopyToClipboard.ts
deleted file mode 100644
index 9a5d8cc..0000000
--- a/hooks/useCopyToClipboard.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { addToast } from '@/lib/toast'
-
-/**
- * Copies `text` to the clipboard and shows a success/failure toast.
- * Was hand-copied (identical try/writeText/toast/catch shape) in
- * BookingActionsModal.tsx#handleCopyCode and HostedEventActionsModal.tsx#handleShare —
- * this is the shared home for any future "copy X" feature.
- */
-const useCopyToClipboard = () => {
- const copyToClipboard = async (text: string, messages: { successMessage: string; failureMessage: string }) => {
- try {
- await navigator.clipboard.writeText(text)
- addToast({ title: messages.successMessage, color: 'success' })
-
- return true
- } catch {
- addToast({ title: messages.failureMessage, color: 'danger' })
-
- return false
- }
- }
-
- return { copyToClipboard }
-}
-
-export default useCopyToClipboard
diff --git a/hooks/useDeferredClientMount.ts b/hooks/useDeferredClientMount.ts
deleted file mode 100644
index 805bf7d..0000000
--- a/hooks/useDeferredClientMount.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-'use client'
-
-import { useEffect, useState } from 'react'
-
-/** Default delay — keeps deferred chrome out of the LCP window on cold loads. */
-export const DEFERRED_CHROME_DELAY_MS = 4000
-
-/**
- * Returns true after `delayMs` so non-critical client UI (PWA banner, etc.) mounts
- * after the first paint / LCP candidates.
- */
-export function useDeferredClientMount(delayMs = DEFERRED_CHROME_DELAY_MS) {
- const [ready, setReady] = useState(false)
-
- useEffect(() => {
- const timeoutId = window.setTimeout(() => {
- setReady(true)
- }, delayMs)
-
- return () => {
- window.clearTimeout(timeoutId)
- }
- }, [delayMs])
-
- return ready
-}
diff --git a/lib/bookingPayment.ts b/lib/bookingPayment.ts
deleted file mode 100644
index 9a274db..0000000
--- a/lib/bookingPayment.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-type BookingExpiresAt = string | Date | Record | null | undefined
-
-const bookingExpiresAtMs = (expiresAt: BookingExpiresAt): number | null => {
- if (expiresAt == null) return null
- if (typeof expiresAt === 'string' || expiresAt instanceof Date) {
- const ms = new Date(expiresAt).getTime()
-
- return Number.isNaN(ms) ? null : ms
- }
-
- return null
-}
-
-/** Whether a pending_payment booking's hold window has passed (client clock). */
-export const isBookingPaymentExpired = (booking: { status: string; expiresAt?: BookingExpiresAt }): boolean => {
- const expiresAtMs = bookingExpiresAtMs(booking.expiresAt)
-
- return booking.status === 'pending_payment' && expiresAtMs != null && expiresAtMs <= Date.now()
-}
diff --git a/lib/chatSocket.ts b/lib/chatSocket.ts
deleted file mode 100644
index b3e99d3..0000000
--- a/lib/chatSocket.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import type { Socket } from 'socket.io-client'
-
-import { CHAT_SOCKET_NAMESPACE, CHAT_SOCKET_URL } from '@/config/chatSocket'
-
-let socket: Socket | null = null
-let connectAbortNonce = 0
-
-export interface ChatMessageEvent {
- id: string
- conversationId: string
- senderId: string
- senderFirstName: string | null
- senderLastName: string | null
- senderAvatarUrl?: string | null
- senderGender?: string | null
- body: string | null
- imageUrl: string | null
- createdAt: string
-}
-
-export interface ConversationUpdatedEvent {
- conversationId: string
- lastMessageAt: string
- preview: string | null
- unreadCount?: number
-}
-
-export const getChatSocket = () => socket
-
-/**
- * Connect chat socket on-demand.
- *
- * Important: `socket.io-client` must be loaded lazily to keep anonymous
- * guest first-load bundles small.
- */
-export const connectChatSocket = async (accessToken: string): Promise => {
- if (socket?.connected) {
- // Keep the stored auth token current even while already connected: if
- // this connection later drops (network blip, server restart) and
- // socket.io-client auto-reconnects, it replays whatever is in
- // `socket.auth` at that time — a stale token here would make every
- // reconnect after the token's ~15min TTL fail silently.
- socket.auth = { token: accessToken }
-
- return socket
- }
-
- if (socket) {
- socket.auth = { token: accessToken }
- socket.connect()
-
- return socket
- }
-
- const nonce = ++connectAbortNonce
- const mod = await import('socket.io-client')
- const { io } = mod
-
- // Another tab/navigation/token change can call disconnectChatSocket while
- // this module is still loading.
- if (nonce !== connectAbortNonce) {
- throw new Error('CHAT_SOCKET_CONNECT_ABORTED')
- }
-
- socket = io(`${CHAT_SOCKET_URL}${CHAT_SOCKET_NAMESPACE}`, {
- transports: ['websocket'],
- autoConnect: true,
- auth: { token: accessToken },
- })
-
- return socket
-}
-
-export const disconnectChatSocket = () => {
- if (!socket) return
-
- connectAbortNonce += 1
- socket.removeAllListeners()
- socket.disconnect()
- socket = null
-}
-
-export interface JoinConversationAck {
- ok: boolean
- error?: string
- conversationId?: string
-}
-
-export const joinConversationRoom = (conversationId: string): Promise => {
- const activeSocket = socket
-
- if (!activeSocket) return Promise.resolve({ ok: false, error: 'NOT_CONNECTED' })
-
- return new Promise((resolve) => {
- activeSocket.timeout(5000).emit('conversation:join', { conversationId }, (error: Error | null, ack?: JoinConversationAck) => {
- if (error) {
- resolve({ ok: false, error: 'ACK_TIMEOUT' })
-
- return
- }
-
- resolve(ack ?? { ok: false, error: 'NO_ACK' })
- })
- })
-}
-
-export const leaveConversationRoom = (conversationId: string) => {
- socket?.emit('conversation:leave', { conversationId })
-}
-
-export const subscribeChatEvents = (handlers: {
- onMessage?: (message: ChatMessageEvent) => void
- onConversationUpdated?: (payload: ConversationUpdatedEvent) => void
-}) => {
- if (!socket) return () => undefined
-
- const messageHandler = (payload: { message: ChatMessageEvent }) => {
- handlers.onMessage?.(payload.message)
- }
-
- const conversationHandler = (payload: ConversationUpdatedEvent) => {
- handlers.onConversationUpdated?.(payload)
- }
-
- socket.on('message:new', messageHandler)
- socket.on('conversation:updated', conversationHandler)
-
- return () => {
- socket?.off('message:new', messageHandler)
- socket?.off('conversation:updated', conversationHandler)
- }
-}
diff --git a/lib/clientSessionHint.ts b/lib/clientSessionHint.ts
deleted file mode 100644
index 2fff7cd..0000000
--- a/lib/clientSessionHint.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import Cookies from 'js-cookie'
-
-/**
- * Synchronous client-only hint that a consumer session likely exists.
- * Used to suppress guest marketing / nationwide feed flash before AuthContext
- * finishes hydrating from localStorage (CLS on `/`).
- *
- * Returns false during SSR — guest ISR HTML stays unchanged.
- */
-export function hasClientSessionHint(): boolean {
- if (typeof window === 'undefined') return false
-
- return Boolean(Cookies.get('accessToken') || localStorage.getItem('user'))
-}
diff --git a/lib/paymentQuery.ts b/lib/paymentQuery.ts
deleted file mode 100644
index c594d35..0000000
--- a/lib/paymentQuery.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-'use client'
-
-/**
- * Shared helpers for consumer payment query keys on the current URL.
- * Used by EventPaymentModalController and EventDetailOverlay dismiss cleanup.
- */
-
-export const PAYMENT_QUERY_KEYS = ['payment', 'bookingId', 'depositId', 'status'] as const
-
-export const stripPaymentQueryParams = (search: string): string => {
- const params = new URLSearchParams(search)
-
- for (const key of PAYMENT_QUERY_KEYS) {
- params.delete(key)
- }
-
- return params.toString()
-}
-
-export const hasActivePaymentQuery = (search: string): boolean => {
- const params = new URLSearchParams(search)
- const mode = params.get('payment')
-
- return mode === 'checkout' || mode === 'result'
-}
diff --git a/lib/server/eventManagementBootstrap.ts b/lib/server/eventManagementBootstrap.ts
deleted file mode 100644
index 0f6ebdc..0000000
--- a/lib/server/eventManagementBootstrap.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { EventManagementBootstrap } from '@/services/eventManagement'
-
-type ApiEnvelope = { success: true; data: T } | { success: false; message: string }
-
-const resolveApiBaseUrl = () => {
- const raw = process.env.NEXT_PUBLIC_API_URL
-
- if (!raw) throw new Error('NEXT_PUBLIC_API_URL is not configured')
- const trimmed = raw.replace(/\/+$/, '')
-
- return trimmed.endsWith('/api/v1') ? trimmed : `${trimmed}/api/v1`
-}
-
-/** Private owner data: never enters the public Next.js or persistent browser cache. */
-export async function fetchEventManagementBootstrapForOwner(
- eventId: string,
- accessToken: string
-): Promise {
- const response = await fetch(`${resolveApiBaseUrl()}/events/${encodeURIComponent(eventId)}/management-bootstrap`, {
- cache: 'no-store',
- headers: { Authorization: `Bearer ${accessToken}` },
- })
-
- if (response.status === 401 || response.status === 403 || response.status === 404) return null
- if (!response.ok) throw new Error(`Management bootstrap failed: ${response.status}`)
-
- const body = (await response.json()) as ApiEnvelope
-
- return body.success ? body.data : null
-}
diff --git a/next.config.js b/next.config.js
index 1c0f61e..81fb8b4 100644
--- a/next.config.js
+++ b/next.config.js
@@ -87,7 +87,7 @@ const nextConfig = {
...(basePath ? { basePath, assetPrefix: basePath } : {}),
allowedDevOrigins: ['192.168.100.51', '192.168.100.52'],
- transpilePackages: ['mapir-react-component', 'react-mapbox-gl', 'arcaptcha-react'],
+ transpilePackages: ['mapir-react-component', 'react-mapbox-gl'],
env: {
NEXT_PUBLIC_MAP_API_KEY: process.env.MAP_API_KEY || process.env.NEXT_PUBLIC_MAP_API_KEY || '',
diff --git a/package.json b/package.json
index 1230f1a..a73da19 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,6 @@
"@tiptap/react": "^3.11.0",
"@tiptap/starter-kit": "^3.11.0",
"@types/jalaali-js": "^1.2.0",
- "arcaptcha-react": "^0.4.0",
"axios": "1.18.1",
"clsx": "2.1.1",
"dayjs": "^1.11.19",
diff --git a/services/chat.ts b/services/chat.ts
deleted file mode 100644
index e1063d4..0000000
--- a/services/chat.ts
+++ /dev/null
@@ -1,354 +0,0 @@
-import type {
- ConversationParticipantResponseDto,
- ConversationResponseDto,
- MessageReplyToDto,
- MessageResponseDto,
- SendMessageDto,
-} from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getChat } from '@/api/generated/chat/chat'
-import { texts } from '@/texts'
-import { parseRemittanceList } from '@/helpers/listResponse'
-
-export type Conversation = ConversationResponseDto
-
-export type ChatMessage = MessageResponseDto
-
-export type ChatMessageReplyTo = MessageReplyToDto
-
-export type ConversationTypeFilter = 'direct' | 'event_group'
-
-export const CONVERSATIONS_PAGE_SIZE = 20
-
-/** Direction-based history page (same field meaning in before / after / around modes). */
-export interface ChatMessagesPage {
- items: ChatMessage[]
- olderCursor: string | null
- newerCursor: string | null
- hasMoreBefore: boolean
- hasMoreAfter: boolean
- aroundMessageId: string | null
-}
-
-export interface ListMessagesParams {
- before?: string
- after?: string
- around?: string
- pageSize?: number
-}
-
-export type SendChatMessagePayload = Pick
-
-export interface ConversationsPage {
- items: Conversation[]
- totalItemsCount: number
-}
-
-const chatApi = getChat()
-
-export const LIST_CONVERSATIONS = async (
- params?: { type?: ConversationTypeFilter; page?: number; pageSize?: number },
- options?: ServiceCallOptions
-): Promise> => {
- const page = params?.page ?? 1
- const pageSize = params?.pageSize ?? CONVERSATIONS_PAGE_SIZE
-
- try {
- const res = await chatApi.chatControllerListMine({
- page,
- pageSize,
- sort: '-lastMessageAt',
- ...(params?.type ? { 'filters[type]': params.type } : {}),
- })
-
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.listConversationsFailed)
- }
-
- const parsed = parseRemittanceList(unwrapApiData(payload) ?? {}, 'items', page, pageSize)
-
- return successResult({ items: parsed.items, totalItemsCount: parsed.pagination.totalItemsCount })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const GET_CONVERSATION = async (conversationId: string, options?: ServiceCallOptions): Promise> => {
- try {
- const res = await chatApi.chatControllerGetOne(conversationId)
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.getConversationFailed)
- }
-
- const conversation = unwrapApiData(payload)
-
- if (!conversation?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(conversation)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const GET_UNREAD_CHAT_COUNT = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await chatApi.chatControllerUnreadCount()
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.unreadCountFailed)
- }
-
- const data = unwrapApiData(payload)
-
- return successResult({ totalUnread: data?.totalUnread ?? 0 })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const MARK_CONVERSATION_READ = async (
- conversationId: string,
- lastSeenMessageId: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await chatApi.chatControllerMarkRead(conversationId, { lastSeenMessageId })
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.markReadFailed)
- }
-
- const data = unwrapApiData(payload)
-
- return successResult({ totalUnread: data?.totalUnread ?? 0 })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-const MESSAGES_PAGE_SIZE = 50
-
-/**
- * Fetches one page of conversation history.
- *
- * Cursor modes are mutually exclusive (`before` | `after` | `around`). Response
- * cursors are direction-based: `olderCursor` → next `before`, `newerCursor` →
- * next `after`, regardless of which mode produced the page.
- */
-export const LIST_MESSAGES = async (
- conversationId: string,
- params?: ListMessagesParams,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const pageSize = params?.pageSize ?? MESSAGES_PAGE_SIZE
- const res = await chatApi.chatControllerListMessages(conversationId, {
- pageSize,
- ...(params?.before ? { before: params.before } : {}),
- ...(params?.after ? { after: params.after } : {}),
- ...(params?.around ? { around: params.around } : {}),
- })
-
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.listMessagesFailed)
- }
-
- const data = unwrapApiData(payload)
- const parsed = parseRemittanceList(data ?? {}, 'items', 1, pageSize)
- const meta = data?.response
-
- return successResult({
- items: parsed.items,
- olderCursor: meta?.olderCursor ?? null,
- newerCursor: meta?.newerCursor ?? null,
- hasMoreBefore: meta?.hasMoreBefore ?? false,
- hasMoreAfter: meta?.hasMoreAfter ?? false,
- aroundMessageId: meta?.aroundMessageId ?? null,
- })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const SEND_MESSAGE = async (
- conversationId: string,
- payload: SendChatMessagePayload,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await chatApi.chatControllerSendGroup(conversationId, payload)
-
- const responsePayload = res.data
-
- if (!responsePayload.success) {
- throw createServiceError(responsePayload.message || texts.chats.sendFailed, responsePayload.code)
- }
-
- const message = unwrapApiData(responsePayload)
-
- if (!message?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(message)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-/**
- * Sends the first (or a subsequent) message of a direct conversation by the
- * other participant's user id, without needing a conversationId up front.
- * Backend resolves/creates the canonical direct conversation for the pair
- * (`get_or_create_direct_conversation`) and inserts the message in one call —
- * see docs/workflows/fa/chat.md, "جریان A — پیام مستقیم".
- */
-export const SEND_DIRECT_MESSAGE = async (
- otherUserId: string,
- payload: SendChatMessagePayload,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await chatApi.chatControllerSendDirect(otherUserId, payload)
-
- const responsePayload = res.data
-
- if (!responsePayload.success) {
- throw createServiceError(responsePayload.message || texts.chats.sendFailed, responsePayload.code)
- }
-
- const message = unwrapApiData(responsePayload)
-
- if (!message?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(message)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const ENSURE_DIRECT_CONVERSATION = async (
- otherUserId: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await chatApi.chatControllerEnsureDirect(otherUserId)
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.openConversationFailed, payload.code)
- }
-
- const conversation = unwrapApiData(payload)
-
- if (!conversation?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(conversation)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export type ChatParticipant = ConversationParticipantResponseDto
-
-export const LIST_PARTICIPANTS = async (
- conversationId: string,
- params?: { page?: number; pageSize?: number },
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await chatApi.chatControllerListParticipants(conversationId, {
- page: params?.page ?? 1,
- pageSize: params?.pageSize ?? 100,
- sort: 'joinedAt',
- })
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.listParticipantsFailed)
- }
-
- const parsed = parseRemittanceList(unwrapApiData(payload) ?? {}, 'items')
-
- return successResult({ items: parsed.items })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/contactMessages.ts b/services/contactMessages.ts
deleted file mode 100644
index 17f22b0..0000000
--- a/services/contactMessages.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import axiosInstance from '@/config/axios'
-import { texts } from '@/texts'
-import { API_ROUTES } from '@/services/config'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData, type ApiResponse } from '@/services/apiResponse'
-
-export interface ContactMessage {
- id: string
- fullName: string
- mobile: string
- subject: string
- message: string
- readAt: string | null
- readBy: string | null
- createdAt: string
-}
-
-export interface CreateContactMessagePayload {
- fullName: string
- mobile: string
- subject: string
- message: string
- arcaptchaToken: string
-}
-
-export const CREATE_CONTACT_MESSAGE = async (
- payload: CreateContactMessagePayload,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await axiosInstance.post>(API_ROUTES.CONTACT_MESSAGES.CREATE, payload)
- const body = res.data
-
- if (!body.success) {
- throw createServiceError(body.message || texts.chats.sendFailed)
- }
-
- const data = unwrapApiData(body)
-
- if (!data?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(data)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/identity.ts b/services/identity.ts
deleted file mode 100644
index e6ac533..0000000
--- a/services/identity.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import { z } from 'zod'
-
-import {
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiDataUnknown, unwrapApiPayload } from '@/helpers/listResponse'
-import { ANALYTICS_EVENTS, trackAnalyticsEventOnce } from '@/lib/analytics'
-import { API_ROUTES } from '@/services/config'
-import axiosInstance from '@/config/axios'
-import { texts } from '@/texts'
-
-export interface MyIdentityVerification {
- id: string
- status: 'pending' | 'verified' | 'rejected'
- nationalCode: string | null
- contractVersion?: string | null
- contractAcceptedAt?: string | null
- contractAcceptanceId?: string | null
- reason?: { code: string; label: string } | null
- rejectionReason?: string | null
- createdAt: string
- reviewedAt?: string | null
-}
-
-export interface IdentityRejectionReason {
- id: number
- code: string
- label: string
- sortOrder: number
-}
-
-export interface RequestHostContractOtpPayload {
- nationalCode: string
- contractAccepted: true
- contractVersion: string
-}
-
-const identityVerificationSchema = z.object({
- id: z.string().min(1),
- status: z.enum(['pending', 'verified', 'rejected']),
- nationalCode: z.string().nullable().optional(),
- contractVersion: z.string().nullable().optional(),
- contractAcceptedAt: z.string().nullable().optional(),
- contractAcceptanceId: z.string().nullable().optional(),
- reason: z.object({ code: z.string(), label: z.string() }).nullable().optional(),
- rejectionReason: z.string().nullable().optional(),
- createdAt: z.string(),
- reviewedAt: z.string().nullable().optional(),
-})
-
-const rejectionReasonSchema = z.object({
- id: z.number(),
- code: z.string(),
- label: z.string(),
- sortOrder: z.number(),
-})
-
-const otpRequestedSchema = z.object({
- purpose: z.literal('host_contract'),
- expiresIn: z.number(),
- contractVersion: z.string(),
-})
-
-export const GET_MY_IDENTITY_VERIFICATION = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await axiosInstance.get('identity/verifications/me')
- const payload = unwrapApiDataUnknown(res.data)
- const latest: unknown = Array.isArray(payload) ? payload[0] : payload
-
- if (!latest || typeof latest !== 'object' || !('id' in latest)) return successResult(null)
- const parsed = identityVerificationSchema.safeParse(latest)
-
- if (!parsed.success) throw new Error(texts.common.invalidServerResponse)
-
- return successResult({
- ...parsed.data,
- nationalCode: parsed.data.nationalCode ?? null,
- })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const REQUEST_HOST_CONTRACT_OTP = async (
- data: RequestHostContractOtpPayload,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await axiosInstance.post(API_ROUTES.IDENTITY.REQUEST_OTP, data)
- const parsed = otpRequestedSchema.safeParse(unwrapApiPayload(res.data))
-
- if (!parsed.success) throw new Error(texts.common.invalidServerResponse)
-
- return successResult(parsed.data)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const CONFIRM_HOST_CONTRACT_OTP = async (
- code: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await axiosInstance.post(API_ROUTES.IDENTITY.CONFIRM_OTP, { code })
- const parsed = identityVerificationSchema.safeParse(unwrapApiPayload(res.data))
-
- if (!parsed.success) throw new Error(texts.common.invalidServerResponse)
-
- trackAnalyticsEventOnce(ANALYTICS_EVENTS.IDENTITY_VERIFICATION_SUBMITTED, parsed.data.id, {
- verification_id: parsed.data.id,
- verification_status: parsed.data.status,
- })
-
- return successResult({
- ...parsed.data,
- nationalCode: parsed.data.nationalCode ?? null,
- })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const GET_IDENTITY_REJECTION_REASONS = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await axiosInstance.get(API_ROUTES.IDENTITY.REJECTION_REASONS)
- const payload = unwrapApiDataUnknown(res.data)
- const parsed = z.array(rejectionReasonSchema).safeParse(Array.isArray(payload) ? payload : [])
-
- if (!parsed.success) throw new Error(texts.common.invalidServerResponse)
-
- return successResult(parsed.data)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/myEvents.ts b/services/myEvents.ts
deleted file mode 100644
index b4e0d34..0000000
--- a/services/myEvents.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { MyEventsGuestItemDto, MyEventsHostItemDto, MyEventsListControllerList200, PaginationMetaDto } from '@/api/generated/models'
-import { getMyEvents } from '@/api/generated/my-events/my-events'
-import { unwrapApiPayload } from '@/helpers/listResponse'
-
-export const MY_EVENTS_PAGE_SIZE = 20
-
-export type MyEventsRole = 'guest' | 'host'
-export type MyEventsGuestStatus = 'upcoming' | 'held' | 'cancelled'
-export type MyEventsHostStatus = 'pending_review' | 'published' | 'rejected'
-export type MyEventsStatus = MyEventsGuestStatus | MyEventsHostStatus
-export type MyEventsGuestItem = MyEventsGuestItemDto
-export type MyEventsHostItem = MyEventsHostItemDto
-
-export interface MyEventsPage {
- role: MyEventsRole
- status: MyEventsStatus
- canHost: boolean
- availableRoles: MyEventsRole[]
- items: TItem[]
- response: PaginationMetaDto
-}
-
-const myEventsApi = getMyEvents()
-
-/** Fetches one server-filtered lifecycle page for the My Events screen. */
-export const fetchMyEventsPage = async ({
- role,
- status,
- page,
- pageSize = MY_EVENTS_PAGE_SIZE,
- signal,
-}: {
- role: MyEventsRole
- status: MyEventsStatus
- page: number
- pageSize?: number
- signal?: AbortSignal
-}): Promise> => {
- const response = await myEventsApi.myEventsListControllerList({ role, status, page, pageSize }, { signal })
- const payload = unwrapApiPayload(response.data)
-
- return payload as MyEventsPage
-}
diff --git a/services/notifications.ts b/services/notifications.ts
deleted file mode 100644
index 289b1e1..0000000
--- a/services/notifications.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import type { NotificationResponseDto } from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getNotifications } from '@/api/generated/notifications/notifications'
-import { parseRemittanceList } from '@/helpers/listResponse'
-import { texts } from '@/texts'
-
-export type InAppNotification = NotificationResponseDto
-
-const notificationsApi = getNotifications()
-
-export const LIST_NOTIFICATIONS = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await notificationsApi.notificationsControllerListMine({ page: 1, pageSize: 30, sort: '-createdAt' })
-
- const parsed = parseRemittanceList(res.data, 'items')
-
- return successResult({ items: parsed.items })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const GET_UNREAD_NOTIFICATION_COUNT = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await notificationsApi.notificationsControllerUnreadCount()
-
- if (!res.data.success) throw createServiceError(res.data.message)
- const payload = unwrapApiData(res.data)
- const totalUnread = Number(payload?.totalUnread ?? 0)
-
- return successResult({ totalUnread: Number.isFinite(totalUnread) ? totalUnread : 0 })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const MARK_NOTIFICATION_READ = async (
- notificationId: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await notificationsApi.notificationsControllerMarkAsRead(notificationId)
-
- if (!res.data.success) throw createServiceError(res.data.message)
- const payload = unwrapApiData(res.data)
-
- if (!payload?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(payload)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const countUnreadNotifications = (items: InAppNotification[]): number => items.filter((item) => !item.readAt).length
diff --git a/services/payments.ts b/services/payments.ts
deleted file mode 100644
index 27701d6..0000000
--- a/services/payments.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-import { z } from 'zod'
-
-import type {
- CreatePaymentResponseDto,
- PaymentCheckoutResponseDto,
- PaymentReceiptResponseDto,
- PaymentResponseDto,
-} from '@/api/generated/models'
-import type { BookingStatus } from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getBookings } from '@/api/generated/bookings/bookings'
-import { getEvents } from '@/api/generated/events/events'
-import { getFinancial } from '@/api/generated/financial/financial'
-import { unwrapApiPayload } from '@/helpers/listResponse'
-import { texts } from '@/texts'
-
-// docs/workflows/fa/payment.md — consumer checkout for a `pending_payment`
-// booking. Payment creation is a *second, separate* call from booking
-// creation (owned by saza2's services/bookings.ts) — this file only covers
-// the payment leg: reading the booking/event to render the checkout
-// screen, then POST /bookings/:bookingId/payments.
-
-export interface BookingForPayment {
- id: string
- eventId: string
- bookingCode: string
- status: BookingStatus
- expiresAt: string | null
-}
-
-export interface EventSummaryForPayment {
- id: string
- slug?: string | null
- title: string
- price: number
-}
-
-export type PaymentCheckout = PaymentCheckoutResponseDto
-
-export type Payment = PaymentResponseDto
-
-// Orval snapshot may lag backend fields returned at runtime for checkout.
-export type CreatePaymentResult = CreatePaymentResponseDto & {
- depositId?: string | null
- depositAmount?: number
- walletBalanceUsed?: number
-}
-
-export type PaymentReceipt = PaymentReceiptResponseDto
-
-const bookingForPaymentSchema = z.object({
- id: z.string().min(1),
- eventId: z.string().min(1),
- bookingCode: z.string(),
- status: z.enum(['pending_payment', 'confirmed', 'cancelled', 'expired', 'refunded', 'no_show']),
- expiresAt: z.string().nullable(),
-})
-
-const paymentReceiptSchema = z.object({
- id: z.string().min(1),
- paymentId: z.string().min(1),
- userId: z.string().min(1),
- bookingId: z.string().min(1),
- eventId: z.string().min(1),
- receiptCode: z.string(),
- snapshot: z.record(z.string(), z.unknown()),
- issuedAt: z.string(),
-})
-
-const bookingsApi = getBookings()
-const eventsApi = getEvents()
-const financialApi = getFinancial()
-
-export const GET_BOOKING_FOR_PAYMENT = async (
- bookingId: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await bookingsApi.bookingsControllerFindOne(bookingId)
- const parsed = bookingForPaymentSchema.safeParse(unwrapApiPayload(res.data))
-
- if (!parsed.success) throw createServiceError(texts.common.invalidServerResponse)
-
- return successResult(parsed.data)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-// Booking detail (GET /bookings/:id) intentionally doesn't carry the
-// event's price/title — only `eventId` — so the checkout screen needs a
-// second read against the public event-detail endpoint.
-export const GET_EVENT_SUMMARY = async (eventId: string, options?: ServiceCallOptions): Promise> => {
- try {
- const res = await eventsApi.eventsControllerFindOne(eventId)
- const payload = unwrapApiPayload(res.data)
-
- if (!payload?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult({
- id: payload.id as string,
- slug: typeof payload.slug === 'string' ? payload.slug : null,
- title: payload.title as string,
- price: Number(payload.price ?? 0),
- })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-// payment.md "تصمیم ۲": `walletAmount` is a single clamp-friendly number —
-// the backend clamps to min(requested, wallet.balance, totalAmount)
-// regardless, so the frontend just sends what the "use wallet" toggle
-// implies.
-export const CREATE_PAYMENT = async (
- bookingId: string,
- walletAmount: number,
- options?: ServiceCallOptions & { discountCode?: string }
-): Promise> => {
- try {
- const res = await financialApi.paymentsControllerCreate(bookingId, {
- walletAmount,
- discountCode: options?.discountCode?.trim() || undefined,
- })
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.wallet.paymentFailed)
- }
-
- const result = unwrapApiData(payload)
-
- if (!result?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(result)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const GET_PAYMENT_RECEIPT = async (paymentId: string, options?: ServiceCallOptions): Promise> => {
- try {
- const res = await financialApi.paymentsControllerReceipt(paymentId)
- const parsed = paymentReceiptSchema.safeParse(unwrapApiPayload(res.data))
-
- if (!parsed.success) throw createServiceError(texts.common.invalidServerResponse)
-
- return successResult(parsed.data)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/settlements.ts b/services/settlements.ts
deleted file mode 100644
index 3650dac..0000000
--- a/services/settlements.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import axiosInstance from '@/config/axios'
-import { unwrapApiPayload } from '@/helpers/listResponse'
-
-export interface SettlementRequest {
- id: string
- settlementCode: string
- organizerId: string
- bankAccountId: string
- totalAmount: number
- status: 'pending' | 'processing' | 'completed' | 'failed'
- processedAt?: string | null
- failureReason?: string | null
- manualTrackingCode?: string | null
- manualReceiptUrl?: string | null
- manualNote?: string | null
- createdAt: string
- updatedAt: string
-}
-
-export const LIST_MY_SETTLEMENTS = async (): Promise<{ items: SettlementRequest[] }> => {
- const response = await axiosInstance.get('settlements/me', {
- params: { page: 1, pageSize: 30, sort: '-createdAt' },
- })
- const payload = unwrapApiPayload<{ items?: SettlementRequest[] }>(response.data)
-
- return { items: Array.isArray(payload.items) ? payload.items : [] }
-}
diff --git a/services/userBlocks.ts b/services/userBlocks.ts
deleted file mode 100644
index 0380509..0000000
--- a/services/userBlocks.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import type { UserBlockResponseDto } from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getUserBlocks } from '@/api/generated/user-blocks/user-blocks'
-import { texts } from '@/texts'
-import { parseRemittanceList } from '@/helpers/listResponse'
-
-// docs/workflows/fa/user-blocking-reporting.md, Flow A — a directed block
-// record (`blocker_id` = current user, `blocked_id` = the other user).
-// Effect is mutual: once either side has a row against the other, new
-// *direct* messages between them are rejected in both directions. Event
-// group chats are never affected.
-
-export type UserBlock = UserBlockResponseDto
-
-const userBlocksApi = getUserBlocks()
-
-export const BLOCK_USER = async (blockedId: string, options?: ServiceCallOptions): Promise> => {
- try {
- const res = await userBlocksApi.userBlocksControllerCreate({ blockedId })
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.blockUserFailed, payload.code)
- }
-
- const block = unwrapApiData(payload)
-
- if (!block?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(block)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const UNBLOCK_USER = async (blockedId: string, options?: ServiceCallOptions): Promise> => {
- try {
- const res = await userBlocksApi.userBlocksControllerUnblock(blockedId)
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.unblockUserFailed, payload.code)
- }
-
- const block = unwrapApiData(payload)
-
- if (!block?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(block)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-/**
- * Checks whether the current user has an active block row against
- * `otherUserId`. Used to toggle the header menu between «مسدود کردن» and
- * «رفع مسدودیت» — there's no dedicated "is blocked" endpoint, so this
- * reuses `GET /user-blocks/me?filters[blockedId]=...` (paginated, so a
- * single-row page is enough to answer a yes/no question).
- *
- * Note: this only reflects blocks *I* created (`blocker_id = me`), not
- * ones the other person may have created against me — matching the known
- * gap documented in user-blocking-reporting.md ("هیچ endpoint «چه کسی مرا
- * مسدود کرده» وجود ندارد").
- */
-export const GET_MY_BLOCK_STATUS = async (
- otherUserId: string,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await userBlocksApi.userBlocksControllerListMine({ page: 1, pageSize: 1, 'filters[blockedId]': otherUserId })
-
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.blockStatusFailed)
- }
-
- const parsed = parseRemittanceList(unwrapApiData(payload) ?? {}, 'items')
-
- return successResult({ isBlocked: parsed.items.length > 0 })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/userReports.ts b/services/userReports.ts
deleted file mode 100644
index a85cfdb..0000000
--- a/services/userReports.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import type { CreateUserReportDto, ReportReasonResponseDto, UserReportResponseDto } from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getUserReports } from '@/api/generated/user-reports/user-reports'
-import { texts } from '@/texts'
-
-// docs/workflows/fa/user-blocking-reporting.md, Flow B — purely
-// informational: creating a report never blocks, hides, or notifies
-// anyone by itself. It only creates a `pending` row an admin reviews later.
-
-export type ReportReason = ReportReasonResponseDto
-
-export type UserReport = UserReportResponseDto
-
-const userReportsApi = getUserReports()
-
-/**
- * Public, unauthenticated — safe to fetch ahead of time (e.g. when the
- * report menu item is rendered) so the reason list is ready by the time
- * the user opens the report form.
- */
-export const LIST_REPORT_REASONS = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await userReportsApi.userReportsControllerListReportReasons()
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.chats.listReportReasonsFailed)
- }
-
- return successResult(unwrapApiData(payload) ?? [])
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-/**
- * At least one of `reasonId` / `description` is required — mirrors the
- * backend's `CreateUserReportDto` cross-field rule
- * (`user_reports_content_chk`).
- */
-export const REPORT_USER = async (
- reportedId: string,
- payload: { reasonId?: number; description?: string },
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await userReportsApi.userReportsControllerCreate({
- reportedId,
- ...payload,
- })
-
- const responsePayload = res.data
-
- if (!responsePayload.success) {
- throw createServiceError(responsePayload.message || texts.chats.submitReportFailed, responsePayload.code)
- }
-
- const report = unwrapApiData(responsePayload)
-
- if (!report?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(report)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-/**
- * Reports one specific chat message. The server derives and persists the
- * conversation, sender, and immutable message snapshot; the client cannot
- * forge those moderation fields.
- */
-export const REPORT_MESSAGE = async (
- reportedId: string,
- messageId: string,
- payload: { reasonId?: number; description?: string },
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const request: CreateUserReportDto = {
- reportedId,
- messageId,
- ...payload,
- }
- const res = await userReportsApi.userReportsControllerCreate(request)
- const responsePayload = res.data
-
- if (!responsePayload.success) {
- throw createServiceError(responsePayload.message || texts.chats.submitReportFailed, responsePayload.code)
- }
-
- const report = unwrapApiData(responsePayload)
-
- if (!report?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(report)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/services/withdrawals.ts b/services/withdrawals.ts
deleted file mode 100644
index 5a7176b..0000000
--- a/services/withdrawals.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import type { BankAccountOwnerType, BankAccountResponseDto, CreateBankAccountDto, WithdrawalResponseDto } from '@/api/generated/models'
-import {
- createServiceError,
- errorResult,
- handleServiceError,
- type ServiceCallOptions,
- type ServiceResult,
- shouldBubbleErrorToParent,
- successResult,
-} from '@/services/errorHandler'
-import { unwrapApiData } from '@/services/apiResponse'
-import { getFinancial } from '@/api/generated/financial/financial'
-import { parseRemittanceList, unwrapApiPayload } from '@/helpers/listResponse'
-import { ANALYTICS_EVENTS, trackAnalyticsEventOnce } from '@/lib/analytics'
-import { texts } from '@/texts'
-
-// docs/workflows/fa/settlement-withdrawal.md — Path A (self-service wallet
-// withdrawal). Distinct from admin settlement (Path B), which never
-// touches `wallets.balance` — out of scope here.
-
-export type { BankAccountOwnerType }
-export type BankAccount = BankAccountResponseDto & {
- verificationStatus?: 'pending_review' | 'approved' | 'rejected'
- rejectionReason?: string | null
- nationalCode?: string | null
-}
-export type CreateBankAccountPayload = CreateBankAccountDto & {
- nationalCode?: string
-}
-export type WithdrawalRequest = WithdrawalResponseDto
-
-const financialApi = getFinancial()
-
-export const LIST_BANK_ACCOUNTS = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await financialApi.bankAccountsControllerListMine()
- const payload = unwrapApiPayload(res.data)
- const items = Array.isArray(payload)
- ? payload
- : Array.isArray((payload as { items?: unknown[] }).items)
- ? (payload as { items: unknown[] }).items
- : []
-
- return successResult(items as BankAccount[])
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const CREATE_BANK_ACCOUNT = async (
- payload: CreateBankAccountPayload,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await financialApi.bankAccountsControllerCreate(payload)
- const body = res.data
-
- if (!body.success) {
- throw createServiceError(body.message || texts.wallet.bankAccountCreateFailed)
- }
-
- const result = unwrapApiData(body)
-
- if (!result?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- return successResult(result)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-export const LIST_WITHDRAWAL_REQUESTS = async (options?: ServiceCallOptions): Promise> => {
- try {
- const res = await financialApi.withdrawalRequestsControllerListMine({ page: 1, pageSize: 30, sort: '-createdAt' })
-
- const parsed = parseRemittanceList(res.data, 'items')
-
- return successResult({ items: parsed.items })
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
-
-// amount is in Toman (platform-wide convention — see payment.md's note on
-// the matching CreatePaymentDto Swagger typo; CreateWithdrawalRequestDto
-// has the same "Rials" typo in its Swagger annotation).
-export const CREATE_WITHDRAWAL_REQUEST = async (
- bankAccountId: string,
- amount: number,
- options?: ServiceCallOptions
-): Promise> => {
- try {
- const res = await financialApi.withdrawalRequestsControllerCreate({
- bankAccountId,
- amount,
- })
- const payload = res.data
-
- if (!payload.success) {
- throw createServiceError(payload.message || texts.wallet.withdrawalCreateFailed)
- }
-
- const result = unwrapApiData(payload)
-
- if (!result?.id) {
- throw createServiceError(texts.common.invalidServerResponse)
- }
-
- trackAnalyticsEventOnce(ANALYTICS_EVENTS.WITHDRAWAL_REQUESTED, result.id, {
- withdrawal_id: result.id,
- value: amount * 10,
- currency: 'IRR',
- withdrawal_status: result.status,
- })
-
- return successResult(result)
- } catch (error) {
- const normalizedError = handleServiceError(error, options)
-
- if (shouldBubbleErrorToParent(options)) {
- throw normalizedError
- }
-
- return errorResult(normalizedError)
- }
-}
diff --git a/validation/identity.ts b/validation/identity.ts
deleted file mode 100644
index ac2c5f8..0000000
--- a/validation/identity.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { z } from 'zod'
-
-import { convertPersianToEnglish } from '@/helpers'
-import { texts } from '@/texts'
-
-const toEnglishDigits = (value: string) => convertPersianToEnglish(value).trim()
-const v = texts.validation.identity
-const nationalId = texts.validation.nationalId
-
-export const IdentityVerificationFormValidation = z
- .object({
- nationalCode: z
- .string()
- .min(1, nationalId.nationalCodeRequired)
- .refine((value) => /^\d{10}$/.test(toEnglishDigits(value)), {
- message: nationalId.nationalCodeDigits,
- }),
- contractAccepted: z.boolean().optional(),
- })
- .superRefine((values, ctx) => {
- if (values.contractAccepted !== true) {
- ctx.addIssue({
- code: 'custom',
- path: ['contractAccepted'],
- message: v.contractRequired,
- })
- }
- })
-
-export const IdentityContractOtpValidation = z.object({
- code: z
- .string()
- .min(1, v.otpRequired)
- .refine((value) => /^\d{4}$/.test(toEnglishDigits(value)), {
- message: v.otpDigits,
- }),
-})
-
-export type IdentityVerificationFormValues = z.infer
-export type IdentityContractOtpValues = z.infer
-
-export const normalizeNationalCode = (value: string) => toEnglishDigits(value)
-export const normalizeOtpCode = (value: string) => toEnglishDigits(value)
diff --git a/validation/profile.ts b/validation/profile.ts
deleted file mode 100644
index f9a2b30..0000000
--- a/validation/profile.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { z } from 'zod'
-
-import { texts } from '@/texts'
-
-const v = texts.validation.profile
-const person = texts.validation.person
-
-export const UpdateProfileFormValidation = z.object({
- firstName: z.string().trim().min(1, person.firstNameRequired).max(100, person.firstNameMax),
- lastName: z.string().trim().min(1, person.lastNameRequired).max(100, person.lastNameMax),
- // z.enum keeps input/output identical so zodResolver matches useForm
- gender: z.enum(['male', 'female'], { message: person.genderRequired }),
- bio: z.string().trim().max(2000, v.bioMax).optional().or(z.literal('')),
- // انتخاب استان فعلاً در فرم پروفایل غیرفعال است.
- // provinceId: z.string().optional().or(z.literal('')),
- cityId: z.string().optional().or(z.literal('')),
- defaultAddress: z.string().trim().max(500, v.addressMax).optional().or(z.literal('')),
-})
-
-export type UpdateProfileFormValues = z.infer