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.
Files changed (102) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/templates/latest/.claude/settings.local.json +56 -0
  4. package/templates/latest/.env.example +8 -0
  5. package/templates/latest/Agent.md +598 -775
  6. package/templates/latest/CLAUDE.md +759 -0
  7. package/templates/latest/README.md +11 -2
  8. package/templates/latest/app/app.css +292 -85
  9. package/templates/latest/app/app.tsx +48 -39
  10. package/templates/latest/app/components/bluecopa-logo.tsx +20 -0
  11. package/templates/latest/app/components/charts/bar-chart.tsx +132 -0
  12. package/templates/latest/app/components/charts/base-chart.tsx +149 -0
  13. package/templates/latest/app/components/charts/chart-provider.tsx +71 -0
  14. package/templates/latest/app/components/charts/chart-theme.ts +262 -0
  15. package/templates/latest/app/components/charts/chart-utils.ts +142 -0
  16. package/templates/latest/app/components/charts/donut-chart.tsx +110 -0
  17. package/templates/latest/app/components/charts/index.ts +5 -0
  18. package/templates/latest/app/components/layouts/app-layout.tsx +22 -0
  19. package/templates/latest/app/components/layouts/app-sidebar.tsx +88 -0
  20. package/templates/latest/app/components/layouts/nav-main.tsx +50 -0
  21. package/templates/latest/app/components/layouts/nav-user.tsx +38 -0
  22. package/templates/latest/app/components/layouts/site-header.tsx +93 -0
  23. package/templates/latest/app/components/loading-screen.tsx +41 -0
  24. package/templates/latest/app/components/ui/ag-grid-table.tsx +79 -0
  25. package/templates/latest/app/components/ui/button.tsx +23 -23
  26. package/templates/latest/app/components/ui/card.tsx +20 -20
  27. package/templates/latest/app/components/ui/dropdown-menu.tsx +54 -49
  28. package/templates/latest/app/components/ui/input.tsx +8 -8
  29. package/templates/latest/app/components/ui/label.tsx +8 -8
  30. package/templates/latest/app/components/ui/separator.tsx +7 -7
  31. package/templates/latest/app/components/ui/sheet.tsx +43 -32
  32. package/templates/latest/app/components/ui/sidebar.tsx +240 -235
  33. package/templates/latest/app/components/ui/skeleton.tsx +4 -4
  34. package/templates/latest/app/components/ui/sonner.tsx +6 -9
  35. package/templates/latest/app/components/ui/tabs.tsx +15 -15
  36. package/templates/latest/app/components/ui/tooltip.tsx +18 -12
  37. package/templates/latest/app/constants/index.ts +31 -0
  38. package/templates/latest/app/contexts/app-context.tsx +201 -0
  39. package/templates/latest/app/hooks/use-mobile.ts +13 -12
  40. package/templates/latest/app/main.tsx +1 -1
  41. package/templates/latest/app/pages/dashboard.tsx +246 -0
  42. package/templates/latest/app/pages/payments.tsx +182 -0
  43. package/templates/latest/app/pages/settings.tsx +128 -0
  44. package/templates/latest/app/routes/index.tsx +19 -0
  45. package/templates/latest/app/single-spa.tsx +69 -186
  46. package/templates/latest/app/types/index.ts +37 -0
  47. package/templates/latest/app/utils/ag-grid-datasource.ts +63 -0
  48. package/templates/latest/app/utils/ag-grid-license.ts +12 -0
  49. package/templates/latest/app/utils/ag-grid-theme.ts +9 -0
  50. package/templates/latest/app/utils/component-style.ts +7 -0
  51. package/templates/latest/app/utils/style-drivers.ts +24 -0
  52. package/templates/latest/app/utils/utils.ts +10 -0
  53. package/templates/latest/components.json +3 -3
  54. package/templates/latest/index.html +30 -2
  55. package/templates/latest/package-lock.json +15 -401
  56. package/templates/latest/package.json +8 -18
  57. package/templates/latest/preview/index.html +125 -285
  58. package/templates/latest/public/favicon.svg +1 -0
  59. package/templates/latest/vite.config.ts +2 -8
  60. package/templates/latest/app/components/app-sidebar.tsx +0 -182
  61. package/templates/latest/app/components/chart-area-interactive.tsx +0 -290
  62. package/templates/latest/app/components/data-table.tsx +0 -807
  63. package/templates/latest/app/components/nav-documents.tsx +0 -92
  64. package/templates/latest/app/components/nav-main.tsx +0 -40
  65. package/templates/latest/app/components/nav-secondary.tsx +0 -42
  66. package/templates/latest/app/components/nav-user.tsx +0 -111
  67. package/templates/latest/app/components/section-cards.tsx +0 -102
  68. package/templates/latest/app/components/site-header.tsx +0 -28
  69. package/templates/latest/app/components/ui/avatar.tsx +0 -53
  70. package/templates/latest/app/components/ui/badge.tsx +0 -46
  71. package/templates/latest/app/components/ui/breadcrumb.tsx +0 -109
  72. package/templates/latest/app/components/ui/chart.tsx +0 -352
  73. package/templates/latest/app/components/ui/checkbox.tsx +0 -30
  74. package/templates/latest/app/components/ui/drawer.tsx +0 -139
  75. package/templates/latest/app/components/ui/select.tsx +0 -183
  76. package/templates/latest/app/components/ui/table.tsx +0 -117
  77. package/templates/latest/app/components/ui/toggle-group.tsx +0 -73
  78. package/templates/latest/app/components/ui/toggle.tsx +0 -47
  79. package/templates/latest/app/data/data.json +0 -614
  80. package/templates/latest/app/data/mock-payments.json +0 -122
  81. package/templates/latest/app/data/mock-transactions.json +0 -128
  82. package/templates/latest/app/hooks/use-bluecopa-user.ts +0 -37
  83. package/templates/latest/app/lib/utils.ts +0 -6
  84. package/templates/latest/app/routes/apitest.tsx +0 -2118
  85. package/templates/latest/app/routes/comments.tsx +0 -588
  86. package/templates/latest/app/routes/dashboard.tsx +0 -36
  87. package/templates/latest/app/routes/payments.tsx +0 -342
  88. package/templates/latest/app/routes/statements.tsx +0 -493
  89. package/templates/latest/app/routes/websocket.tsx +0 -450
  90. package/templates/latest/app/routes.tsx +0 -22
  91. package/templates/latest/dist/assets/__federation_expose_App-D-lv9y21.js +0 -161
  92. package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +0 -438
  93. package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +0 -16
  94. package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +0 -17
  95. package/templates/latest/dist/assets/client-Dms8K6Dw.js +0 -78879
  96. package/templates/latest/dist/assets/index-BrhXrqF7.js +0 -60
  97. package/templates/latest/dist/assets/index-BzNimew1.js +0 -69
  98. package/templates/latest/dist/assets/index-DMFtQdNS.js +0 -412
  99. package/templates/latest/dist/assets/remoteEntry.css +0 -3996
  100. package/templates/latest/dist/assets/remoteEntry.js +0 -88
  101. package/templates/latest/dist/favicon.ico +0 -0
  102. package/templates/latest/dist/index.html +0 -19
@@ -1,13 +1,13 @@
1
- import { cn } from "~/lib/utils"
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 { useTheme } from "next-themes"
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={theme as ToasterProps["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 "~/lib/utils"
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 gap-2", className)}
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
- "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
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
- "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
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 "~/lib/utils"
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>(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
  }
@@ -8,5 +8,5 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
8
8
  <BrowserRouter>
9
9
  <App />
10
10
  </BrowserRouter>
11
- </React.StrictMode>
11
+ </React.StrictMode>,
12
12
  );