Skip to content

Commit 5d4bd3e

Browse files
update(OUT-2056): Initial "Connect to Quickbooks" screen
- [X] update initial connection screen with settings section and product mapping table - [X] blurred and disabled setting section - [X] issue fix: when sync fails, sync flag was not true after re-authorization - [X] issue fix: when sync fails and re-authorization is done, the QB items in dropdown were not loaded. - [X] only fetch QB items when sync flag is true. Added condition to not fetch QB items when: - sync fails - during initial page - [X] "Connecting" label in callout button when initial connection and re-authorization
1 parent 27db547 commit 5d4bd3e

7 files changed

Lines changed: 120 additions & 108 deletions

File tree

src/app/(home)/HomeClient.tsx

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
11
'use client'
22
import { useApp } from '@/app/context/AppContext'
33
import { Main as DashboardMain } from '@/components/dashboard/Main'
4-
import { SilentError } from '@/components/template/SilentError'
5-
import { useAppBridge, useQuickbooks } from '@/hook/useQuickbooks'
6-
import { Button, Spinner } from 'copilot-design-system'
4+
import { useAppBridge } from '@/hook/useQuickbooks'
75

86
export default function HomeClient() {
9-
const {
10-
token,
11-
tokenPayload,
12-
reconnect,
13-
portalConnectionStatus,
14-
isEnabled,
15-
syncFlag,
16-
} = useApp()
17-
18-
const { loading, handleConnect, isReconnecting } = useQuickbooks(
19-
token,
20-
tokenPayload,
21-
reconnect,
22-
)
7+
const { token, portalConnectionStatus, isEnabled, syncFlag } = useApp()
238

249
// bridge related logics like disconnect app and download sync log csv
2510
useAppBridge({
@@ -29,45 +14,9 @@ export default function HomeClient() {
2914
connectionStatus: portalConnectionStatus || false,
3015
})
3116

32-
if (portalConnectionStatus === null) {
33-
return (
34-
<SilentError
35-
message="Something went wrong while connecting to QuickBooks"
36-
resetFn={handleConnect}
37-
/>
38-
)
39-
}
40-
4117
return (
4218
<div className="home-client-wrapper w-full h-full">
43-
{portalConnectionStatus ? (
44-
<>
45-
{isReconnecting && (
46-
<div>
47-
<span className="me-2">Reconnecting to QuickBooks</span>{' '}
48-
<Spinner size={5} />
49-
</div>
50-
)}
51-
<DashboardMain />
52-
</>
53-
) : (
54-
<div className="flex items-center justify-center h-full text-xl">
55-
{loading ? (
56-
<div className="flex items-center">
57-
<span className="me-2">
58-
Connecting to QuickBooks. Please wait
59-
</span>{' '}
60-
<Spinner size={5} />
61-
</div>
62-
) : (
63-
<Button
64-
label="Connect to QuickBooks"
65-
onClick={() => handleConnect()}
66-
disabled={loading}
67-
/>
68-
)}
69-
</div>
70-
)}
19+
<DashboardMain />
7120
</div>
7221
)
7322
}

src/app/api/quickbooks/auth/auth.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
QBPortalConnectionCreateSchemaType,
1616
QBPortalConnectionUpdateSchemaType,
1717
} from '@/db/schema/qbPortalConnections'
18+
import { QBSetting } from '@/db/schema/qbSettings'
1819
import {
1920
getPortalConnection,
2021
getPortalSettings,
@@ -111,6 +112,13 @@ export class AuthService extends BaseService {
111112
portalId,
112113
syncFlag,
113114
})
115+
} else {
116+
await settingsService.updateQBSettings(
117+
{
118+
syncFlag,
119+
},
120+
eq(QBSetting.portalId, portalId),
121+
)
114122
}
115123
}
116124

src/components/dashboard/Main.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Spinner,
1313
} from 'copilot-design-system'
1414
import LastSyncAt from '@/components/dashboard/LastSyncAt'
15+
import { SilentError } from '@/components/template/SilentError'
1516

1617
type CalloutType = {
1718
title: string
@@ -43,6 +44,13 @@ const DashboardCallout = (lastSyncTime: string | null) => ({
4344
actionIcon: 'Repeat' as IconType,
4445
buttonVariant: 'secondary' as const,
4546
},
47+
[CalloutVariant.INFO]: {
48+
title: 'Authorize your account',
49+
description: 'Log into QuickBooks with an admin account to get started.',
50+
actionLabel: 'Connect to QuickBooks',
51+
actionIcon: 'Check' as IconType,
52+
buttonVariant: 'primary' as const,
53+
},
4654
})
4755

4856
export const Main = () => {
@@ -53,8 +61,21 @@ export const Main = () => {
5361
isReconnecting,
5462
lastSyncTimestamp,
5563
itemMapped,
64+
portalConnectionStatus,
65+
syncFlag,
66+
handleConnect,
67+
isConnecting,
5668
} = useDashboardMain()
5769

70+
if (portalConnectionStatus === null) {
71+
return (
72+
<SilentError
73+
message="Something went wrong while connecting to QuickBooks"
74+
resetFn={handleConnect}
75+
/>
76+
)
77+
}
78+
5879
const dashboardCallout: CalloutType =
5980
DashboardCallout(lastSyncTimestamp)[status]
6081

@@ -70,31 +91,39 @@ export const Main = () => {
7091
variant={status}
7192
{...(dashboardCallout.actionLabel && {
7293
actionProps: {
73-
label: isReconnecting
74-
? 'Reauthorizing...'
75-
: dashboardCallout.actionLabel,
94+
label:
95+
isReconnecting || isConnecting
96+
? 'Connecting...'
97+
: dashboardCallout.actionLabel,
7698
onClick: buttonAction,
7799
disabled:
78100
isReconnecting ||
101+
isConnecting ||
79102
(status === CalloutVariant.WARNING && !itemMapped),
80103
prefixIcon: dashboardCallout.actionIcon,
81104
...(dashboardCallout.buttonVariant && {
82105
variant: dashboardCallout.buttonVariant,
83106
}),
107+
className: !portalConnectionStatus ? 'lg:!px-8' : '',
84108
},
85109
})}
86110
/>
87-
<div className="mt-6 mb-2">
88-
<Heading
89-
size="xl"
90-
tag="h2"
91-
className="pb-4 border-b-1 border-b-card-divider !leading-7" // forcing styles with "!"
92-
>
93-
Settings
94-
</Heading>
95-
<Divider />
111+
<div className={!portalConnectionStatus ? 'opacity-25 relative' : ''}>
112+
{!portalConnectionStatus && (
113+
<div className="absolute top-0 left-0 w-full h-full z-10"></div>
114+
)}
115+
<div className="mt-6 mb-2">
116+
<Heading
117+
size="xl"
118+
tag="h2"
119+
className="pb-4 border-b-1 border-b-card-divider !leading-7" // forcing styles with "!"
120+
>
121+
Settings
122+
</Heading>
123+
<Divider />
124+
</div>
125+
<SettingAccordion syncFlag={syncFlag} />
96126
</div>
97-
<SettingAccordion />
98127
</>
99128
)}
100129
</>

src/components/dashboard/settings/SettingAccordion.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
} from '@/hook/useSettings'
1010
import { Button } from 'copilot-design-system'
1111

12-
export default function SettingAccordion() {
12+
export default function SettingAccordion({
13+
syncFlag,
14+
}: {
15+
syncFlag: boolean | null
16+
}) {
1317
const {
1418
openDropdowns,
1519
setOpenDropdowns,
@@ -89,6 +93,7 @@ export default function SettingAccordion() {
8993
className={`absolute top-[14px] right-0 z-10 flex items-center justify-end`}
9094
>
9195
{index === 0 &&
96+
syncFlag &&
9297
(showProductConfirm || setting.settingShowConfirm) && (
9398
<>
9499
{!initialSettingMapFlag && (
@@ -107,7 +112,7 @@ export default function SettingAccordion() {
107112
/>
108113
</>
109114
)}
110-
{index === 1 && showInvoiceButton && (
115+
{index === 1 && syncFlag && showInvoiceButton && (
111116
<>
112117
{!initialSettingMapFlag && (
113118
<Button

src/hook/useDashboard.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,54 @@ export const useDashboardMain = () => {
1515
isEnabled,
1616
initialSettingMapFlag,
1717
itemMapped,
18+
portalConnectionStatus,
1819
} = useApp()
1920

20-
const { handleConnect, isReconnecting, handleSyncEnable } = useQuickbooks(
21-
token,
22-
tokenPayload,
23-
reconnect,
24-
)
21+
const {
22+
handleConnect,
23+
isReconnecting,
24+
handleSyncEnable,
25+
loading: isConnecting,
26+
} = useQuickbooks(token, tokenPayload, reconnect)
27+
2528
const [callOutStatus, setCallOutStatus] = useState<
26-
CalloutVariant.SUCCESS | CalloutVariant.ERROR | CalloutVariant.WARNING
29+
| CalloutVariant.SUCCESS
30+
| CalloutVariant.ERROR
31+
| CalloutVariant.WARNING
32+
| CalloutVariant.INFO
2733
>(CalloutVariant.SUCCESS)
2834
const [isLoading, setIsLoading] = useState(true)
2935
const [buttonAction, setButtonAction] = useState<
3036
(() => Promise<NodeJS.Timeout>) | undefined
3137
>(undefined)
3238

3339
useEffect(() => {
34-
if (syncFlag) {
35-
if (!isEnabled) {
36-
setCallOutStatus(CalloutVariant.WARNING)
37-
setButtonAction(() => handleSyncEnable)
38-
} else {
39-
setCallOutStatus(CalloutVariant.SUCCESS)
40-
}
41-
} else {
42-
let timeout: NodeJS.Timeout
43-
setCallOutStatus(CalloutVariant.ERROR)
40+
let timeout: NodeJS.Timeout
41+
if (!portalConnectionStatus) {
42+
// No early return to run timeout cleanup function
43+
setCallOutStatus(CalloutVariant.INFO)
4444
setButtonAction(() => async () => {
45-
timeout = await handleConnect(AuthStatus.RECONNECT)
45+
timeout = await handleConnect()
4646
return timeout
4747
})
48-
49-
return () => clearTimeout(timeout)
48+
} else {
49+
if (syncFlag) {
50+
if (!isEnabled) {
51+
setCallOutStatus(CalloutVariant.WARNING)
52+
setButtonAction(() => handleSyncEnable)
53+
} else {
54+
setCallOutStatus(CalloutVariant.SUCCESS)
55+
}
56+
} else {
57+
setCallOutStatus(CalloutVariant.ERROR)
58+
setButtonAction(() => async () => {
59+
timeout = await handleConnect(AuthStatus.RECONNECT)
60+
return timeout
61+
})
62+
}
5063
}
5164
setIsLoading(false)
65+
return () => clearTimeout(timeout)
5266
}, [syncFlag, isEnabled])
5367

5468
return {
@@ -59,5 +73,9 @@ export const useDashboardMain = () => {
5973
lastSyncTimestamp,
6074
itemMapped,
6175
initialSettingMapFlag,
76+
portalConnectionStatus,
77+
syncFlag,
78+
isConnecting,
79+
handleConnect,
6280
}
6381
}

src/hook/useQuickbooks.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const useQuickbooks = (
3838
syncFlag: payload.new.sync_flag,
3939
isEnabled: payload.new.is_enabled,
4040
}))
41+
setLoading(false)
4142
},
4243
)
4344
.on(
@@ -66,6 +67,7 @@ export const useQuickbooks = (
6667
: prev.lastSyncTimestamp,
6768
portalConnectionStatus: connectionStatus,
6869
}))
70+
setLoading(false)
6971
},
7072
)
7173
.on(
@@ -108,24 +110,17 @@ export const useQuickbooks = (
108110
}, [reconnect])
109111

110112
const getAuthUrl = async (type?: string) => {
113+
setLoading(true)
111114
const redirectUrl = copilotDashboardUrl
112115
const url = `/api/quickbooks/auth?token=${token}${type ? `&type=${type}` : ''}`
113-
setLoading(true)
114116
const response = await fetch(url, {
115117
method: 'POST',
116118
body: JSON.stringify({ redirectUrl }),
117119
})
118-
119120
return await response.json()
120121
}
121122

122123
const handleConnect = async (type?: string) => {
123-
setAppParams((prev) => ({
124-
...prev,
125-
portalConnectionStatus: false,
126-
}))
127-
setLoading(true)
128-
129124
// set time-out in case if user closes the popped up window. This will prevent the app from the infinite "connecting" state
130125
const timeout = setTimeout(() => {
131126
if (!portalConnectionStatus) {

0 commit comments

Comments
 (0)