create-bluecopa-react-app 1.0.41 → 1.0.43

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 +2717 -955
  56. package/templates/latest/package.json +19 -21
  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,218 +1,101 @@
1
- import React, { useEffect } from "react";
1
+ import React from "react";
2
2
  import { createRoot, type Root } from "react-dom/client";
3
- import singleSpaReact from "single-spa-react";
4
- import {
5
- MemoryRouter,
6
- BrowserRouter,
7
- useNavigate,
8
- useLocation,
9
- } from "react-router-dom";
3
+ import { BrowserRouter } from "react-router-dom";
10
4
  import App from "./app";
11
- import type { AppProps } from "single-spa";
5
+ import type { MfeProps } from "~/types";
12
6
 
13
- // Single-spa lifecycle props interface
14
- interface LifecycleProps {
7
+ /** Props passed by the host app when mounting this MFE. */
8
+ interface MountProps extends MfeProps {
9
+ /** The DOM element to render into (required). */
15
10
  domElement: HTMLElement;
16
- basename?: string;
17
- singleSpa?: {
18
- name: string;
19
- mountParcel: Function;
20
- getProps: () => any;
21
- };
11
+ /** Aliases the host may use instead of domElement. */
12
+ container?: HTMLElement;
13
+ element?: HTMLElement;
14
+ root?: HTMLElement;
15
+ /** Alias for basename used by some hosts. */
16
+ baseUrl?: string;
22
17
  }
23
18
 
24
- let root: Root | null = null;
19
+ let reactRoot: Root | null = null;
25
20
 
26
- // Helper function to extract path from browser URL
27
- function getInitialPath(basename?: string): string {
28
- const currentPath = window.location.pathname;
29
- const search = window.location.search;
30
- const hash = window.location.hash;
31
-
32
- // If basename is provided, extract the path after basename
33
- let pathAfterBasename = currentPath;
34
- if (basename && currentPath.startsWith(basename)) {
35
- pathAfterBasename = currentPath.slice(basename.length) || "/";
36
- }
37
-
38
- // Combine path, search params, and hash
39
- return pathAfterBasename + search + hash;
40
- }
41
-
42
- // Component to sync MemoryRouter navigation with browser URL
43
- const RouterSync: React.FC<{ basename?: string }> = ({ basename }) => {
44
- const location = useLocation();
45
- const navigate = useNavigate();
46
-
47
- useEffect(() => {
48
- // Get the current path from MemoryRouter
49
- const mainAppPath = window.location.pathname;
50
- if (mainAppPath.includes("/apps/ext/")) {
51
- const currentPath = location.pathname;
52
- const search = location.search || "";
53
- const hash = location.hash || "";
54
-
55
- // Calculate the full URL path
56
- const basenamePath = basename || "";
57
- const fullPath = basenamePath + currentPath + search + hash;
58
-
59
- // Update browser URL without reloading the page
60
- const currentBrowserPath =
61
- window.location.pathname +
62
- window.location.search +
63
- window.location.hash;
64
- if (currentBrowserPath !== fullPath) {
65
- // Use pushState to maintain browser history for back/forward buttons
66
- window.history.pushState(
67
- { ...window.history.state, path: fullPath },
68
- "",
69
- fullPath
70
- );
71
- }
72
- }
73
- }, [location.pathname, location.search, location.hash, basename]);
74
-
75
- // Listen for browser navigation (back/forward buttons)
76
- useEffect(() => {
77
- const handlePopState = () => {
78
- const mainAppPath = window.location.pathname;
79
- // If current path does not contain /apps/ext/, clean up all /apps/ext/ references
80
- if (mainAppPath.includes("/apps/ext/")) {
81
- const currentPath = window.location.pathname;
82
- const search = window.location.search || "";
83
- const hash = window.location.hash || "";
84
- const basenamePath = basename || "";
85
-
86
- // Extract the path after basename
87
- let pathToNavigate = currentPath;
88
- if (basenamePath && currentPath.startsWith(basenamePath)) {
89
- pathToNavigate = currentPath.slice(basenamePath.length) || "/";
90
- }
91
-
92
- // Combine with search and hash
93
- const fullPathToNavigate = pathToNavigate + search + hash;
94
- const currentRouterPath =
95
- location.pathname + (location.search || "") + (location.hash || "");
96
-
97
- // Navigate MemoryRouter to match browser URL
98
- if (currentRouterPath !== fullPathToNavigate) {
99
- navigate(fullPathToNavigate, { replace: true });
100
- }
101
- } else {
102
- window.removeEventListener("popstate", handlePopState);
103
- }
104
- };
105
-
106
- window.addEventListener("popstate", handlePopState);
107
- return () => window.removeEventListener("popstate", handlePopState);
108
- }, [navigate, location.pathname, location.search, location.hash, basename]);
109
-
110
- return null;
111
- };
112
-
113
- // Root component wrapper that handles routing
114
21
  const MicrofrontendRoot: React.FC<{
115
- isMicroFrontend: boolean;
116
- props?: { basename: string };
117
- }> = ({ isMicroFrontend, props }) => {
118
- // Use MemoryRouter for micro-frontend to avoid conflicts with host routing
119
- // Use BrowserRouter for standalone mode
120
- const Router = isMicroFrontend ? MemoryRouter : BrowserRouter;
121
-
122
- // Get initial path from browser URL for MemoryRouter
123
- const initialPath = isMicroFrontend ? getInitialPath(props?.basename) : "/";
124
-
125
- const routerProps = isMicroFrontend
126
- ? { initialEntries: [initialPath], initialIndex: 0 }
127
- : { basename: props?.basename };
128
-
22
+ props?: MfeProps;
23
+ }> = ({ props }) => {
129
24
  return (
130
- <Router {...routerProps}>
131
- {isMicroFrontend && <RouterSync basename={props?.basename} />}
25
+ <BrowserRouter basename={props?.basename}>
132
26
  <App {...props} />
133
- </Router>
27
+ </BrowserRouter>
134
28
  );
135
29
  };
136
30
 
137
- // Single-spa React configuration
138
- const lifecycles = singleSpaReact({
139
- React,
140
- ReactDOMClient: { createRoot },
141
- rootComponent: (props: AppProps & { basename: string }) => {
142
- return <MicrofrontendRoot props={props} isMicroFrontend={true} />;
143
- },
144
- errorBoundary: (err, info) => {
145
- console.error("Microfrontend Single-spa Error:", err, info);
146
- return <div>Something went wrong loading Microfrontend</div>;
147
- },
148
- renderType: "createRoot",
149
- domElementGetter: () => {
150
- const el = document.getElementById(
151
- "single-spa-application:bluecopa-preview"
31
+ /**
32
+ * Resolves the DOM element from the various prop aliases the host may pass.
33
+ * The host sends domElement, container, element, root — accept any of them.
34
+ */
35
+ function resolveDomElement(props: MountProps): HTMLElement {
36
+ const el = props.domElement || props.container || props.element || props.root;
37
+ if (!el) {
38
+ throw new Error(
39
+ "domElement (or container/element/root) is required for mounting the MFE",
152
40
  );
153
- if (!el)
154
- throw new Error(
155
- "Mount target #single-spa-application:bluecopa-preview not found"
156
- );
157
- return el;
158
- },
159
- });
160
-
161
- // Export the single-spa lifecycle functions
162
- export const { mount, unmount, bootstrap } = lifecycles;
163
-
164
- // Export a manual mount function for direct usage
165
- export const manualMount = async (props: LifecycleProps) => {
166
- if (!props.domElement) {
167
- throw new Error("domElement is required for mounting the application");
168
41
  }
42
+ return el;
43
+ }
44
+
45
+ /**
46
+ * Mount the MFE into a given DOM element.
47
+ * This is the primary entry point called by the host's single-spa-config.
48
+ */
49
+ export async function mount(props: MountProps): Promise<void> {
50
+ const domElement = resolveDomElement(props);
169
51
 
170
52
  try {
171
- // Clean up any existing root
172
- if (root) {
173
- root.unmount();
174
- root = null;
53
+ // Clean up previous root if remounting
54
+ if (reactRoot) {
55
+ reactRoot.unmount();
56
+ reactRoot = null;
175
57
  }
176
58
 
177
- // Create new root
178
- root = createRoot(props.domElement);
179
-
180
- // Mount the application with the provided basename as micro-frontend
181
- root.render(
59
+ reactRoot = createRoot(domElement);
60
+ reactRoot.render(
182
61
  <MicrofrontendRoot
183
- isMicroFrontend={true}
184
62
  props={{
185
- basename: props.basename || "/app/external/microfrontend",
63
+ basename: props.basename || props.baseUrl,
64
+ accessToken: props.accessToken,
65
+ workspaceId: props.workspaceId,
66
+ apiBaseUrl: props.apiBaseUrl,
67
+ userId: props.userId,
186
68
  }}
187
- />
69
+ />,
188
70
  );
189
-
190
- console.log("Microfrontend mounted successfully");
191
- return;
192
71
  } catch (error) {
193
- console.error("Failed to mount Microfrontend:", error);
72
+ console.error("Failed to mount MFE:", error);
194
73
  throw error;
195
74
  }
196
- };
75
+ }
197
76
 
198
- // Export a manual unmount function
199
- export const manualUnmount = async () => {
77
+ /**
78
+ * Unmount the MFE and clean up the React tree.
79
+ */
80
+ export async function unmount(): Promise<void> {
200
81
  try {
201
- if (root) {
202
- root.unmount();
203
- root = null;
82
+ if (reactRoot) {
83
+ reactRoot.unmount();
84
+ reactRoot = null;
204
85
  }
205
- console.log("Microfrontend unmounted successfully");
206
- return Promise.resolve();
207
86
  } catch (error) {
208
- console.error("Failed to unmount Microfrontend:", error);
209
- return Promise.reject(error);
87
+ console.error("Failed to unmount MFE:", error);
88
+ throw error;
210
89
  }
211
- };
90
+ }
212
91
 
213
- // Default export for module federation
214
- export default {
215
- mount: manualMount,
216
- unmount: manualUnmount,
217
- bootstrap: () => Promise.resolve(),
218
- };
92
+ /**
93
+ * Bootstrap — no-op, required by single-spa contract.
94
+ */
95
+ export async function bootstrap(): Promise<void> {}
96
+
97
+ /**
98
+ * Default export provides the same lifecycle for Module Federation consumers
99
+ * that import the default rather than named exports.
100
+ */
101
+ export default { mount, unmount, bootstrap };
@@ -0,0 +1,37 @@
1
+ /** Props passed to the MFE root component from the host app (single-spa customProps or manualMount). */
2
+ export interface MfeProps {
3
+ apiBaseUrl?: string;
4
+ workspaceId?: string;
5
+ accessToken?: string;
6
+ userId?: string;
7
+ basename?: string;
8
+ }
9
+
10
+ /** Normalized user object provided by AppContext. */
11
+ export type AppUser = {
12
+ userId?: string;
13
+ name?: string;
14
+ userExists?: boolean;
15
+ settings?: Record<string, unknown>;
16
+ workspaceIds?: string[];
17
+ currentWorkspace?: unknown;
18
+ teams?: unknown[];
19
+ userTeamProperties?: unknown[];
20
+ email?: string | null;
21
+ role?: string;
22
+ [key: string]: unknown;
23
+ };
24
+
25
+ /** Workspace data settings derived from user/workspace API (currency, timezone, date format, etc.) */
26
+ export type WorkspaceDataSettings = {
27
+ currency: string;
28
+ timezone: string;
29
+ dateFormat: string;
30
+ fiscalYear: { yearType: string; startMonth?: number; startDate?: number };
31
+ numberSettings: {
32
+ numberSystem: string;
33
+ defaultPrecision: number;
34
+ nullPlaceholder?: string;
35
+ };
36
+ weekStartDay: string;
37
+ };
@@ -0,0 +1,63 @@
1
+ import type {
2
+ IServerSideDatasource,
3
+ IServerSideGetRowsParams,
4
+ } from "ag-grid-community";
5
+ import { copaApi } from "@bluecopa/react";
6
+
7
+ interface CreateDatasourceOptions {
8
+ tableId: string;
9
+ totalCount?: number;
10
+ }
11
+
12
+ export function createServerSideDatasource({
13
+ tableId,
14
+ totalCount,
15
+ }: CreateDatasourceOptions): IServerSideDatasource {
16
+ return {
17
+ getRows: async (params: IServerSideGetRowsParams) => {
18
+ const {
19
+ startRow = 0,
20
+ endRow = 50,
21
+ sortModel,
22
+ filterModel,
23
+ } = params.request;
24
+ const limit = endRow - startRow;
25
+ const offset = startRow;
26
+
27
+ const order_by = sortModel?.[0]?.colId;
28
+ const order = sortModel?.[0]?.sort as "asc" | "desc" | undefined;
29
+
30
+ const filters = filterModel
31
+ ? Object.entries(filterModel).reduce(
32
+ (acc, [col, filter]) => {
33
+ acc[col] = (filter as any).filter;
34
+ return acc;
35
+ },
36
+ {} as Record<string, unknown>,
37
+ )
38
+ : undefined;
39
+
40
+ try {
41
+ const response = await copaApi.inputTable.getRows(tableId, {
42
+ limit,
43
+ offset,
44
+ order,
45
+ order_by,
46
+ filters,
47
+ });
48
+
49
+ const rows =
50
+ (response as any)?.data ||
51
+ (response as any)?.rows ||
52
+ [];
53
+ const rowCount =
54
+ totalCount ?? (response as any)?.totalCount ?? undefined;
55
+
56
+ params.success({ rowData: rows, rowCount });
57
+ } catch (error) {
58
+ console.error("[AG Grid Datasource] Failed:", error);
59
+ params.fail();
60
+ }
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,12 @@
1
+ import { LicenseManager } from "ag-grid-enterprise";
2
+
3
+ export function initAgGridLicense() {
4
+ const key = import.meta.env.VITE_AG_GRID_LICENSE_KEY;
5
+ if (!key) {
6
+ console.warn(
7
+ "[AG Grid] No license key found (VITE_AG_GRID_LICENSE_KEY). Enterprise features will show watermarks.",
8
+ );
9
+ return;
10
+ }
11
+ LicenseManager.setLicenseKey(key);
12
+ }
@@ -0,0 +1,9 @@
1
+ import { themeQuartz } from "ag-grid-community";
2
+
3
+ export const gridTheme = themeQuartz.withParams({
4
+ accentColor: "#3548ff",
5
+ spacing: 6,
6
+ borderRadius: 8,
7
+ headerBackgroundColor: "oklch(0.97 0.003 280)",
8
+ fontSize: 13,
9
+ });
@@ -0,0 +1,7 @@
1
+ // Unprefixed style tokens for component composition.
2
+ // These get auto-prefixed by style-drivers.ts before being applied.
3
+ export const styles = {
4
+ card: "rounded-lg border bg-card text-card-foreground shadow-sm",
5
+ cardHeader: "flex flex-col space-y-1.5 p-6",
6
+ cardContent: "p-6 pt-0",
7
+ } as const;
@@ -0,0 +1,24 @@
1
+ import { styles } from "./component-style";
2
+
3
+ const PREFIX = "copa";
4
+
5
+ function prefixClasses(classString: string): string {
6
+ return classString
7
+ .split(" ")
8
+ .filter(Boolean)
9
+ .map((cls) => {
10
+ // Handle variant prefixes (hover:, focus:, data-[...]:, etc.)
11
+ const parts = cls.split(":");
12
+ const utility = parts.pop()!;
13
+ const variants = parts;
14
+ // Reconstruct: variants + copa: + utility
15
+ return [...variants, `${PREFIX}:${utility}`].join(":");
16
+ })
17
+ .join(" ");
18
+ }
19
+
20
+ export const prefixedStyles = Object.fromEntries(
21
+ Object.entries(styles).map(([key, value]) => [key, prefixClasses(value)])
22
+ ) as typeof styles;
23
+
24
+ export { prefixClasses };
@@ -0,0 +1,10 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+
4
+ const twMerge = extendTailwindMerge({
5
+ prefix: "copa",
6
+ });
7
+
8
+ export function cn(...inputs: ClassValue[]) {
9
+ return twMerge(clsx(inputs));
10
+ }
@@ -8,14 +8,14 @@
8
8
  "css": "app/app.css",
9
9
  "baseColor": "gray",
10
10
  "cssVariables": true,
11
- "prefix": ""
11
+ "prefix": "copa"
12
12
  },
13
13
  "iconLibrary": "lucide",
14
14
  "aliases": {
15
15
  "components": "~/components",
16
- "utils": "~/lib/utils",
16
+ "utils": "~/utils/utils",
17
17
  "ui": "~/components/ui",
18
- "lib": "~/lib",
18
+ "lib": "~/utils",
19
19
  "hooks": "~/hooks"
20
20
  },
21
21
  "registries": {
@@ -2,13 +2,41 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Bluecopa - AI-powered Finance Automation</title>
8
8
  <meta name="description" content="AI-powered close automation for modern finance teams. Streamline your order-to-cash, procure-to-pay, and record-to-report processes." />
9
+ <style>
10
+ /*
11
+ * Standalone preflight — replaces Tailwind's preflight for dev mode.
12
+ * This file is NOT used in MFE mode (host provides its own resets).
13
+ */
14
+ *, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; margin: 0; padding: 0; }
15
+ html { height: 100%; line-height: 1.5; -webkit-text-size-adjust: 100%; tab-size: 4; }
16
+ body { height: 100%; line-height: inherit; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
17
+ #root { height: 100%; }
18
+ h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
19
+ a { color: inherit; text-decoration: inherit; }
20
+ ol, ul, menu { list-style: none; }
21
+ img, svg, video, canvas, audio, iframe, embed, object { display: block; max-width: 100%; }
22
+ button, input, optgroup, select, textarea { font: inherit; color: inherit; }
23
+ button { cursor: pointer; background: transparent; }
24
+ table { border-collapse: collapse; border-spacing: 0; text-indent: 0; border-color: inherit; }
25
+ hr { height: 0; color: inherit; border-top-width: 1px; }
26
+ p, blockquote, dl, dd, figure, fieldset, legend, pre { margin: 0; }
27
+ </style>
9
28
  </head>
10
29
  <body>
11
- <div id="root"></div>
30
+ <div id="root">
31
+ <!-- Inline loader: visible instantly before JS/CSS loads, replaced when React mounts -->
32
+ <div id="initial-loader" style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%;background:#f8f8fa;font-family:'Satoshi',system-ui,sans-serif;">
33
+ <div style="display:flex;flex-direction:column;align-items:center;gap:16px;">
34
+ <div style="width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:#3548ff;border-radius:50%;animation:init-spin 0.7s linear infinite;"></div>
35
+ <span style="color:#6b7280;font-size:0.835rem;">Loading...</span>
36
+ </div>
37
+ </div>
38
+ <style>@keyframes init-spin{to{transform:rotate(360deg)}}</style>
39
+ </div>
12
40
  <script type="module" src="/app/main.tsx"></script>
13
41
  </body>
14
42
  </html>