create-bluecopa-react-app 1.0.41 → 1.0.42
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/README.md +7 -5
- package/package.json +1 -1
- package/templates/latest/.claude/settings.local.json +56 -0
- package/templates/latest/.env.example +8 -0
- package/templates/latest/Agent.md +598 -775
- package/templates/latest/CLAUDE.md +759 -0
- package/templates/latest/README.md +11 -2
- package/templates/latest/app/app.css +292 -85
- package/templates/latest/app/app.tsx +48 -39
- package/templates/latest/app/components/bluecopa-logo.tsx +20 -0
- package/templates/latest/app/components/charts/bar-chart.tsx +132 -0
- package/templates/latest/app/components/charts/base-chart.tsx +149 -0
- package/templates/latest/app/components/charts/chart-provider.tsx +71 -0
- package/templates/latest/app/components/charts/chart-theme.ts +262 -0
- package/templates/latest/app/components/charts/chart-utils.ts +142 -0
- package/templates/latest/app/components/charts/donut-chart.tsx +110 -0
- package/templates/latest/app/components/charts/index.ts +5 -0
- package/templates/latest/app/components/layouts/app-layout.tsx +22 -0
- package/templates/latest/app/components/layouts/app-sidebar.tsx +88 -0
- package/templates/latest/app/components/layouts/nav-main.tsx +50 -0
- package/templates/latest/app/components/layouts/nav-user.tsx +38 -0
- package/templates/latest/app/components/layouts/site-header.tsx +93 -0
- package/templates/latest/app/components/loading-screen.tsx +41 -0
- package/templates/latest/app/components/ui/ag-grid-table.tsx +79 -0
- package/templates/latest/app/components/ui/button.tsx +23 -23
- package/templates/latest/app/components/ui/card.tsx +20 -20
- package/templates/latest/app/components/ui/dropdown-menu.tsx +54 -49
- package/templates/latest/app/components/ui/input.tsx +8 -8
- package/templates/latest/app/components/ui/label.tsx +8 -8
- package/templates/latest/app/components/ui/separator.tsx +7 -7
- package/templates/latest/app/components/ui/sheet.tsx +43 -32
- package/templates/latest/app/components/ui/sidebar.tsx +240 -235
- package/templates/latest/app/components/ui/skeleton.tsx +4 -4
- package/templates/latest/app/components/ui/sonner.tsx +6 -9
- package/templates/latest/app/components/ui/tabs.tsx +15 -15
- package/templates/latest/app/components/ui/tooltip.tsx +18 -12
- package/templates/latest/app/constants/index.ts +31 -0
- package/templates/latest/app/contexts/app-context.tsx +201 -0
- package/templates/latest/app/hooks/use-mobile.ts +13 -12
- package/templates/latest/app/main.tsx +1 -1
- package/templates/latest/app/pages/dashboard.tsx +246 -0
- package/templates/latest/app/pages/payments.tsx +182 -0
- package/templates/latest/app/pages/settings.tsx +128 -0
- package/templates/latest/app/routes/index.tsx +19 -0
- package/templates/latest/app/single-spa.tsx +69 -186
- package/templates/latest/app/types/index.ts +37 -0
- package/templates/latest/app/utils/ag-grid-datasource.ts +63 -0
- package/templates/latest/app/utils/ag-grid-license.ts +12 -0
- package/templates/latest/app/utils/ag-grid-theme.ts +9 -0
- package/templates/latest/app/utils/component-style.ts +7 -0
- package/templates/latest/app/utils/style-drivers.ts +24 -0
- package/templates/latest/app/utils/utils.ts +10 -0
- package/templates/latest/components.json +3 -3
- package/templates/latest/index.html +30 -2
- package/templates/latest/package-lock.json +15 -401
- package/templates/latest/package.json +8 -18
- package/templates/latest/preview/index.html +125 -285
- package/templates/latest/public/favicon.svg +1 -0
- package/templates/latest/vite.config.ts +2 -8
- package/templates/latest/app/components/app-sidebar.tsx +0 -182
- package/templates/latest/app/components/chart-area-interactive.tsx +0 -290
- package/templates/latest/app/components/data-table.tsx +0 -807
- package/templates/latest/app/components/nav-documents.tsx +0 -92
- package/templates/latest/app/components/nav-main.tsx +0 -40
- package/templates/latest/app/components/nav-secondary.tsx +0 -42
- package/templates/latest/app/components/nav-user.tsx +0 -111
- package/templates/latest/app/components/section-cards.tsx +0 -102
- package/templates/latest/app/components/site-header.tsx +0 -28
- package/templates/latest/app/components/ui/avatar.tsx +0 -53
- package/templates/latest/app/components/ui/badge.tsx +0 -46
- package/templates/latest/app/components/ui/breadcrumb.tsx +0 -109
- package/templates/latest/app/components/ui/chart.tsx +0 -352
- package/templates/latest/app/components/ui/checkbox.tsx +0 -30
- package/templates/latest/app/components/ui/drawer.tsx +0 -139
- package/templates/latest/app/components/ui/select.tsx +0 -183
- package/templates/latest/app/components/ui/table.tsx +0 -117
- package/templates/latest/app/components/ui/toggle-group.tsx +0 -73
- package/templates/latest/app/components/ui/toggle.tsx +0 -47
- package/templates/latest/app/data/data.json +0 -614
- package/templates/latest/app/data/mock-payments.json +0 -122
- package/templates/latest/app/data/mock-transactions.json +0 -128
- package/templates/latest/app/hooks/use-bluecopa-user.ts +0 -37
- package/templates/latest/app/lib/utils.ts +0 -6
- package/templates/latest/app/routes/apitest.tsx +0 -2118
- package/templates/latest/app/routes/comments.tsx +0 -588
- package/templates/latest/app/routes/dashboard.tsx +0 -36
- package/templates/latest/app/routes/payments.tsx +0 -342
- package/templates/latest/app/routes/statements.tsx +0 -493
- package/templates/latest/app/routes/websocket.tsx +0 -450
- package/templates/latest/app/routes.tsx +0 -22
- package/templates/latest/dist/assets/__federation_expose_App-D-lv9y21.js +0 -161
- package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +0 -438
- package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +0 -16
- package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +0 -17
- package/templates/latest/dist/assets/client-Dms8K6Dw.js +0 -78879
- package/templates/latest/dist/assets/index-BrhXrqF7.js +0 -60
- package/templates/latest/dist/assets/index-BzNimew1.js +0 -69
- package/templates/latest/dist/assets/index-DMFtQdNS.js +0 -412
- package/templates/latest/dist/assets/remoteEntry.css +0 -3996
- package/templates/latest/dist/assets/remoteEntry.js +0 -88
- package/templates/latest/dist/favicon.ico +0 -0
- package/templates/latest/dist/index.html +0 -19
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { cn } from "~/
|
|
1
|
+
import { cn } from "~/utils/utils";
|
|
2
2
|
|
|
3
3
|
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
4
|
return (
|
|
5
5
|
<div
|
|
6
6
|
data-slot="skeleton"
|
|
7
|
-
className={cn("bg-accent animate-pulse rounded-md", className)}
|
|
7
|
+
className={cn("copa:bg-accent copa:animate-pulse copa:rounded-md", className)}
|
|
8
8
|
{...props}
|
|
9
9
|
/>
|
|
10
|
-
)
|
|
10
|
+
);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export { Skeleton }
|
|
13
|
+
export { Skeleton };
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
1
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
3
2
|
|
|
4
3
|
const Toaster = ({ ...props }: ToasterProps) => {
|
|
5
|
-
const { theme = "system" } = useTheme()
|
|
6
|
-
|
|
7
4
|
return (
|
|
8
5
|
<Sonner
|
|
9
|
-
theme=
|
|
10
|
-
className="toaster group"
|
|
6
|
+
theme="light"
|
|
7
|
+
className="toaster copa:group"
|
|
11
8
|
style={
|
|
12
9
|
{
|
|
13
10
|
"--normal-bg": "var(--popover)",
|
|
@@ -17,7 +14,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|
|
17
14
|
}
|
|
18
15
|
{...props}
|
|
19
16
|
/>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
17
|
+
);
|
|
18
|
+
};
|
|
22
19
|
|
|
23
|
-
export { Toaster }
|
|
20
|
+
export { Toaster };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
5
|
|
|
6
|
-
import { cn } from "~/
|
|
6
|
+
import { cn } from "~/utils/utils";
|
|
7
7
|
|
|
8
8
|
function Tabs({
|
|
9
9
|
className,
|
|
@@ -12,10 +12,10 @@ function Tabs({
|
|
|
12
12
|
return (
|
|
13
13
|
<TabsPrimitive.Root
|
|
14
14
|
data-slot="tabs"
|
|
15
|
-
className={cn("flex flex-col
|
|
15
|
+
className={cn("copa:flex copa:flex-col", className)}
|
|
16
16
|
{...props}
|
|
17
17
|
/>
|
|
18
|
-
)
|
|
18
|
+
);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function TabsList({
|
|
@@ -26,12 +26,12 @@ function TabsList({
|
|
|
26
26
|
<TabsPrimitive.List
|
|
27
27
|
data-slot="tabs-list"
|
|
28
28
|
className={cn(
|
|
29
|
-
"
|
|
30
|
-
className
|
|
29
|
+
"copa:flex copa:items-center copa:gap-6 copa:border-b copa:border-border",
|
|
30
|
+
className,
|
|
31
31
|
)}
|
|
32
32
|
{...props}
|
|
33
33
|
/>
|
|
34
|
-
)
|
|
34
|
+
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function TabsTrigger({
|
|
@@ -42,12 +42,12 @@ function TabsTrigger({
|
|
|
42
42
|
<TabsPrimitive.Trigger
|
|
43
43
|
data-slot="tabs-trigger"
|
|
44
44
|
className={cn(
|
|
45
|
-
"
|
|
46
|
-
className
|
|
45
|
+
"copa:relative copa:inline-flex copa:items-center copa:justify-center copa:pb-3 copa:text-sm copa:font-medium copa:text-muted-foreground copa:transition-colors copa:hover:text-foreground copa:focus-visible:outline-none copa:data-[state=active]:text-primary copa:data-[state=active]:font-semibold copa:data-[state=active]:after:absolute copa:data-[state=active]:after:bottom-0 copa:data-[state=active]:after:left-0 copa:data-[state=active]:after:right-0 copa:data-[state=active]:after:h-0.5 copa:data-[state=active]:after:bg-primary copa:data-[state=active]:after:rounded-full copa:data-[disabled]:pointer-events-none copa:data-[disabled]:opacity-50",
|
|
46
|
+
className,
|
|
47
47
|
)}
|
|
48
48
|
{...props}
|
|
49
49
|
/>
|
|
50
|
-
)
|
|
50
|
+
);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function TabsContent({
|
|
@@ -57,10 +57,10 @@ function TabsContent({
|
|
|
57
57
|
return (
|
|
58
58
|
<TabsPrimitive.Content
|
|
59
59
|
data-slot="tabs-content"
|
|
60
|
-
className={cn("flex-1 outline-none", className)}
|
|
60
|
+
className={cn("copa:flex-1 copa:outline-none copa:pt-6", className)}
|
|
61
61
|
{...props}
|
|
62
62
|
/>
|
|
63
|
-
)
|
|
63
|
+
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
66
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
3
|
|
|
4
|
-
import { cn } from "~/
|
|
4
|
+
import { cn } from "~/utils/utils";
|
|
5
5
|
|
|
6
6
|
function TooltipProvider({
|
|
7
7
|
delayDuration = 0,
|
|
@@ -13,7 +13,7 @@ function TooltipProvider({
|
|
|
13
13
|
delayDuration={delayDuration}
|
|
14
14
|
{...props}
|
|
15
15
|
/>
|
|
16
|
-
)
|
|
16
|
+
);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function Tooltip({
|
|
@@ -23,13 +23,13 @@ function Tooltip({
|
|
|
23
23
|
<TooltipProvider>
|
|
24
24
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
25
|
</TooltipProvider>
|
|
26
|
-
)
|
|
26
|
+
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function TooltipTrigger({
|
|
30
30
|
...props
|
|
31
31
|
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
32
|
-
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props}
|
|
32
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function TooltipContent({
|
|
@@ -39,21 +39,27 @@ function TooltipContent({
|
|
|
39
39
|
...props
|
|
40
40
|
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
41
41
|
return (
|
|
42
|
-
<TooltipPrimitive.Portal
|
|
42
|
+
<TooltipPrimitive.Portal
|
|
43
|
+
container={
|
|
44
|
+
typeof document !== "undefined"
|
|
45
|
+
? document.querySelector<HTMLElement>(".mfe-root") ?? undefined
|
|
46
|
+
: undefined
|
|
47
|
+
}
|
|
48
|
+
>
|
|
43
49
|
<TooltipPrimitive.Content
|
|
44
50
|
data-slot="tooltip-content"
|
|
45
51
|
sideOffset={sideOffset}
|
|
46
52
|
className={cn(
|
|
47
|
-
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
48
|
-
className
|
|
53
|
+
"copa:bg-primary copa:text-primary-foreground copa:animate-in copa:fade-in-0 copa:zoom-in-95 copa:data-[state=closed]:animate-out copa:data-[state=closed]:fade-out-0 copa:data-[state=closed]:zoom-out-95 copa:data-[side=bottom]:slide-in-from-top-2 copa:data-[side=left]:slide-in-from-right-2 copa:data-[side=right]:slide-in-from-left-2 copa:data-[side=top]:slide-in-from-bottom-2 copa:z-50 copa:w-fit copa:origin-(--radix-tooltip-content-transform-origin) copa:rounded-md copa:px-3 copa:py-1.5 copa:text-xs copa:text-balance",
|
|
54
|
+
className,
|
|
49
55
|
)}
|
|
50
56
|
{...props}
|
|
51
57
|
>
|
|
52
58
|
{children}
|
|
53
|
-
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
59
|
+
<TooltipPrimitive.Arrow className="copa:bg-primary copa:fill-primary copa:z-50 copa:size-2.5 copa:translate-y-[calc(-50%_-_2px)] copa:rotate-45 copa:rounded-[2px]" />
|
|
54
60
|
</TooltipPrimitive.Content>
|
|
55
61
|
</TooltipPrimitive.Portal>
|
|
56
|
-
)
|
|
62
|
+
);
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
65
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { WorkspaceDataSettings } from "~/types";
|
|
2
|
+
|
|
3
|
+
// ─── Route Paths ────────────────────────────────────────────────────────────
|
|
4
|
+
export const ROUTES = {
|
|
5
|
+
DASHBOARD: "/",
|
|
6
|
+
PAYMENTS: "/payments",
|
|
7
|
+
SETTINGS: "/settings",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
// ─── API Defaults ───────────────────────────────────────────────────────────
|
|
11
|
+
export const DEFAULT_API_BASE_URL = "http://localhost:3000/api/v1";
|
|
12
|
+
export const DEFAULT_WORKSPACE_ID = "prod";
|
|
13
|
+
|
|
14
|
+
/** React Query defaults */
|
|
15
|
+
export const QUERY_DEFAULTS = {
|
|
16
|
+
STALE_TIME: 5 * 60 * 1000, // 5 minutes
|
|
17
|
+
RETRY: 1,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
// ─── Workspace Defaults ─────────────────────────────────────────────────────
|
|
21
|
+
export const DEFAULT_WORKSPACE_SETTINGS: WorkspaceDataSettings = {
|
|
22
|
+
currency: "USD",
|
|
23
|
+
timezone: "UTC",
|
|
24
|
+
dateFormat: "MM/DD/YYYY HH:mm:ss",
|
|
25
|
+
fiscalYear: { yearType: "calendar_year" },
|
|
26
|
+
numberSettings: { numberSystem: "intl", defaultPrecision: 2 },
|
|
27
|
+
weekStartDay: "SUNDAY",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ─── Breakpoints ────────────────────────────────────────────────────────────
|
|
31
|
+
export const MOBILE_BREAKPOINT = 768;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState, type ReactNode } from "react";
|
|
2
|
+
import { useUser, useGetAllUsers } from "@bluecopa/react";
|
|
3
|
+
import type { AppUser, WorkspaceDataSettings } from "~/types";
|
|
4
|
+
import { DEFAULT_WORKSPACE_SETTINGS } from "~/constants";
|
|
5
|
+
|
|
6
|
+
interface AppContextValue {
|
|
7
|
+
user: AppUser;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
refetch: () => void;
|
|
11
|
+
workspaceIds: string[];
|
|
12
|
+
teams: unknown[];
|
|
13
|
+
isAdmin: boolean;
|
|
14
|
+
users: unknown[];
|
|
15
|
+
usersLoading: boolean;
|
|
16
|
+
usersError: Error | null;
|
|
17
|
+
/** Workspace data settings (currency, timezone, date format, fiscal year, number settings, week start day) */
|
|
18
|
+
currentWorkspaceSettings: WorkspaceDataSettings;
|
|
19
|
+
setCurrency: (currency: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AppContext = createContext<AppContextValue | undefined>(undefined);
|
|
23
|
+
|
|
24
|
+
const fallbackUser = {
|
|
25
|
+
userId: "Not Logged In",
|
|
26
|
+
userExists: false,
|
|
27
|
+
settings: {},
|
|
28
|
+
workspaceIds: [] as string[],
|
|
29
|
+
currentWorkspace: null,
|
|
30
|
+
teams: [] as unknown[],
|
|
31
|
+
userTeamProperties: [] as unknown[],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const defaultWorkspaceDataSettings = DEFAULT_WORKSPACE_SETTINGS;
|
|
35
|
+
|
|
36
|
+
function getWorkspaceDataSettings(workspace: unknown): WorkspaceDataSettings {
|
|
37
|
+
if (!workspace || typeof workspace !== "object") return defaultWorkspaceDataSettings;
|
|
38
|
+
const w = workspace as Record<string, unknown>;
|
|
39
|
+
const customProperties = w.customProperties as Record<string, unknown> | undefined;
|
|
40
|
+
const workspaceSettings = customProperties?.workspaceSettings as Record<string, unknown> | undefined;
|
|
41
|
+
const currencySettings = workspaceSettings?.currencySettings as Record<string, unknown> | undefined;
|
|
42
|
+
const defaultCurrency = currencySettings?.defaultCurrency as { value?: string } | undefined;
|
|
43
|
+
const calendarSettings = workspaceSettings?.calendarSettings as Record<string, unknown> | undefined;
|
|
44
|
+
const numberSettings = workspaceSettings?.numberSettings as Record<string, unknown> | undefined;
|
|
45
|
+
const fiscalYears = w.fiscalYears as Array<{ startMonth?: number; startDate?: number; workspaceDefault?: boolean }> | undefined;
|
|
46
|
+
const defaultFiscal = fiscalYears?.find((fy) => fy.workspaceDefault) ?? fiscalYears?.[0];
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
currency: defaultCurrency?.value ?? defaultWorkspaceDataSettings.currency,
|
|
50
|
+
timezone: (workspaceSettings?.timezone as string) ?? defaultWorkspaceDataSettings.timezone,
|
|
51
|
+
dateFormat: (workspaceSettings?.dateFormat as string) ?? defaultWorkspaceDataSettings.dateFormat,
|
|
52
|
+
fiscalYear: {
|
|
53
|
+
yearType: (calendarSettings?.yearType as string) ?? defaultWorkspaceDataSettings.fiscalYear.yearType,
|
|
54
|
+
startMonth: defaultFiscal?.startMonth,
|
|
55
|
+
startDate: defaultFiscal?.startDate,
|
|
56
|
+
},
|
|
57
|
+
numberSettings: {
|
|
58
|
+
numberSystem: (numberSettings?.numberSystem as string) ?? defaultWorkspaceDataSettings.numberSettings.numberSystem,
|
|
59
|
+
defaultPrecision: (numberSettings?.defaultPrecision as number) ?? defaultWorkspaceDataSettings.numberSettings.defaultPrecision,
|
|
60
|
+
nullPlaceholder: numberSettings?.nullPlaceholder as string | undefined,
|
|
61
|
+
},
|
|
62
|
+
weekStartDay: (w.weekStartDay as string) ?? defaultWorkspaceDataSettings.weekStartDay,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AppProviderProps = {
|
|
67
|
+
children: ReactNode;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function AppProvider({ children }: AppProviderProps) {
|
|
71
|
+
const [currencyOverride, setCurrencyOverride] = useState<string | null>(null);
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
data: apiUsers,
|
|
75
|
+
isLoading: usersLoading,
|
|
76
|
+
error: usersError,
|
|
77
|
+
} = useGetAllUsers({
|
|
78
|
+
enabled: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const { data: user, isLoading, error, refetch } = useUser();
|
|
82
|
+
|
|
83
|
+
const userData = error || !user ? fallbackUser : user;
|
|
84
|
+
const userId = (userData as { userId?: string })?.userId ?? null;
|
|
85
|
+
const currentWorkspace =
|
|
86
|
+
(userData as { currentWorkspace?: unknown })?.currentWorkspace ?? null;
|
|
87
|
+
const workspaceIds =
|
|
88
|
+
(userData as { workspaceIds?: string[] })?.workspaceIds ?? [];
|
|
89
|
+
const teams = (userData as { teams?: unknown[] })?.teams ?? [];
|
|
90
|
+
|
|
91
|
+
const userRole = useMemo(() => {
|
|
92
|
+
if (!teams || teams.length === 0) return "";
|
|
93
|
+
return teams
|
|
94
|
+
.map((team: unknown) => {
|
|
95
|
+
if (typeof team === "string") return team;
|
|
96
|
+
const t = team as { name?: string; teamName?: string };
|
|
97
|
+
return t?.name ?? t?.teamName ?? "";
|
|
98
|
+
})
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join(" ")
|
|
101
|
+
.toLowerCase();
|
|
102
|
+
}, [teams]);
|
|
103
|
+
|
|
104
|
+
const isAdmin = useMemo(() => {
|
|
105
|
+
if (!teams || teams.length === 0) return false;
|
|
106
|
+
return teams.some((team: unknown) => {
|
|
107
|
+
const t = team as { name?: string; teamName?: string };
|
|
108
|
+
const teamName =
|
|
109
|
+
typeof team === "string" ? team : t?.name ?? t?.teamName ?? "";
|
|
110
|
+
return String(teamName).toLowerCase().includes("admin");
|
|
111
|
+
});
|
|
112
|
+
}, [teams]);
|
|
113
|
+
|
|
114
|
+
const usersList = useMemo(() => {
|
|
115
|
+
if (!apiUsers) return [];
|
|
116
|
+
if (Array.isArray(apiUsers)) return apiUsers;
|
|
117
|
+
const obj = apiUsers as Record<string, unknown>;
|
|
118
|
+
if (typeof obj === "object" && "data" in obj) {
|
|
119
|
+
const data = obj.data;
|
|
120
|
+
if (Array.isArray(data)) return data;
|
|
121
|
+
}
|
|
122
|
+
if (typeof obj === "object" && "records" in obj) {
|
|
123
|
+
const records = obj.records;
|
|
124
|
+
if (Array.isArray(records)) return records;
|
|
125
|
+
}
|
|
126
|
+
return [];
|
|
127
|
+
}, [apiUsers]);
|
|
128
|
+
|
|
129
|
+
const foundUser = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
usersList.find(
|
|
132
|
+
(u: unknown) => (u as { id?: string })?.id === userId
|
|
133
|
+
) as { email?: string; } | undefined,
|
|
134
|
+
[usersList, userId]
|
|
135
|
+
);
|
|
136
|
+
const userEmail = foundUser?.email ?? userId;
|
|
137
|
+
|
|
138
|
+
const userWithDetails = useMemo<AppUser>(
|
|
139
|
+
() => ({
|
|
140
|
+
...(typeof userData === "object" && userData !== null
|
|
141
|
+
? (userData as Record<string, unknown>)
|
|
142
|
+
: {}),
|
|
143
|
+
email: userEmail ?? "Not Logged In",
|
|
144
|
+
name: userEmail ?? "Not Logged In",
|
|
145
|
+
role: userRole || "",
|
|
146
|
+
}),
|
|
147
|
+
[userData, userEmail, userRole]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const currentWorkspaceSettings = useMemo<WorkspaceDataSettings>(() => {
|
|
151
|
+
const settings = getWorkspaceDataSettings(currentWorkspace);
|
|
152
|
+
if (currencyOverride) {
|
|
153
|
+
return { ...settings, currency: currencyOverride };
|
|
154
|
+
}
|
|
155
|
+
return settings;
|
|
156
|
+
}, [currentWorkspace, currencyOverride]);
|
|
157
|
+
|
|
158
|
+
const value = useMemo<AppContextValue>(
|
|
159
|
+
() => ({
|
|
160
|
+
user: userWithDetails,
|
|
161
|
+
isLoading,
|
|
162
|
+
error: error ?? null,
|
|
163
|
+
refetch: typeof refetch === "function" ? refetch : () => {},
|
|
164
|
+
workspaceIds,
|
|
165
|
+
teams,
|
|
166
|
+
isAdmin,
|
|
167
|
+
users: usersList,
|
|
168
|
+
usersLoading,
|
|
169
|
+
usersError: usersError ?? null,
|
|
170
|
+
currentWorkspaceSettings,
|
|
171
|
+
setCurrency: setCurrencyOverride,
|
|
172
|
+
}),
|
|
173
|
+
[
|
|
174
|
+
userWithDetails,
|
|
175
|
+
isLoading,
|
|
176
|
+
error,
|
|
177
|
+
refetch,
|
|
178
|
+
userId,
|
|
179
|
+
workspaceIds,
|
|
180
|
+
teams,
|
|
181
|
+
userRole,
|
|
182
|
+
isAdmin,
|
|
183
|
+
usersList,
|
|
184
|
+
usersLoading,
|
|
185
|
+
usersError,
|
|
186
|
+
currentWorkspaceSettings,
|
|
187
|
+
]
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<AppContext.Provider value={value}>{children}</AppContext.Provider>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function useAppContext(): AppContextValue {
|
|
196
|
+
const ctx = useContext(AppContext);
|
|
197
|
+
if (ctx === undefined) {
|
|
198
|
+
throw new Error("useAppContext must be used within an AppProvider");
|
|
199
|
+
}
|
|
200
|
+
return ctx;
|
|
201
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
const MOBILE_BREAKPOINT = 768
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { MOBILE_BREAKPOINT } from "~/constants";
|
|
4
3
|
|
|
5
4
|
export function useIsMobile() {
|
|
6
|
-
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
|
5
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
|
6
|
+
undefined,
|
|
7
|
+
);
|
|
7
8
|
|
|
8
9
|
React.useEffect(() => {
|
|
9
|
-
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
10
11
|
const onChange = () => {
|
|
11
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
-
}
|
|
13
|
-
mql.addEventListener("change", onChange)
|
|
14
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
-
return () => mql.removeEventListener("change", onChange)
|
|
16
|
-
}, [])
|
|
12
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
13
|
+
};
|
|
14
|
+
mql.addEventListener("change", onChange);
|
|
15
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
16
|
+
return () => mql.removeEventListener("change", onChange);
|
|
17
|
+
}, []);
|
|
17
18
|
|
|
18
|
-
return !!isMobile
|
|
19
|
+
return !!isMobile;
|
|
19
20
|
}
|