Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/action/quickbooks.action.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use server'

import { AuthStatus } from '@/app/api/core/types/auth'
import { PortalConnectionWithSettingType } from '@/db/schema/qbPortalConnections'
import { QBSettingsSelectSchemaType } from '@/db/schema/qbSettings'
import {
getPortalConnection,
getPortalSettings,
} from '@/db/service/token.service'
import IntuitAPI, { IntuitAPITokensType } from '@/utils/intuitAPI'
import CustomLogger from '@/utils/logger'
import { z } from 'zod'

export async function checkPortalConnection(
Expand All @@ -29,6 +33,24 @@ export async function checkSyncStatus(portalId: string): Promise<boolean> {
}
}

export async function checkForNonUsCompany(tokenInfo: IntuitAPITokensType) {
CustomLogger.info({
message: 'checkForNonUsCompany | Checking for non-US company',
})
const intuitApi = new IntuitAPI(tokenInfo)
const companyInfo = await intuitApi.getCompanyInfo()

CustomLogger.info({
obj: { companyInfo },
message: 'checkForNonUsCompany | Company Info',
})

if (companyInfo?.Country !== 'US') {
return true
}
return false
}

export async function reconnectIfCta(type?: string) {
if (!type) {
return false
Expand Down
3 changes: 3 additions & 0 deletions src/app/context/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'
import { ProductMappingItemType } from '@/db/schema/qbProductSync'
import { Token, WorkspaceResponse } from '@/type/common'
import { IntuitAPITokensType } from '@/utils/intuitAPI'
import { createContext, useContext, useState, ReactNode } from 'react'

type AppContextType = {
Expand All @@ -17,6 +18,8 @@ type AppContextType = {
initialInvoiceSettingMapFlag?: boolean // flag to determine the initial invoice setting flag
initialProductSettingMapFlag?: boolean // flag to determine the initial product setting flag
workspace: WorkspaceResponse
nonUsCompany?: boolean
qbTokens?: IntuitAPITokensType
}

const AppContext = createContext<
Expand Down
15 changes: 14 additions & 1 deletion src/components/dashboard/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ export const Main = () => {
syncFlag,
handleConnect,
isConnecting,
nonUsCompanyChecking,
} = useDashboardMain()

const { enableAppIndicator } = useApp()
const { enableAppIndicator, nonUsCompany } = useApp()

if (portalConnectionStatus === null) {
return (
Expand All @@ -89,6 +90,16 @@ export const Main = () => {
</div>
) : (
<main className="main-section px-8 sm:px-[100px] lg:px-[220px] pb-[54px] pt-6">
{nonUsCompany && (
<div className="mb-4">
<Callout
title="Support Limited to U.S. QuickBooks Online Accounts"
description="At this time, the integration only supports US-based QuickBooks Online accounts. To use the integration, please disconnect your current account and reconnect using a US QuickBooks account."
variant={CalloutVariant.ERROR}
/>
</div>
)}

<Callout
title={dashboardCallout.title}
description={dashboardCallout.description}
Expand All @@ -101,6 +112,8 @@ export const Main = () => {
: dashboardCallout.actionLabel,
onClick: buttonAction,
disabled:
nonUsCompanyChecking ||
nonUsCompany || // disable the button if non-us company
isReconnecting ||
isConnecting ||
(status === CalloutVariant.WARNING && !enableAppIndicator),
Expand Down
20 changes: 20 additions & 0 deletions src/db/service/token.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use server'
import { db } from '@/db'
import { PortalConnectionWithSettingType } from '@/db/schema/qbPortalConnections'
import { QBSettingsSelectSchemaType } from '@/db/schema/qbSettings'
import { WorkspaceResponse } from '@/type/common'
import { CopilotAPI } from '@/utils/copilotAPI'
import { IntuitAPITokensType } from '@/utils/intuitAPI'
import { and, eq, isNull } from 'drizzle-orm'

export const getPortalConnection = async (
Expand Down Expand Up @@ -55,3 +57,21 @@ export const getWorkspaceInfo = async (
): Promise<WorkspaceResponse> => {
return await new CopilotAPI(token).getWorkspace()
}

export const getPortalTokens = async (
portalId: string,
): Promise<IntuitAPITokensType> => {
const portalConnection = await getPortalConnection(portalId)
if (!portalConnection) throw new Error('Portal connection not found')

return {
accessToken: portalConnection.accessToken,
refreshToken: portalConnection.refreshToken,
intuitRealmId: portalConnection.intuitRealmId,
incomeAccountRef: portalConnection.incomeAccountRef,
expenseAccountRef: portalConnection.expenseAccountRef,
assetAccountRef: portalConnection.assetAccountRef,
serviceItemRef: portalConnection.serviceItemRef,
clientFeeRef: portalConnection.clientFeeRef,
}
}
30 changes: 29 additions & 1 deletion src/hook/useDashboard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client'
import { checkForNonUsCompany } from '@/action/quickbooks.action'
import { AuthStatus } from '@/app/api/core/types/auth'
import { useApp } from '@/app/context/AppContext'
import { CalloutVariant } from '@/components/type/callout'
import { getPortalTokens } from '@/db/service/token.service'
import { useQuickbooks } from '@/hook/useQuickbooks'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'

export const useDashboardMain = () => {
const {
Expand All @@ -14,6 +16,8 @@
lastSyncTimestamp,
isEnabled,
portalConnectionStatus,
qbTokens,
setAppParams,
} = useApp()

const {
Expand All @@ -33,6 +37,29 @@
const [buttonAction, setButtonAction] = useState<
(() => Promise<NodeJS.Timeout>) | undefined
>(undefined)
const [nonUsCompanyChecking, setNonUsCompanyChecking] = useState(false)

const checkCompanyCountry = useCallback(async () => {
setNonUsCompanyChecking(true)
let tempTokenInfo = qbTokens
if (!tempTokenInfo) {
tempTokenInfo = await getPortalTokens(tokenPayload.workspaceId)
}

const nonUsCompany = await checkForNonUsCompany(tempTokenInfo)
setAppParams((prev) => ({
...prev,
qbTokens: tempTokenInfo,
nonUsCompany,
}))
setNonUsCompanyChecking(false)
}, [syncFlag])

Check warning on line 56 in src/hook/useDashboard.ts

View workflow job for this annotation

GitHub Actions / Run linters

React Hook useCallback has missing dependencies: 'qbTokens', 'setAppParams', and 'tokenPayload.workspaceId'. Either include them or remove the dependency array

useEffect(() => {
if (syncFlag) {
checkCompanyCountry()
}
}, [syncFlag])

Check warning on line 62 in src/hook/useDashboard.ts

View workflow job for this annotation

GitHub Actions / Run linters

React Hook useEffect has a missing dependency: 'checkCompanyCountry'. Either include it or remove the dependency array

useEffect(() => {
let timeout: NodeJS.Timeout
Expand Down Expand Up @@ -61,7 +88,7 @@
}
setIsLoading(false)
return () => clearTimeout(timeout)
}, [syncFlag, isEnabled])

Check warning on line 91 in src/hook/useDashboard.ts

View workflow job for this annotation

GitHub Actions / Run linters

React Hook useEffect has missing dependencies: 'handleConnect', 'handleSyncEnable', and 'portalConnectionStatus'. Either include them or remove the dependency array

return {
callOutStatus,
Expand All @@ -73,5 +100,6 @@
syncFlag,
isConnecting,
handleConnect,
nonUsCompanyChecking,
}
}
22 changes: 22 additions & 0 deletions src/utils/intuitAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,27 @@ export default class IntuitAPI {
return purchase
}

async _getCompanyInfo() {
CustomLogger.info({
message: `IntuitAPI#getCompanyInfo | Company Info query start for realmId: ${this.tokens.intuitRealmId}.`,
})
const query = `SELECT * FROM CompanyInfo maxresults 1`
const companyInfo = await this.customQuery(query)

if (!companyInfo) return null

if (companyInfo?.Fault) {
CustomLogger.error({ obj: companyInfo.Fault?.Error, message: 'Error: ' })
throw new APIError(
companyInfo.Fault?.Error?.code || httpStatus.BAD_REQUEST,
`${IntuitAPIErrorMessage}getCompanyInfo`,
companyInfo.Fault?.Error,
)
}

return companyInfo.CompanyInfo?.[0]
}

private wrapWithRetry<Args extends unknown[], R>(
fn: (...args: Args) => Promise<R>,
): (...args: Args) => Promise<R> {
Expand Down Expand Up @@ -842,4 +863,5 @@ export default class IntuitAPI {
createPurchase = this.wrapWithRetry(this._createPurchase)
deletePayment = this.wrapWithRetry(this._deletePayment)
deletePurchase = this.wrapWithRetry(this._deletePurchase)
getCompanyInfo = this.wrapWithRetry(this._getCompanyInfo)
}
Loading