@accounter/client 0.0.8-alpha-20251021225827-178e480c997a9811913e16f85cb94329041b096e → 0.0.8-alpha-20251022034712-a30a030f5a83f0dff0ee9fa8ccd0ebdaf7986647
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -25
- package/dist/assets/index-B2UYAO1O.css +1 -0
- package/dist/assets/index-BexxGuN6.js +1224 -0
- package/dist/assets/{index.es-CYeQ4a5s.js → index.es-CWwhWGxX.js} +5 -5
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/business-transactions/business-extended-info.tsx +15 -13
- package/src/components/business-transactions/business-transactions-single.tsx +3 -3
- package/src/components/business-transactions/index.tsx +1 -12
- package/src/components/business-trips/business-trip.tsx +3 -3
- package/src/components/charges/cells/business-trip.tsx +8 -6
- package/src/components/charges/cells/counterparty.tsx +5 -7
- package/src/components/common/accounter-table.tsx +5 -6
- package/src/components/common/business-trip-report/parts/core-expense-row.tsx +9 -11
- package/src/components/common/business-trip-report/parts/uncategorized-transactions.tsx +13 -11
- package/src/components/common/buttons/button-with-label.tsx +41 -0
- package/src/components/common/buttons/button.tsx +44 -0
- package/src/components/common/buttons/index.ts +2 -0
- package/src/components/common/buttons/logout-button.tsx +6 -7
- package/src/components/common/documents-to-charge-matcher/selection-handler/index.tsx +2 -4
- package/src/components/common/documents-to-charge-matcher/selection-handler/wide-filtered-selection.tsx +7 -5
- package/src/components/common/forms/edit-document.tsx +10 -23
- package/src/components/common/new-documents-list.tsx +8 -10
- package/src/components/documents-table/cells/creditor.tsx +4 -11
- package/src/components/documents-table/cells/debtor.tsx +4 -11
- package/src/components/layout/dashboard-layout.tsx +0 -4
- package/src/components/layout/sidelinks.tsx +27 -28
- package/src/components/ledger-table/counterparty-cell.tsx +13 -19
- package/src/components/login-page.tsx +1 -2
- package/src/components/reports/corporate-tax-ruling-compliance-report/index.tsx +3 -3
- package/src/components/reports/profit-and-loss-report/index.tsx +3 -3
- package/src/components/reports/tax-report/index.tsx +3 -3
- package/src/components/screens/businesses/business.tsx +9 -21
- package/src/components/screens/charges/charge.tsx +9 -22
- package/src/components/transactions-table/cells/counterparty.tsx +2 -9
- package/src/components/transactions-table/cells-legacy/counterparty.tsx +2 -9
- package/src/index.tsx +22 -4
- package/src/providers/auth-guard.tsx +23 -14
- package/src/providers/index.tsx +2 -7
- package/src/providers/urql.tsx +12 -7
- package/src/providers/user-provider.tsx +2 -3
- package/dist/assets/Checkbox-CxedbJAl.js +0 -6
- package/dist/assets/Progress-D5SuJtCd.js +0 -1
- package/dist/assets/Typography-BQFz-z7L.js +0 -1
- package/dist/assets/accordion-COWOBKuq.js +0 -1
- package/dist/assets/accountant-approvals-Bd2y8us_.js +0 -1
- package/dist/assets/all-charges-SWBnaZu7.js +0 -1
- package/dist/assets/arrow-up-down-dZmrBLse.js +0 -6
- package/dist/assets/business--GVVfDEa.js +0 -37
- package/dist/assets/business-transactions-single-BsbkUf_H.js +0 -1
- package/dist/assets/business-trip-ByXPVXdG.js +0 -1
- package/dist/assets/charges-filters-D43UbXob.js +0 -1
- package/dist/assets/charges-ledger-validation-D0uMH_JE.js +0 -1
- package/dist/assets/chart-ClU1KbWe.js +0 -74
- package/dist/assets/data-table-pagination-D9Y0_Tn8.js +0 -11
- package/dist/assets/editable-business-trip-DhqOQBPa.js +0 -16
- package/dist/assets/graphql-document-dedupe-fragments-ByT8-wlV.js +0 -1
- package/dist/assets/index-1U6rQgQe.js +0 -6
- package/dist/assets/index-3-AKn8tg.js +0 -1
- package/dist/assets/index-91A2PLZ6.js +0 -137
- package/dist/assets/index-BBHuCWRn.js +0 -1
- package/dist/assets/index-BPNuFFtx.js +0 -1
- package/dist/assets/index-BXqHnRVY.js +0 -1
- package/dist/assets/index-BciOH8FS.js +0 -1
- package/dist/assets/index-BjHuUHDO.js +0 -1
- package/dist/assets/index-BxKmoNQd.js +0 -1
- package/dist/assets/index-C3bqiFIv.js +0 -2
- package/dist/assets/index-C5MeepK_.js +0 -11
- package/dist/assets/index-CAwm68Mg.js +0 -1
- package/dist/assets/index-CJ8OGXxv.js +0 -1
- package/dist/assets/index-CJyY-qF6.js +0 -1
- package/dist/assets/index-CMYnx46_.js +0 -6
- package/dist/assets/index-CNrwxUZ7.js +0 -1
- package/dist/assets/index-CvV5z5r9.js +0 -876
- package/dist/assets/index-D08H2GXq.js +0 -17
- package/dist/assets/index-GFsPY1p4.js +0 -2
- package/dist/assets/index-KwNwThNu.js +0 -1
- package/dist/assets/index-YA8IBFyB.js +0 -1
- package/dist/assets/index-ZpyI3qxW.js +0 -24
- package/dist/assets/index-gdTXrWXt.css +0 -1
- package/dist/assets/index-ytnIEraq.js +0 -9
- package/dist/assets/issue-document-CdikNnO2.js +0 -1
- package/dist/assets/login-page-effgZS3V.js +0 -1
- package/dist/assets/missing-info-charges-CnPFTzoZ.js +0 -1
- package/dist/assets/page-not-found-D8YlgDOm.js +0 -1
- package/dist/assets/pencil-mxW0-tGM.js +0 -6
- package/dist/assets/report-commentary-row-DCozKgVE.js +0 -1
- package/dist/assets/save-CHlytUqu.js +0 -11
- package/dist/assets/sequential-CAnleQny.js +0 -1
- package/dist/assets/similar-charges-by-business-modal-Dzbspk_r.js +0 -1
- package/dist/assets/sub-Cp_PhKiD.js +0 -1
- package/dist/assets/subMonths-DCj_iXAn.js +0 -1
- package/src/components/error-boundary.tsx +0 -189
- package/src/components/layout/breadcrumbs.tsx +0 -77
- package/src/components/layout/document-title.tsx +0 -31
- package/src/components/layout/navigation-progress.tsx +0 -52
- package/src/components/layout/page-skeleton.tsx +0 -49
- package/src/providers/urql-client.ts +0 -86
- package/src/router/config.tsx +0 -534
- package/src/router/layouts/dashboard-layout.tsx +0 -20
- package/src/router/layouts/root-layout.tsx +0 -69
- package/src/router/loaders/auth-loader.ts +0 -32
- package/src/router/loaders/business-loader.ts +0 -25
- package/src/router/loaders/charge-loader.ts +0 -25
- package/src/router/loaders/index.ts +0 -17
- package/src/router/routes.ts +0 -88
- package/src/router/types.ts +0 -62
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { type ReactElement } from 'react';
|
|
2
|
-
import { AlertCircle, Home, RefreshCw } from 'lucide-react';
|
|
3
|
-
import { isRouteErrorResponse, Link, useRouteError } from 'react-router-dom';
|
|
4
|
-
import { ROUTES } from '../router/routes.js';
|
|
5
|
-
import { Button } from './ui/button.js';
|
|
6
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Global error boundary for router errors
|
|
10
|
-
* Handles different error types and provides appropriate UI
|
|
11
|
-
*/
|
|
12
|
-
export function ErrorBoundary(): ReactElement {
|
|
13
|
-
const error = useRouteError();
|
|
14
|
-
|
|
15
|
-
// Handle route errors (404, 401, 500, etc.)
|
|
16
|
-
if (isRouteErrorResponse(error)) {
|
|
17
|
-
switch (error.status) {
|
|
18
|
-
case 404:
|
|
19
|
-
return (
|
|
20
|
-
<ErrorLayout
|
|
21
|
-
icon={<AlertCircle className="h-16 w-16 text-yellow-500" />}
|
|
22
|
-
title="Page Not Found"
|
|
23
|
-
description="The page you're looking for doesn't exist."
|
|
24
|
-
statusCode={404}
|
|
25
|
-
>
|
|
26
|
-
<div className="flex gap-4">
|
|
27
|
-
<Link to={ROUTES.HOME}>
|
|
28
|
-
<Button variant="default">
|
|
29
|
-
<Home className="mr-2 h-4 w-4" />
|
|
30
|
-
Go Home
|
|
31
|
-
</Button>
|
|
32
|
-
</Link>
|
|
33
|
-
<Button variant="outline" onClick={() => window.history.back()}>
|
|
34
|
-
Go Back
|
|
35
|
-
</Button>
|
|
36
|
-
</div>
|
|
37
|
-
</ErrorLayout>
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
case 401:
|
|
41
|
-
return (
|
|
42
|
-
<ErrorLayout
|
|
43
|
-
icon={<AlertCircle className="h-16 w-16 text-red-500" />}
|
|
44
|
-
title="Unauthorized"
|
|
45
|
-
description="You need to be logged in to access this page."
|
|
46
|
-
statusCode={401}
|
|
47
|
-
>
|
|
48
|
-
<Link to={ROUTES.LOGIN}>
|
|
49
|
-
<Button variant="default">Go to Login</Button>
|
|
50
|
-
</Link>
|
|
51
|
-
</ErrorLayout>
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
case 403:
|
|
55
|
-
return (
|
|
56
|
-
<ErrorLayout
|
|
57
|
-
icon={<AlertCircle className="h-16 w-16 text-red-500" />}
|
|
58
|
-
title="Forbidden"
|
|
59
|
-
description="You don't have permission to access this resource."
|
|
60
|
-
statusCode={403}
|
|
61
|
-
>
|
|
62
|
-
<Link to={ROUTES.HOME}>
|
|
63
|
-
<Button variant="default">
|
|
64
|
-
<Home className="mr-2 h-4 w-4" />
|
|
65
|
-
Go Home
|
|
66
|
-
</Button>
|
|
67
|
-
</Link>
|
|
68
|
-
</ErrorLayout>
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
case 500:
|
|
72
|
-
return (
|
|
73
|
-
<ErrorLayout
|
|
74
|
-
icon={<AlertCircle className="h-16 w-16 text-red-500" />}
|
|
75
|
-
title="Server Error"
|
|
76
|
-
description="Something went wrong on our end. We're working to fix it."
|
|
77
|
-
statusCode={500}
|
|
78
|
-
>
|
|
79
|
-
<div className="flex gap-4">
|
|
80
|
-
<Button variant="default" onClick={() => window.location.reload()}>
|
|
81
|
-
<RefreshCw className="mr-2 h-4 w-4" />
|
|
82
|
-
Reload Page
|
|
83
|
-
</Button>
|
|
84
|
-
<Link to={ROUTES.HOME}>
|
|
85
|
-
<Button variant="outline">
|
|
86
|
-
<Home className="mr-2 h-4 w-4" />
|
|
87
|
-
Go Home
|
|
88
|
-
</Button>
|
|
89
|
-
</Link>
|
|
90
|
-
</div>
|
|
91
|
-
</ErrorLayout>
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
default:
|
|
95
|
-
return (
|
|
96
|
-
<ErrorLayout
|
|
97
|
-
icon={<AlertCircle className="h-16 w-16 text-orange-500" />}
|
|
98
|
-
title={`Error ${error.status}`}
|
|
99
|
-
description={error.statusText || 'An unexpected error occurred'}
|
|
100
|
-
statusCode={error.status}
|
|
101
|
-
>
|
|
102
|
-
<Link to={ROUTES.HOME}>
|
|
103
|
-
<Button variant="default">
|
|
104
|
-
<Home className="mr-2 h-4 w-4" />
|
|
105
|
-
Go Home
|
|
106
|
-
</Button>
|
|
107
|
-
</Link>
|
|
108
|
-
</ErrorLayout>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Handle generic JavaScript errors
|
|
114
|
-
const genericError = error as Error;
|
|
115
|
-
const isDevelopment = import.meta.env.DEV;
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<ErrorLayout
|
|
119
|
-
icon={<AlertCircle className="h-16 w-16 text-red-500" />}
|
|
120
|
-
title="Unexpected Error"
|
|
121
|
-
description="An unexpected error occurred. Please try again."
|
|
122
|
-
>
|
|
123
|
-
{isDevelopment && genericError.message && (
|
|
124
|
-
<Card className="max-w-2xl mt-4 bg-red-50 border-red-200">
|
|
125
|
-
<CardHeader>
|
|
126
|
-
<CardTitle className="text-sm text-red-700">Error Details (Dev Only)</CardTitle>
|
|
127
|
-
</CardHeader>
|
|
128
|
-
<CardContent>
|
|
129
|
-
<pre className="text-xs text-red-600 overflow-auto">
|
|
130
|
-
{genericError.message}
|
|
131
|
-
{genericError.stack && `\n\n${genericError.stack}`}
|
|
132
|
-
</pre>
|
|
133
|
-
</CardContent>
|
|
134
|
-
</Card>
|
|
135
|
-
)}
|
|
136
|
-
<div className="flex gap-4 mt-4">
|
|
137
|
-
<Button variant="default" onClick={() => window.location.reload()}>
|
|
138
|
-
<RefreshCw className="mr-2 h-4 w-4" />
|
|
139
|
-
Reload Page
|
|
140
|
-
</Button>
|
|
141
|
-
<Link to={ROUTES.HOME}>
|
|
142
|
-
<Button variant="outline">
|
|
143
|
-
<Home className="mr-2 h-4 w-4" />
|
|
144
|
-
Go Home
|
|
145
|
-
</Button>
|
|
146
|
-
</Link>
|
|
147
|
-
</div>
|
|
148
|
-
</ErrorLayout>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Reusable error layout component
|
|
154
|
-
*/
|
|
155
|
-
interface ErrorLayoutProps {
|
|
156
|
-
icon: ReactElement;
|
|
157
|
-
title: string;
|
|
158
|
-
description: string;
|
|
159
|
-
statusCode?: number;
|
|
160
|
-
children?: React.ReactNode;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function ErrorLayout({
|
|
164
|
-
icon,
|
|
165
|
-
title,
|
|
166
|
-
description,
|
|
167
|
-
statusCode,
|
|
168
|
-
children,
|
|
169
|
-
}: ErrorLayoutProps): ReactElement {
|
|
170
|
-
return (
|
|
171
|
-
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
|
|
172
|
-
<Card className="max-w-md w-full">
|
|
173
|
-
<CardHeader className="text-center space-y-4">
|
|
174
|
-
<div className="flex justify-center">{icon}</div>
|
|
175
|
-
<div>
|
|
176
|
-
<CardTitle className="text-2xl font-bold">
|
|
177
|
-
{title}
|
|
178
|
-
{statusCode && (
|
|
179
|
-
<span className="ml-2 text-sm text-muted-foreground">({statusCode})</span>
|
|
180
|
-
)}
|
|
181
|
-
</CardTitle>
|
|
182
|
-
<CardDescription className="mt-2">{description}</CardDescription>
|
|
183
|
-
</div>
|
|
184
|
-
</CardHeader>
|
|
185
|
-
<CardContent className="flex flex-col items-center space-y-4">{children}</CardContent>
|
|
186
|
-
</Card>
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Fragment, type ReactElement } from 'react';
|
|
2
|
-
import { ChevronRight, Home } from 'lucide-react';
|
|
3
|
-
import { Link, useMatches } from 'react-router-dom';
|
|
4
|
-
|
|
5
|
-
interface RouteHandle {
|
|
6
|
-
breadcrumb?: string | ((data?: unknown) => string);
|
|
7
|
-
title?: string | ((data?: unknown) => string);
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface Crumb {
|
|
12
|
-
title: string;
|
|
13
|
-
path: string;
|
|
14
|
-
isLast: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Breadcrumbs component - displays navigation path
|
|
19
|
-
* Reads breadcrumb data from route handles
|
|
20
|
-
*/
|
|
21
|
-
export function Breadcrumbs(): ReactElement {
|
|
22
|
-
const matches = useMatches();
|
|
23
|
-
|
|
24
|
-
// Build breadcrumb trail from matched routes
|
|
25
|
-
const crumbs: Crumb[] = matches
|
|
26
|
-
.filter(match => {
|
|
27
|
-
const handle = match.handle as RouteHandle | undefined;
|
|
28
|
-
return handle?.breadcrumb;
|
|
29
|
-
})
|
|
30
|
-
.map((match, index, array) => {
|
|
31
|
-
const handle = match.handle as RouteHandle;
|
|
32
|
-
const title =
|
|
33
|
-
typeof handle.breadcrumb === 'function'
|
|
34
|
-
? handle.breadcrumb(match.data)
|
|
35
|
-
: handle.breadcrumb || 'Page';
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
title,
|
|
39
|
-
path: match.pathname,
|
|
40
|
-
isLast: index === array.length - 1,
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Don't render if no breadcrumbs
|
|
45
|
-
if (crumbs.length === 0) {
|
|
46
|
-
return <div className="h-8" />; // Spacer to maintain layout
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<nav aria-label="Breadcrumb" className="flex items-center space-x-1 text-sm text-gray-600">
|
|
51
|
-
{/* Home icon as first item */}
|
|
52
|
-
<Link
|
|
53
|
-
to="/"
|
|
54
|
-
className="flex items-center hover:text-gray-900 transition-colors"
|
|
55
|
-
aria-label="Home"
|
|
56
|
-
>
|
|
57
|
-
<Home className="h-4 w-4" />
|
|
58
|
-
</Link>
|
|
59
|
-
|
|
60
|
-
{/* Breadcrumb items */}
|
|
61
|
-
{crumbs.map((crumb, index) => (
|
|
62
|
-
<Fragment key={`${crumb.path}-${index}`}>
|
|
63
|
-
<ChevronRight className="h-4 w-4 text-gray-400" />
|
|
64
|
-
{crumb.isLast ? (
|
|
65
|
-
<span className="font-medium text-gray-900" aria-current="page">
|
|
66
|
-
{crumb.title}
|
|
67
|
-
</span>
|
|
68
|
-
) : (
|
|
69
|
-
<Link to={crumb.path} className="hover:text-gray-900 transition-colors">
|
|
70
|
-
{crumb.title}
|
|
71
|
-
</Link>
|
|
72
|
-
)}
|
|
73
|
-
</Fragment>
|
|
74
|
-
))}
|
|
75
|
-
</nav>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { useMatches } from 'react-router-dom';
|
|
3
|
-
|
|
4
|
-
interface RouteHandle {
|
|
5
|
-
title?: string | ((data?: unknown) => string);
|
|
6
|
-
breadcrumb?: string | ((data?: unknown) => string);
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Component that updates document title based on route handle
|
|
12
|
-
* Place this in the root layout to automatically update titles
|
|
13
|
-
*/
|
|
14
|
-
export function DocumentTitle() {
|
|
15
|
-
const matches = useMatches();
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const lastMatch = matches[matches.length - 1];
|
|
19
|
-
const handle = lastMatch?.handle as RouteHandle | undefined;
|
|
20
|
-
|
|
21
|
-
if (handle?.title) {
|
|
22
|
-
const title =
|
|
23
|
-
typeof handle.title === 'function' ? handle.title(lastMatch.data) : handle.title;
|
|
24
|
-
document.title = `${title} - Accounter`;
|
|
25
|
-
} else {
|
|
26
|
-
document.title = 'Accounter';
|
|
27
|
-
}
|
|
28
|
-
}, [matches]);
|
|
29
|
-
|
|
30
|
-
return <div style={{ display: 'none' }} />;
|
|
31
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { useNavigation } from 'react-router-dom';
|
|
3
|
-
import { Progress } from '../ui/progress.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Global navigation progress indicator
|
|
7
|
-
* Shows a loading bar at the top of the page during route transitions
|
|
8
|
-
*/
|
|
9
|
-
export function NavigationProgress() {
|
|
10
|
-
const navigation = useNavigation();
|
|
11
|
-
const [progress, setProgress] = useState(0);
|
|
12
|
-
const isLoading = navigation.state === 'loading';
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (isLoading) {
|
|
16
|
-
// Start at 20% when loading begins
|
|
17
|
-
setProgress(20);
|
|
18
|
-
|
|
19
|
-
// Simulate progress
|
|
20
|
-
const interval = setInterval(() => {
|
|
21
|
-
setProgress(prev => {
|
|
22
|
-
// Asymptotically approach 90% but never quite reach it
|
|
23
|
-
const next = prev + (90 - prev) * 0.1;
|
|
24
|
-
return Math.min(next, 90);
|
|
25
|
-
});
|
|
26
|
-
}, 300);
|
|
27
|
-
|
|
28
|
-
return () => clearInterval(interval);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Complete the progress when loading finishes
|
|
32
|
-
setProgress(100);
|
|
33
|
-
|
|
34
|
-
// Reset after animation
|
|
35
|
-
const timeout = setTimeout(() => {
|
|
36
|
-
setProgress(0);
|
|
37
|
-
}, 500);
|
|
38
|
-
|
|
39
|
-
return () => clearTimeout(timeout);
|
|
40
|
-
}, [isLoading]);
|
|
41
|
-
|
|
42
|
-
// Don't render anything if not loading and progress is 0
|
|
43
|
-
if (!isLoading && progress === 0) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className="fixed top-0 left-0 right-0 z-50 h-1">
|
|
49
|
-
<Progress value={progress} className="h-1 rounded-none transition-all duration-300" />
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from 'react';
|
|
2
|
-
import { Skeleton } from '../ui/skeleton.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generic page loading skeleton
|
|
6
|
-
* Displayed during route transitions
|
|
7
|
-
*/
|
|
8
|
-
export function PageSkeleton(): ReactElement {
|
|
9
|
-
return (
|
|
10
|
-
<div className="flex flex-col gap-4 p-4">
|
|
11
|
-
<Skeleton className="h-12 w-64" /> {/* Page title */}
|
|
12
|
-
<div className="flex gap-4">
|
|
13
|
-
<Skeleton className="h-10 w-32" /> {/* Button/filter */}
|
|
14
|
-
<Skeleton className="h-10 w-32" />
|
|
15
|
-
</div>
|
|
16
|
-
<Skeleton className="h-96 w-full" /> {/* Main content area */}
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Table loading skeleton
|
|
23
|
-
*/
|
|
24
|
-
export function TableSkeleton(): ReactElement {
|
|
25
|
-
return (
|
|
26
|
-
<div className="flex flex-col gap-2">
|
|
27
|
-
<Skeleton className="h-12 w-full" /> {/* Header */}
|
|
28
|
-
{Array.from({ length: 8 }).map((_, i) => (
|
|
29
|
-
<Skeleton key={i} className="h-16 w-full" />
|
|
30
|
-
))}
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Report loading skeleton
|
|
37
|
-
*/
|
|
38
|
-
export function ReportSkeleton(): ReactElement {
|
|
39
|
-
return (
|
|
40
|
-
<div className="flex flex-col gap-6">
|
|
41
|
-
<Skeleton className="h-16 w-96" /> {/* Report title */}
|
|
42
|
-
<div className="flex gap-4">
|
|
43
|
-
<Skeleton className="h-10 w-48" /> {/* Date filter */}
|
|
44
|
-
<Skeleton className="h-10 w-32" /> {/* Export button */}
|
|
45
|
-
</div>
|
|
46
|
-
<Skeleton className="h-[600px] w-full" /> {/* Report content */}
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClient,
|
|
3
|
-
fetchExchange,
|
|
4
|
-
mapExchange,
|
|
5
|
-
type AnyVariables,
|
|
6
|
-
type Client,
|
|
7
|
-
type Operation,
|
|
8
|
-
} from 'urql';
|
|
9
|
-
import { authExchange } from '@urql/exchange-auth';
|
|
10
|
-
import { UserService } from '../services/user-service.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Singleton URQL client for use in loaders and server-side operations
|
|
14
|
-
* This is separate from the Provider client to avoid React context dependencies
|
|
15
|
-
*/
|
|
16
|
-
let globalClient: Client | null = null;
|
|
17
|
-
|
|
18
|
-
export function getUrqlClient(): Client {
|
|
19
|
-
if (globalClient) {
|
|
20
|
-
return globalClient;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const authService = new UserService();
|
|
24
|
-
|
|
25
|
-
let url: string;
|
|
26
|
-
switch (import.meta.env.MODE) {
|
|
27
|
-
case 'production': {
|
|
28
|
-
url = 'https://accounter.onrender.com/graphql';
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
case 'staging': {
|
|
32
|
-
url = 'https://accounter-staging.onrender.com/graphql';
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
default: {
|
|
36
|
-
url = 'http://localhost:4000/graphql';
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
globalClient = createClient({
|
|
42
|
-
url,
|
|
43
|
-
exchanges: [
|
|
44
|
-
mapExchange({
|
|
45
|
-
onResult(result) {
|
|
46
|
-
if (result.error?.networkError) {
|
|
47
|
-
console.error('Network Error:', result.error.networkError);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
}),
|
|
51
|
-
authExchange(async utils => {
|
|
52
|
-
return {
|
|
53
|
-
addAuthToOperation(operation): Operation<void, AnyVariables> {
|
|
54
|
-
const token = authService.authToken();
|
|
55
|
-
if (!token) {
|
|
56
|
-
return operation;
|
|
57
|
-
}
|
|
58
|
-
return utils.appendHeaders(operation, {
|
|
59
|
-
Authorization: token,
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
didAuthError(error, _operation): boolean {
|
|
63
|
-
return (
|
|
64
|
-
error?.response?.status === 401 ||
|
|
65
|
-
error?.graphQLErrors?.some(e => e.extensions?.code === 'FORBIDDEN')
|
|
66
|
-
);
|
|
67
|
-
},
|
|
68
|
-
async refreshAuth(): Promise<void> {
|
|
69
|
-
authService.logout();
|
|
70
|
-
// Redirect handled by route loader
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}),
|
|
74
|
-
fetchExchange,
|
|
75
|
-
],
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return globalClient;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Reset the global client (useful for tests or logout)
|
|
83
|
-
*/
|
|
84
|
-
export function resetUrqlClient(): void {
|
|
85
|
-
globalClient = null;
|
|
86
|
-
}
|