diff --git a/src/components/dashboard/settings/SettingAccordion.tsx b/src/components/dashboard/settings/SettingAccordion.tsx index cd482bd3..88a097ba 100644 --- a/src/components/dashboard/settings/SettingAccordion.tsx +++ b/src/components/dashboard/settings/SettingAccordion.tsx @@ -42,6 +42,9 @@ export default function SettingAccordion({ isLoading, changeSettings, showButton: showInvoiceButton, + bankAccounts, + isBankAccountsLoading, + selectBankAccount, } = useInvoiceDetailSettings() const accordionItems = [ @@ -72,6 +75,9 @@ export default function SettingAccordion({ settingState={settingState} changeSettings={changeSettings} isLoading={isLoading} + bankAccounts={bankAccounts} + isBankAccountsLoading={isBankAccountsLoading} + selectBankAccount={selectBankAccount} /> ), }, diff --git a/src/components/dashboard/settings/sections/invoice/InvoiceDetail.tsx b/src/components/dashboard/settings/sections/invoice/InvoiceDetail.tsx index 5c93dccd..cdfae987 100644 --- a/src/components/dashboard/settings/sections/invoice/InvoiceDetail.tsx +++ b/src/components/dashboard/settings/sections/invoice/InvoiceDetail.tsx @@ -1,25 +1,52 @@ import { useApp } from '@/app/context/AppContext' +import { BankAccountType } from '@/hook/useSettings' import { InvoiceSettingType } from '@/type/common' import { getWorkspaceLabel } from '@/utils/workspace' import { Checkbox, Spinner } from 'copilot-design-system' +import { useEffect, useRef, useState } from 'react' type InvoiceDetailProps = { settingState: InvoiceSettingType changeSettings: (flag: keyof InvoiceSettingType, state: boolean) => void isLoading: boolean + bankAccounts: BankAccountType[] + isBankAccountsLoading: boolean + selectBankAccount: (ref: string) => void } export default function InvoiceDetail({ settingState, changeSettings, isLoading, + bankAccounts, + isBankAccountsLoading, + selectBankAccount, }: InvoiceDetailProps) { const { workspace } = useApp() + const [isDropdownOpen, setIsDropdownOpen] = useState(false) + const dropdownRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsDropdownOpen(false) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) if (isLoading) { return } + const selectedAccount = bankAccounts.find( + (acc) => acc.Id === settingState.bankAccountRef, + ) + return ( <>
@@ -53,6 +80,86 @@ export default function InvoiceDetail({ />
)} + {settingState.absorbedFeeFlag && settingState.bankDepositFeeFlag && ( +
+ +

+ Select the QuickBooks bank account where Stripe deposits land. +

+
+ + {isDropdownOpen && ( +
+ {isBankAccountsLoading ? ( +
+ +
+ ) : bankAccounts.length === 0 ? ( +
+ No bank accounts found in QuickBooks +
+ ) : ( + bankAccounts.map((account) => ( + + )) + )} +
+ )} +
+ {!isBankAccountsLoading && + !settingState.bankAccountRef && + bankAccounts.length > 0 && ( +

+ Please select a bank account to enable bank deposits. +

+ )} +
+ )}
{ - const initialInvoiceSetting = { + const initialInvoiceSetting: InvoiceSettingType = { absorbedFeeFlag: false, bankDepositFeeFlag: false, useCompanyNameFlag: false, + bankAccountRef: null, } const { token, setAppParams } = useApp() const [settingState, setSettingState] = useState( @@ -520,6 +526,7 @@ export const useInvoiceDetailSettings = () => { const [intialSettingState, setIntialSettingState] = useState< InvoiceSettingType | undefined >() + const { data: setting, error, @@ -529,9 +536,18 @@ export const useInvoiceDetailSettings = () => { revalidateOnMount: false, }) - const changeSettings = async ( + const { data: bankAccountsData, isLoading: isBankAccountsLoading } = + useSwrHelper( + settingState.bankDepositFeeFlag + ? `/api/quickbooks/setting/bank-account?token=${token}` + : null, + { suspense: false, revalidateOnMount: true }, + ) + const bankAccounts: BankAccountType[] = bankAccountsData?.accounts || [] + + const changeSettings = ( flag: keyof InvoiceSettingType, - state: boolean, + state: boolean | string | null, ) => { setSettingState((prev) => ({ ...prev, @@ -539,16 +555,27 @@ export const useInvoiceDetailSettings = () => { })) } + const selectBankAccount = (ref: string) => { + setSettingState((prev) => ({ ...prev, bankAccountRef: ref })) + } + useEffect(() => { if (!settingState || !intialSettingState) return - const showButton = !equal(intialSettingState, settingState) - setShowButton(showButton) - }, [settingState]) + const hasChanges = !equal(intialSettingState, settingState) + // Block submit if bank deposit is on but no bank account selected + const missingBankAccount = + settingState.bankDepositFeeFlag && !settingState.bankAccountRef + setShowButton(hasChanges && !missingBankAccount) + }, [settingState, intialSettingState]) useEffect(() => { if (setting && setting?.setting) { - setSettingState(setting.setting) - setIntialSettingState(structuredClone(setting.setting)) + const loaded: InvoiceSettingType = { + ...setting.setting, + bankAccountRef: setting.bankAccountRef || null, + } + setSettingState(loaded) + setIntialSettingState(structuredClone(loaded)) setAppParams((prev) => ({ ...prev, initialInvoiceSettingMapFlag: setting.setting.initialInvoiceSettingMap, @@ -562,16 +589,21 @@ export const useInvoiceDetailSettings = () => { const submitInvoiceSettings = async () => { setShowButton(false) - const res = await postFetcher( - `/api/quickbooks/setting?type=${SettingType.INVOICE}&token=${token}`, - {}, - { ...settingState, type: SettingType.INVOICE }, - ) - if (!res || res?.error) { - setShowButton(true) // show the update settings button if error - console.error('Error submitting Invoice settings', { res }) - } else { - mutate(`/api/quickbooks/setting?type=invoice&token=${token}`) + try { + const res = await postFetcher( + `/api/quickbooks/setting?type=${SettingType.INVOICE}&token=${token}`, + {}, + { ...settingState, type: SettingType.INVOICE }, + ) + if (res?.error) { + setShowButton(true) + console.error('Error submitting Invoice settings', { res }) + } else { + mutate(`/api/quickbooks/setting?type=invoice&token=${token}`) + } + } catch (err) { + setShowButton(true) + console.error('Error submitting Invoice settings', err) } } @@ -588,6 +620,9 @@ export const useInvoiceDetailSettings = () => { error, isLoading, showButton, + bankAccounts, + isBankAccountsLoading, + selectBankAccount, } }