diff --git a/src/app/admin/ai-setup/page.tsx b/src/app/admin/ai-setup/page.tsx index 6a6be33..e5b9c0f 100644 --- a/src/app/admin/ai-setup/page.tsx +++ b/src/app/admin/ai-setup/page.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import React, { useState } from 'react'; import { AdminPageLayout } from '@/components/layout/admin-layout'; +import VerificationGuard from '@/components/VerificationGuard'; import AndroidInstantSetup from './components/Android/InstantSetup'; import AndroidManualSetup from './components/Android/ManualSetup'; @@ -153,16 +154,18 @@ export default function AISetupPage() { return ( - - {stepComponents[step]} - + + + {stepComponents[step]} + + ); } diff --git a/src/app/admin/billing/components/BillingSection.tsx b/src/app/admin/billing/components/BillingSection.tsx index 010f570..799794d 100644 --- a/src/app/admin/billing/components/BillingSection.tsx +++ b/src/app/admin/billing/components/BillingSection.tsx @@ -8,6 +8,7 @@ import { useEffect, useState } from 'react'; import CancelConfirmModal from '@/components/ui/CancelConfirmModal'; import { useGetPlansQuery } from '@/features/public/publicApiSlice'; +import { useVerificationCheck } from '@/features/settings/hooks/useVerificationCheck'; import { useChangePlan, useCreateSubscription, @@ -71,6 +72,7 @@ export default function BillingSection() { const { downgrade } = useDowngradeToFree(); const { subscription, isSubscribed, isCancelled, currentPlanId } = useSubscription(); + const { blockOperationWithAlert } = useVerificationCheck(); const tierOrder = { FREE: 0, BASIC: 1, PRO: 2 }; const sortedPlans = [...plans].sort( @@ -108,6 +110,11 @@ export default function BillingSection() { tier: 'FREE' | 'BASIC' | 'PRO', planId: string, ): Promise => { + // Check verification before any plan operations with detailed message + if (!blockOperationWithAlert('switch plans')) { + return; + } + if (label.startsWith('Go with')) { if (!subscription || subscription.status === 'cancelled') { await create(planId); diff --git a/src/app/admin/booking/components/TaskManager/BookingModal.tsx b/src/app/admin/booking/components/TaskManager/BookingModal.tsx index b36aa5d..be9f729 100644 --- a/src/app/admin/booking/components/TaskManager/BookingModal.tsx +++ b/src/app/admin/booking/components/TaskManager/BookingModal.tsx @@ -27,7 +27,9 @@ import { type Service } from '@/features/service/serviceApi'; import { useCreateServiceBookingMutation } from '@/features/service/serviceBookingApi'; import type { ServiceManagement } from '@/features/service-management/serviceManagementApi'; import { useGetServiceFormFieldsQuery } from '@/features/service-management/serviceManagementApi'; +import { useVerificationCheck } from '@/features/settings/hooks/useVerificationCheck'; import { useAppSelector } from '@/redux/hooks'; +import type { RootState } from '@/redux/store'; interface Props { onClose: () => void; onCreate: (service: Service) => void; @@ -296,7 +298,9 @@ const BookingModal: React.FC = ({ Record >({}); const [createServiceBooking] = useCreateServiceBookingMutation(); + const user = useAppSelector(state => state.auth.user); + const { blockOperationWithAlert } = useVerificationCheck(); // Get custom form fields for the selected service const { data: customFormFields = [] } = useGetServiceFormFieldsQuery( @@ -445,6 +449,11 @@ const BookingModal: React.FC = ({ const handleCreate = async (): Promise => { try { + // Check verification before creating booking + if (!blockOperationWithAlert('create bookings')) { + return; + } + if (!user) { throw new Error('User is missing, please login again.'); } diff --git a/src/app/admin/overview/components/VerificationReminder.tsx b/src/app/admin/overview/components/VerificationReminder.tsx new file mode 100644 index 0000000..f83c10e --- /dev/null +++ b/src/app/admin/overview/components/VerificationReminder.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { + Alert, + AlertTitle, + Box, + Button, + Chip, + Typography, +} from '@mui/material'; +import { useRouter } from 'next/navigation'; +import React from 'react'; + +import { useGetVerificationQuery } from '@/features/settings/settingsApi'; + +interface VerificationReminderProps { + userId: string; +} + +export default function VerificationReminder({ + userId, +}: VerificationReminderProps) { + const router = useRouter(); + const { data: verificationData, isLoading } = useGetVerificationQuery( + userId, + { + skip: !userId, + }, + ); + + if (isLoading || !verificationData) { + return null; + } + + // Check if both email and phone are verified + const isFullyVerified = + verificationData.emailVerified && verificationData.mobileVerified; + + if (isFullyVerified) { + return null; // Don't show reminder if fully verified + } + + const handleGoToSettings = () => { + router.push('/admin/settings'); + }; + + const unverifiedCount = [ + !verificationData.emailVerified, + !verificationData.mobileVerified, + ].filter(Boolean).length; + + return ( + + + + ๐Ÿšจ Account Verification Required - {unverifiedCount} Item + {unverifiedCount > 1 ? 's' : ''} Pending + + + + Important: Your account requires verification to + access all features and ensure security. +
+ Impact: Some operations are currently blocked until + verification is complete. +
+ + + {!verificationData.emailVerified && ( + + + + Email: {verificationData.email ?? 'Not provided'} + + + )} + {!verificationData.mobileVerified && ( + + + + Phone: {verificationData.mobile ?? 'Not provided'} + + + )} + + + + + + + +
+
+ ); +} diff --git a/src/app/admin/overview/page.tsx b/src/app/admin/overview/page.tsx index 6220b81..f900fb2 100644 --- a/src/app/admin/overview/page.tsx +++ b/src/app/admin/overview/page.tsx @@ -7,11 +7,13 @@ import React, { useEffect } from 'react'; import { AdminPageLayout } from '@/components/layout/admin-layout'; import ProFeatureModal from '@/components/ui/ProFeatureModal'; import { useSubscription } from '@/features/subscription/useSubscription'; +import { useAppSelector } from '@/redux/hooks'; import { getPlanTier, isFreeOrBasicPlan, isProPlan } from '@/utils/planUtils'; import ActivitySection from './components/ActivitySection'; import CampaignProgressSection from './components/CompaignProgressSection'; import RecentService from './components/RecentService'; +import VerificationReminder from './components/VerificationReminder'; const styles = { contentContainer: { @@ -36,6 +38,8 @@ const styles = { }; export default function OverviewPage() { + const user = useAppSelector(state => state.auth.user); + const { subscription } = useSubscription(); const params = useSearchParams(); const router = useRouter(); @@ -66,6 +70,9 @@ export default function OverviewPage() { return ( <> + {/* Verification Reminder */} + {user?._id && } + diff --git a/src/app/admin/service-management/components/EditServiceModal.tsx b/src/app/admin/service-management/components/EditServiceModal.tsx index 379cdff..70f6b4a 100644 --- a/src/app/admin/service-management/components/EditServiceModal.tsx +++ b/src/app/admin/service-management/components/EditServiceModal.tsx @@ -28,7 +28,9 @@ import { useSaveServiceFormFieldsMutation, useUpdateServiceMutation, } from '@/features/service-management/serviceManagementApi'; +import { useVerificationCheck } from '@/features/settings/hooks/useVerificationCheck'; import { useAppSelector } from '@/redux/hooks'; +import type { RootState } from '@/redux/store'; import theme from '@/theme'; import CustomFormModal from './CustomFormModal'; @@ -219,6 +221,7 @@ export default function EditServiceModal({ useMediaQuery(theme.breakpoints.down('sm')); useMediaQuery(theme.breakpoints.down('xs')); + const user = useAppSelector(state => state.auth.user); // Fallback: try to get user from auth check if Redux state is empty @@ -232,6 +235,7 @@ export default function EditServiceModal({ const [createService, { isLoading: isCreating }] = useCreateServiceMutation(); const [updateService, { isLoading: isUpdating }] = useUpdateServiceMutation(); const [saveServiceFormFields] = useSaveServiceFormFieldsMutation(); + const { blockOperationWithAlert } = useVerificationCheck(); // ่Žทๅ–็Žฐๆœ‰็š„่กจๅ•ๅญ—ๆฎต const { data: existingFormFields = [] } = useGetServiceFormFieldsQuery( @@ -309,6 +313,11 @@ export default function EditServiceModal({ const handleSubmit = async (): Promise => { try { + // Check verification before creating/updating service + if (!blockOperationWithAlert('manage services')) { + return; + } + // Validation before submission if (!formData.name.trim()) { alert('Please enter a service name'); diff --git a/src/app/admin/service-management/page.tsx b/src/app/admin/service-management/page.tsx index d5d1050..8021323 100644 --- a/src/app/admin/service-management/page.tsx +++ b/src/app/admin/service-management/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { AdminPageLayout } from '@/components/layout/admin-layout'; +import VerificationGuard from '@/components/VerificationGuard'; import { useSubscription } from '@/features/subscription/useSubscription'; import { getPlanTier, isProPlan } from '@/utils/planUtils'; @@ -71,10 +72,12 @@ export default function ServiceManagementPage() { padding="normal" background="solid" > - + + + ); } diff --git a/src/app/admin/settings/SettingsSection.tsx b/src/app/admin/settings/SettingsSection.tsx index a9270ae..27b625c 100644 --- a/src/app/admin/settings/SettingsSection.tsx +++ b/src/app/admin/settings/SettingsSection.tsx @@ -36,9 +36,9 @@ const SettingsContainer = styled(Box)({ export default function SettingsSection() { return ( + - diff --git a/src/app/admin/settings/VerificationSection.tsx b/src/app/admin/settings/VerificationSection.tsx index 860f15b..dd5986a 100644 --- a/src/app/admin/settings/VerificationSection.tsx +++ b/src/app/admin/settings/VerificationSection.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Box } from '@mui/material'; +import { Alert, AlertTitle, Box, Chip, Typography } from '@mui/material'; import React, { useState } from 'react'; import EditModal from '@/app/admin/settings/components/EditModal'; @@ -12,9 +12,11 @@ import VerificationForm from '@/app/admin/settings/components/Verification/Verif import { useGetUserProfileQuery, useGetVerificationQuery, + useSendEmailVerificationMutation, + useSendSmsVerificationMutation, useUpdateVerificationMutation, useVerifyEmailMutation, - useVerifyMobileMutation, + useVerifySmsMutation, } from '@/features/settings/settingsApi'; import { useAppSelector } from '@/redux/hooks'; import { validateVerificationForm } from '@/utils/validationSettings'; @@ -31,8 +33,10 @@ export default function VerificationSection() { skip: !user?._id, }); const [updateVerification] = useUpdateVerificationMutation(); - const [verifyMobile] = useVerifyMobileMutation(); + const [sendEmailVerification] = useSendEmailVerificationMutation(); const [verifyEmail] = useVerifyEmailMutation(); + const [sendSmsVerification] = useSendSmsVerificationMutation(); + const [verifySms] = useVerifySmsMutation(); const [open, setOpen] = useState(false); const [verificationModal, setVerificationModal] = useState<{ open: boolean; @@ -112,6 +116,20 @@ export default function VerificationSection() { return; } + // Check if country code is selected for mobile verification + if ( + (formValues.type === 'SMS' || formValues.type === 'Both') && + formValues.mobile + ) { + const match = /^(\+\d+)?\s*(.*)$/.exec(formValues.mobile); + const countryCode = match?.[1]; + + if (!countryCode || countryCode === '') { + setError('Please select a country code for mobile number'); + return; + } + } + // Check if mobile or email has changed const mobileChanged = formValues.mobile !== values.mobile; const emailChanged = formValues.email !== values.email; @@ -139,7 +157,18 @@ export default function VerificationSection() { throw new Error('Mobile number not available'); } - await verifyMobile({ + // Check if country code is selected + const match = /^(\+\d+)?\s*(.*)$/.exec(values.mobile); + const countryCode = match?.[1]; + + if (!countryCode || countryCode === '') { + setError( + 'Please select a country code before sending SMS verification', + ); + return; + } + + await sendSmsVerification({ userId: user._id, mobile: values.mobile, }).unwrap(); @@ -150,7 +179,7 @@ export default function VerificationSection() { contact: values.mobile, }); } catch { - setError('Failed to verify mobile number'); + setError('Failed to send SMS verification'); } }; @@ -160,7 +189,7 @@ export default function VerificationSection() { throw new Error('Email not available'); } - await verifyEmail({ + await sendEmailVerification({ userId: user._id, email: values.email, }).unwrap(); @@ -171,7 +200,7 @@ export default function VerificationSection() { contact: values.email, }); } catch { - setError('Failed to verify email address'); + setError('Failed to send verification email'); } }; @@ -183,6 +212,26 @@ export default function VerificationSection() { }); }; + const handleVerifyCode = async (code: string) => { + if (!user?._id || !verificationModal.contact) { + throw new Error('User or contact not available'); + } + + if (verificationModal.type === 'email') { + await verifyEmail({ + userId: user._id, + email: verificationModal.contact, + code, + }).unwrap(); + } else if (verificationModal.type === 'mobile') { + await verifySms({ + userId: user._id, + mobile: verificationModal.contact, + code, + }).unwrap(); + } + }; + const handleMarketingPromotionsChange = async (checked: boolean) => { try { if (!user?._id) { @@ -291,9 +340,79 @@ export default function VerificationSection() { ); }; + // Check verification status + const isFullyVerified = + verificationData?.emailVerified && verificationData?.mobileVerified; + const unverifiedCount = [ + !verificationData?.emailVerified, + !verificationData?.mobileVerified, + ].filter(Boolean).length; + return ( <> + + {/* Verification Status Alert */} + {!isFullyVerified && verificationData && ( + + + ๐Ÿšจ Account Verification Required - {unverifiedCount} Item + {unverifiedCount > 1 ? 's' : ''} Pending + + + Impact: Some operations are blocked until + verification is complete. + + + {!verificationData.emailVerified && ( + + )} + {!verificationData.mobileVerified && ( + + )} + + + )} + + {/* Verification Success Alert */} + {isFullyVerified && verificationData && ( + + + โœ… Account Fully Verified + + + All contact information has been verified. You have full access to + all features. + + + )} + {/* Display Mode */} @@ -327,6 +446,7 @@ export default function VerificationSection() { type={verificationModal.type} contact={verificationModal.contact} onClose={handleCloseVerificationModal} + onVerify={handleVerifyCode} /> ); diff --git a/src/app/admin/settings/components/PhoneInput.tsx b/src/app/admin/settings/components/PhoneInput.tsx index afb896f..8f17e75 100644 --- a/src/app/admin/settings/components/PhoneInput.tsx +++ b/src/app/admin/settings/components/PhoneInput.tsx @@ -22,6 +22,7 @@ const PhoneInput: React.FC = ({ const [countryOptions, setCountryOptions] = useState< { label: string; value: string }[] >([]); + const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { fetch('https://restcountries.com/v3.1/all?fields=name,idd') @@ -63,12 +64,26 @@ const PhoneInput: React.FC = ({ a.label.localeCompare(b.label), ); setCountryOptions(options); + setIsInitialized(true); }) .catch(() => { setCountryOptions([]); + setIsInitialized(true); }); }, []); + // Set default country code to Australia if no value is provided + useEffect(() => { + if (isInitialized && !value && countryOptions.length > 0) { + const australiaOption = countryOptions.find( + option => option.label === 'Australia' || option.value === '+61', + ); + if (australiaOption) { + onChange(australiaOption.value); + } + } + }, [isInitialized, value, countryOptions, onChange]); + const match = /^(\+\d+)?\s*(.*)$/.exec(value); const prefix = match?.[1] ?? ''; const number = match?.[2] ?? ''; diff --git a/src/app/admin/settings/components/Verification/VerificationCodeModal.tsx b/src/app/admin/settings/components/Verification/VerificationCodeModal.tsx index 1c7b9ec..90b9945 100644 --- a/src/app/admin/settings/components/Verification/VerificationCodeModal.tsx +++ b/src/app/admin/settings/components/Verification/VerificationCodeModal.tsx @@ -18,7 +18,7 @@ interface VerificationCodeModalProps { type: 'mobile' | 'email'; contact: string; // mobile number or email onClose: () => void; - // onVerify: (code: string) => Promise; + onVerify: (code: string) => Promise; } const VerificationCodeModal: React.FC = ({ @@ -26,7 +26,7 @@ const VerificationCodeModal: React.FC = ({ type, contact, onClose, - // onVerify, + onVerify, }) => { const [code, setCode] = useState(''); const [error, setError] = useState(null); @@ -38,7 +38,7 @@ const VerificationCodeModal: React.FC = ({ onClose(); }; - const handleVerify = () => { + const handleVerify = async () => { if (!code.trim()) { setError('Please enter verification code'); return; @@ -48,7 +48,7 @@ const VerificationCodeModal: React.FC = ({ setError(null); try { - // await onVerify(code); + await onVerify(code); handleClose(); } catch { setError('Invalid verification code. Please try again.'); diff --git a/src/app/auth/callback/AuthCallbackContent.tsx b/src/app/auth/callback/AuthCallbackContent.tsx index 12afc63..51636c3 100644 --- a/src/app/auth/callback/AuthCallbackContent.tsx +++ b/src/app/auth/callback/AuthCallbackContent.tsx @@ -49,8 +49,6 @@ export default function AuthCallbackContent() { // Clear any persisted auth state to prevent old user ID from being used localStorage.removeItem('persist:root'); - console.log('[AuthCallback] Setting user with ID:', parsedUser._id); - dispatch( setCredentials({ csrfToken, diff --git a/src/components/VerificationGuard.tsx b/src/components/VerificationGuard.tsx new file mode 100644 index 0000000..c693cb6 --- /dev/null +++ b/src/components/VerificationGuard.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { Box, CircularProgress, Typography } from '@mui/material'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; + +import { useVerificationCheck } from '@/features/settings/hooks/useVerificationCheck'; + +interface VerificationGuardProps { + children: React.ReactNode; + redirectTo?: string; +} + +export default function VerificationGuard({ + children, + redirectTo = '/admin/settings', +}: VerificationGuardProps) { + const router = useRouter(); + const { isFullyVerified, isLoading } = useVerificationCheck(); + + useEffect(() => { + if (!isLoading && !isFullyVerified) { + router.push(redirectTo); + } + }, [isLoading, isFullyVerified, router, redirectTo]); + + if (isLoading) { + return ( + + + + Checking verification status... + + + ); + } + + if (!isFullyVerified) { + return ( + + + Verification Required + + + Redirecting to settings... + + + ); + } + + return <>{children}; +} diff --git a/src/components/common/VerificationGuard.tsx b/src/components/common/VerificationGuard.tsx new file mode 100644 index 0000000..14d9bd0 --- /dev/null +++ b/src/components/common/VerificationGuard.tsx @@ -0,0 +1,54 @@ +'use client'; + +import React from 'react'; + +import { useVerificationCheck } from '@/features/settings/hooks/useVerificationCheck'; + +interface VerificationGuardProps { + children: React.ReactNode; + operation: string; + fallback?: React.ReactNode; +} + +/** + * A guard component that blocks operations if user is not fully verified + * Shows fallback content or nothing if verification is required + */ +export default function VerificationGuard({ + children, + operation: _operation, + fallback, +}: VerificationGuardProps) { + const { isFullyVerified, isLoading } = useVerificationCheck(); + + if (isLoading) { + return <>{fallback ??
Loading verification status...
}; + } + + if (!isFullyVerified) { + return <>{fallback ?? null}; + } + + return <>{children}; +} + +/** + * Hook to wrap functions that require verification + */ +export const useVerificationGuard = () => { + const { blockOperationWithAlert } = useVerificationCheck(); + + const guardFunction = unknown>( + fn: T, + operation: string, + ): T => { + return ((...args: Parameters) => { + if (!blockOperationWithAlert(operation)) { + return; + } + return fn(...args); + }) as T; + }; + + return { guardFunction }; +}; diff --git a/src/features/settings/hooks/useVerificationCheck.ts b/src/features/settings/hooks/useVerificationCheck.ts new file mode 100644 index 0000000..707f128 --- /dev/null +++ b/src/features/settings/hooks/useVerificationCheck.ts @@ -0,0 +1,93 @@ +import { useRouter } from 'next/navigation'; +import { useCallback } from 'react'; + +import { useGetVerificationQuery } from '@/features/settings/settingsApi'; +import { useAppSelector } from '@/redux/hooks'; + +export const useVerificationCheck = () => { + const router = useRouter(); + const user = useAppSelector(state => state.auth.user); + + const { data: verificationData, isLoading } = useGetVerificationQuery( + user?._id ?? '', + { + skip: !user?._id, + }, + ); + + const isFullyVerified = + verificationData?.emailVerified && verificationData?.mobileVerified; + const isEmailVerified = verificationData?.emailVerified ?? false; + const isPhoneVerified = verificationData?.mobileVerified ?? false; + + const checkVerificationAndRedirect = useCallback(() => { + if (isLoading) return false; + + if (!isFullyVerified) { + router.push('/admin/settings'); + return false; + } + + return true; + }, [isLoading, isFullyVerified, router]); + + const showVerificationModal = useCallback(() => { + if (!isFullyVerified) { + router.push('/admin/settings'); + return true; + } + return false; + }, [isFullyVerified, router]); + + // New function to check verification with detailed error message + const checkVerificationWithMessage = useCallback( + (operation: string) => { + if (isLoading) { + return { allowed: false, message: 'Verification status is loading...' }; + } + + if (!isFullyVerified) { + const unverifiedItems = []; + if (!isEmailVerified) unverifiedItems.push('email'); + if (!isPhoneVerified) unverifiedItems.push('phone'); + + return { + allowed: false, + message: `Cannot ${operation}. Please verify your ${unverifiedItems.join(' and ')} first.`, + unverifiedItems, + redirect: () => router.push('/admin/settings'), + }; + } + + return { allowed: true }; + }, + [isLoading, isFullyVerified, isEmailVerified, isPhoneVerified, router], + ); + + // Function to block operations with alert + const blockOperationWithAlert = useCallback( + (operation: string) => { + const result = checkVerificationWithMessage(operation); + if (!result.allowed) { + alert( + `๐Ÿšจ Verification Required\n\n${result.message}\n\nYou will be redirected to Settings to complete verification.`, + ); + result.redirect?.(); + } + return result.allowed; + }, + [checkVerificationWithMessage], + ); + + return { + isFullyVerified, + isEmailVerified, + isPhoneVerified, + isLoading, + verificationData, + checkVerificationAndRedirect, + showVerificationModal, + checkVerificationWithMessage, + blockOperationWithAlert, + }; +}; diff --git a/src/features/settings/settingsApi.ts b/src/features/settings/settingsApi.ts index b858b21..9aede30 100644 --- a/src/features/settings/settingsApi.ts +++ b/src/features/settings/settingsApi.ts @@ -138,7 +138,7 @@ export const settingsApi = createApi({ }), getVerification: builder.query({ query: userId => ({ - url: `/api/settings/user/${userId}/verification`, + url: `/verification/user/${userId}`, method: 'GET', }), providesTags: ['Verification'], @@ -148,7 +148,7 @@ export const settingsApi = createApi({ { userId: string } & VerificationSettings >({ query: ({ userId, ...verificationData }) => ({ - url: `/api/settings/user/${userId}/verification`, + url: `/verification/user/${userId}`, method: 'PUT', data: verificationData, }), @@ -159,23 +159,56 @@ export const settingsApi = createApi({ { userId: string; mobile: string } >({ query: ({ userId, mobile }) => ({ - url: `/api/settings/user/${userId}/verification/mobile`, + url: `/verification/user/${userId}/mobile`, method: 'POST', data: { mobile }, }), invalidatesTags: ['Verification'], }), - verifyEmail: builder.mutation< + sendSmsVerification: builder.mutation< + { success: boolean; message?: string }, + { userId: string; mobile: string } + >({ + query: ({ userId, mobile }) => ({ + url: `/verification/user/${userId}/mobile/send`, + method: 'POST', + data: { mobile }, + }), + invalidatesTags: ['Verification'], + }), + verifySms: builder.mutation< { success: boolean; message: string }, + { userId: string; mobile: string; code: string } + >({ + query: ({ userId, mobile, code }) => ({ + url: `/verification/user/${userId}/mobile/verify`, + method: 'POST', + data: { mobile, code }, + }), + invalidatesTags: ['Verification'], + }), + sendEmailVerification: builder.mutation< + { success: boolean; message?: string }, { userId: string; email: string } >({ query: ({ userId, email }) => ({ - url: `/api/settings/user/${userId}/verification/email`, + url: `/verification/user/${userId}/email/send`, method: 'POST', data: { email }, }), invalidatesTags: ['Verification'], }), + verifyEmail: builder.mutation< + { success: boolean; message: string }, + { userId: string; email: string; code: string } + >({ + query: ({ userId, email, code }) => ({ + url: `/verification/user/${userId}/email/verify`, + method: 'POST', + data: { email, code }, + }), + invalidatesTags: ['Verification'], + }), checkABNExists: builder.mutation< { exists: boolean }, { abn: string; userId: string } @@ -220,6 +253,9 @@ export const { useGetVerificationQuery, useUpdateVerificationMutation, useVerifyMobileMutation, + useSendSmsVerificationMutation, + useVerifySmsMutation, + useSendEmailVerificationMutation, useVerifyEmailMutation, useGetAddressQuery, useUpdateAddressMutation,