firebase-os 1.1.4 → 1.1.5

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 (124) hide show
  1. package/dist/FirebaseOS.d.ts +15 -0
  2. package/dist/firebase-os.cjs.js +2 -17
  3. package/dist/firebase-os.es.js +63 -74
  4. package/dist/index.d.ts +3 -0
  5. package/dist/lib/ConfigContext.d.ts +12 -0
  6. package/package.json +3 -2
  7. package/scripts/postinstall.js +86 -15
  8. package/src/App.css +184 -0
  9. package/src/App.tsx +214 -0
  10. package/src/FirebaseOS.tsx +80 -0
  11. package/src/assets/hero.png +0 -0
  12. package/src/assets/react.svg +1 -0
  13. package/src/assets/vite.svg +1 -0
  14. package/src/components/AdminNotifications.test.tsx +98 -0
  15. package/src/components/AdminNotifications.tsx +194 -0
  16. package/src/components/Button.test.tsx +22 -0
  17. package/src/components/Button.tsx +53 -0
  18. package/src/components/ConfirmModal.test.tsx +98 -0
  19. package/src/components/ConfirmModal.tsx +73 -0
  20. package/src/components/ContactPopup.test.tsx +98 -0
  21. package/src/components/ContactPopup.tsx +437 -0
  22. package/src/components/CustomSelect.test.tsx +47 -0
  23. package/src/components/CustomSelect.tsx +89 -0
  24. package/src/components/DashboardNav.test.tsx +98 -0
  25. package/src/components/DashboardNav.tsx +281 -0
  26. package/src/components/Input.test.tsx +33 -0
  27. package/src/components/Input.tsx +61 -0
  28. package/src/components/JsonEditor.tsx +579 -0
  29. package/src/components/Navbar.test.tsx +98 -0
  30. package/src/components/Navbar.tsx +563 -0
  31. package/src/configs/forms/contactForm.config.ts +15 -0
  32. package/src/configs/forms/index.ts +29 -0
  33. package/src/configs/forms/pubForm.config.ts +11 -0
  34. package/src/configs/forms/supportForm.config.ts +14 -0
  35. package/src/configs/forms/userForm.config.ts +11 -0
  36. package/src/configs/pages/admin.config.ts +29 -0
  37. package/src/configs/pages/contact.config.ts +6 -0
  38. package/src/configs/pages/home.config.ts +18 -0
  39. package/src/configs/pages/mem.config.ts +2 -0
  40. package/src/configs/pages/menuOrders.config.ts +11 -0
  41. package/src/configs/pages/pub.config.ts +11 -0
  42. package/src/configs/pages/shared.config.ts +29 -0
  43. package/src/configs/pages/support.config.ts +7 -0
  44. package/src/configs/pages/tabOrders.config.ts +33 -0
  45. package/src/configs/pages/user.config.ts +29 -0
  46. package/src/configs/theme.config.ts +93 -0
  47. package/src/index.css +403 -0
  48. package/src/index.ts +22 -0
  49. package/src/lib/AuthContext.test.tsx +88 -0
  50. package/src/lib/AuthContext.tsx +191 -0
  51. package/src/lib/ConfigContext.tsx +45 -0
  52. package/src/lib/ThemeContext.tsx +227 -0
  53. package/src/lib/firebase.ts +91 -0
  54. package/src/main.tsx +22 -0
  55. package/src/microcomponents/AdminExampleContent.tsx +44 -0
  56. package/src/microcomponents/PrivateExampleContent.tsx +39 -0
  57. package/src/microcomponents/Public.tsx +126 -0
  58. package/src/microcomponents/SharedExampleContent.tsx +53 -0
  59. package/src/pages/Dashboard.test.tsx +98 -0
  60. package/src/pages/Dashboard.tsx +60 -0
  61. package/src/pages/DynamicPage.tsx +237 -0
  62. package/src/pages/FormsAdmin.test.tsx +98 -0
  63. package/src/pages/FormsAdmin.tsx +459 -0
  64. package/src/pages/Home.test.tsx +98 -0
  65. package/src/pages/Home.tsx +144 -0
  66. package/src/pages/Login.test.tsx +98 -0
  67. package/src/pages/Login.tsx +108 -0
  68. package/src/pages/PagesAdmin.test.tsx +98 -0
  69. package/src/pages/PagesAdmin.tsx +1022 -0
  70. package/src/pages/Profile.test.tsx +98 -0
  71. package/src/pages/Profile.tsx +319 -0
  72. package/src/pages/Register.test.tsx +98 -0
  73. package/src/pages/Register.tsx +116 -0
  74. package/src/pages/Requests.test.tsx +95 -0
  75. package/src/pages/Requests.tsx +422 -0
  76. package/src/pages/ResetPassword.test.tsx +98 -0
  77. package/src/pages/ResetPassword.tsx +92 -0
  78. package/src/pages/Settings.test.tsx +98 -0
  79. package/src/pages/Settings.tsx +393 -0
  80. package/src/pages/Setup.tsx +401 -0
  81. package/src/pages/StorageAdmin.test.tsx +150 -0
  82. package/src/pages/StorageAdmin.tsx +769 -0
  83. package/src/pages/Submissions.test.tsx +95 -0
  84. package/src/pages/Submissions.tsx +372 -0
  85. package/src/pages/Templates.test.tsx +98 -0
  86. package/src/pages/Templates.tsx +103 -0
  87. package/src/pages/ThemeAdmin.test.tsx +144 -0
  88. package/src/pages/ThemeAdmin.tsx +1000 -0
  89. package/src/pages/Users.test.tsx +95 -0
  90. package/src/pages/Users.tsx +334 -0
  91. package/src/pages/Verify.test.tsx +98 -0
  92. package/src/pages/Verify.tsx +95 -0
  93. package/src/prompts/index.ts +13 -0
  94. package/src/prompts/pages/publicPage.ts +44 -0
  95. package/src/prompts/sharedConstants.ts +12 -0
  96. package/src/prompts/tabs/board/adminboard.ts +32 -0
  97. package/src/prompts/tabs/board/privateboard.ts +36 -0
  98. package/src/prompts/tabs/board/publicboard.ts +36 -0
  99. package/src/prompts/tabs/calendar/admincalendar.ts +32 -0
  100. package/src/prompts/tabs/calendar/privatecalendar.ts +36 -0
  101. package/src/prompts/tabs/calendar/publiccalendar.ts +36 -0
  102. package/src/prompts/tabs/crud/admin.ts +54 -0
  103. package/src/prompts/tabs/crud/private.ts +55 -0
  104. package/src/prompts/tabs/crud/shared.ts +53 -0
  105. package/src/prompts/tabs/table/admintable.ts +32 -0
  106. package/src/prompts/tabs/table/privatetable.ts +36 -0
  107. package/src/prompts/tabs/table/publictable.ts +36 -0
  108. package/src/setupTests.ts +1 -0
  109. package/src/templates/AdminPageTemplate.tsx +678 -0
  110. package/src/templates/PrivatePageTemplate.tsx +594 -0
  111. package/src/templates/PublicPageTemplate.tsx +92 -0
  112. package/src/templates/SharedPageTemplate.tsx +551 -0
  113. package/src/templates/TemplateBoard.test.tsx +106 -0
  114. package/src/templates/TemplateBoard.tsx +642 -0
  115. package/src/templates/TemplateCalendar.test.tsx +106 -0
  116. package/src/templates/TemplateCalendar.tsx +848 -0
  117. package/src/templates/TemplateConfirmation.test.tsx +106 -0
  118. package/src/templates/TemplateConfirmation.tsx +145 -0
  119. package/src/templates/TemplateInlineForm.test.tsx +106 -0
  120. package/src/templates/TemplateInlineForm.tsx +129 -0
  121. package/src/templates/TemplatePopupForm.test.tsx +106 -0
  122. package/src/templates/TemplatePopupForm.tsx +174 -0
  123. package/src/templates/TemplateTable.test.tsx +106 -0
  124. package/src/templates/TemplateTable.tsx +675 -0
@@ -89363,62 +89363,62 @@ function vUn({ config: e }) {
89363
89363
  //#endregion
89364
89364
  //#region src/pages/DynamicPage.tsx
89365
89365
  function yUn() {
89366
- let e = Se(), { user: t } = $e(), [n, r] = x(null), [i, a] = x(!0);
89366
+ let e = Se(), { user: t } = $e(), n = Ze(), [r, i] = x(null), [a, o] = x(!0);
89367
89367
  m(() => {
89368
89368
  (async () => {
89369
- a(!0);
89369
+ o(!0);
89370
89370
  try {
89371
- let [t, n] = await Promise.all([ee(C(W, "sys_pages")), ee(C(W, "sys_tabs"))]), i = null, a = null, o = (t, n) => {
89371
+ let [t, n] = await Promise.all([ee(C(W, "sys_pages")), ee(C(W, "sys_tabs"))]), r = null, a = null, o = (t, n) => {
89372
89372
  t.forEach((t) => {
89373
- let r = t.data(), o = r.route;
89373
+ let i = t.data(), o = i.route;
89374
89374
  o && !o.startsWith("/") && (o = "/" + o);
89375
89375
  let s = "/" + t.id === e.pathname;
89376
- (o === e.pathname || s) && (i = {
89377
- ...r,
89376
+ (o === e.pathname || s) && (r = {
89377
+ ...i,
89378
89378
  isPrivate: n,
89379
- isAdmin: r.pageType === "admin",
89380
- isShared: r.pageType === "shared"
89379
+ isAdmin: i.pageType === "admin",
89380
+ isShared: i.pageType === "shared"
89381
89381
  }, a = t.id);
89382
89382
  });
89383
89383
  };
89384
- if (o(t, !1), i || o(n, !0), i || (e.pathname === "/contact" ? (i = {
89384
+ if (o(t, !1), r || o(n, !0), r || (e.pathname === "/contact" ? (r = {
89385
89385
  route: "/contact",
89386
89386
  template: "popup_form",
89387
89387
  form: "contact"
89388
- }, a = "contact") : e.pathname === "/support" && (i = {
89388
+ }, a = "contact") : e.pathname === "/support" && (r = {
89389
89389
  route: "/support",
89390
89390
  template: "popup_form",
89391
89391
  form: "support"
89392
- }, a = "support")), i) {
89393
- if ((a === "contact" || a === "support") && !i.form && (i.form = a), i.pageId = a, i.form) {
89394
- let e = await E(T(W, "sys_forms", i.form));
89395
- if (e.exists()) i.formConfigOverride = {
89392
+ }, a = "support")), r) {
89393
+ if ((a === "contact" || a === "support") && !r.form && (r.form = a), r.pageId = a, r.form) {
89394
+ let e = await E(T(W, "sys_forms", r.form));
89395
+ if (e.exists()) r.formConfigOverride = {
89396
89396
  ...e.data(),
89397
89397
  id: e.id
89398
89398
  };
89399
- else if (i.form === "contact") {
89399
+ else if (r.form === "contact") {
89400
89400
  let { contactFormConfig: e } = await import("./contactForm.config-DLeI_ZEj.js");
89401
- i.formConfigOverride = e;
89402
- } else if (i.form === "support") {
89401
+ r.formConfigOverride = e;
89402
+ } else if (r.form === "support") {
89403
89403
  let { supportFormConfig: e } = await import("./supportForm.config-74T-eSSH.js");
89404
- i.formConfigOverride = e;
89404
+ r.formConfigOverride = e;
89405
89405
  }
89406
89406
  }
89407
- let e = i.pageName || i.tabName || i.title;
89408
- e && (document.title = e), r(i);
89409
- } else r("not-found");
89407
+ let e = r.pageName || r.tabName || r.title;
89408
+ e && (document.title = e), i(r);
89409
+ } else i("not-found");
89410
89410
  } catch {
89411
- r("not-found");
89411
+ i("not-found");
89412
89412
  } finally {
89413
- a(!1);
89413
+ o(!1);
89414
89414
  }
89415
89415
  })();
89416
89416
  }, [e.pathname]);
89417
- let [o, s] = x(!1);
89417
+ let [s, c] = x(!1);
89418
89418
  if (m(() => {
89419
89419
  let e;
89420
- return i ? e = setTimeout(() => s(!0), 150) : s(!1), () => clearTimeout(e);
89421
- }, [i]), i) return o ? t ? /* @__PURE__ */ V("main", {
89420
+ return a ? e = setTimeout(() => c(!0), 150) : c(!1), () => clearTimeout(e);
89421
+ }, [a]), a) return s ? t ? /* @__PURE__ */ V("main", {
89422
89422
  className: "flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col",
89423
89423
  children: [/* @__PURE__ */ V("div", {
89424
89424
  className: "mb-6",
@@ -89434,7 +89434,7 @@ function yUn() {
89434
89434
  className: "flex-1 flex items-center justify-center min-h-[60vh] z-10",
89435
89435
  children: /* @__PURE__ */ B(K, { className: "w-6 h-6 animate-spin text-accent" })
89436
89436
  }) : null;
89437
- if (n === "not-found") return /* @__PURE__ */ B("main", {
89437
+ if (r === "not-found") return /* @__PURE__ */ B("main", {
89438
89438
  className: "flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col pt-12 min-h-screen",
89439
89439
  children: /* @__PURE__ */ V("div", {
89440
89440
  className: "flex flex-col items-center justify-center flex-1 text-center",
@@ -89481,53 +89481,53 @@ function yUn() {
89481
89481
  ]
89482
89482
  })
89483
89483
  });
89484
- if ((n.pageId === "support" || n.isPrivate) && !t) return /* @__PURE__ */ B(be, {
89484
+ if ((r.pageId === "support" || r.isPrivate) && !t) return /* @__PURE__ */ B(be, {
89485
89485
  to: "/login",
89486
89486
  state: { from: e }
89487
89487
  });
89488
- let c = n.pageName?.toLowerCase().replace(/\s+/g, "_") || n.pageId || "custom_page", l = `pub_${c}`;
89489
- if (n.template === "none") return n.isAdmin ? /* @__PURE__ */ B(_Un, { config: n }) : n.isShared ? /* @__PURE__ */ B(vUn, { config: n }) : n.isPrivate ? /* @__PURE__ */ B(gUn, { config: n }) : /* @__PURE__ */ B(hUn, { config: n });
89490
- if (n.template === "board") {
89491
- let e = n.isAdmin ? `admin_${n.pageId || c}` : n.isShared ? `mem_${n.pageId || c}` : n.isPrivate ? `user_${n.pageId || c}` : l;
89492
- return /* @__PURE__ */ B(JVn, {
89493
- title: n.tabTitle || n.tabName || n.title || n.pageName || "Board",
89494
- subtitle: n.route,
89488
+ let l = r.pageName?.toLowerCase().replace(/\s+/g, "_") || r.pageId || "custom_page", u = `pub_${l}`;
89489
+ if (r.template === "none") return r.isAdmin ? /* @__PURE__ */ B(_Un, { config: r }) : r.isShared ? /* @__PURE__ */ B(vUn, { config: r }) : r.isPrivate ? /* @__PURE__ */ B(gUn, { config: r }) : /* @__PURE__ */ B(hUn, { config: r });
89490
+ if (r.template === "board") {
89491
+ let e = r.isAdmin ? `admin_${r.pageId || l}` : r.isShared ? `mem_${r.pageId || l}` : r.isPrivate ? `user_${r.pageId || l}` : u;
89492
+ return /* @__PURE__ */ B(n.components?.TemplateBoard || JVn, {
89493
+ title: r.tabTitle || r.tabName || r.title || r.pageName || "Board",
89494
+ subtitle: r.route,
89495
89495
  tasksCollection: `${e}_tasks`,
89496
89496
  categoriesCollection: `${e}_categories`,
89497
- config: n
89497
+ config: r
89498
89498
  });
89499
89499
  }
89500
- if (n.template === "table") {
89501
- let e = n.isAdmin ? `admin_${n.pageId || c}` : n.isShared ? `mem_${n.pageId || c}` : n.isPrivate ? `user_${n.pageId || c}` : l;
89502
- return /* @__PURE__ */ B($Hn, {
89503
- title: n.tabTitle || n.tabName || n.title || n.pageName || "Table",
89504
- subtitle: n.route,
89500
+ if (r.template === "table") {
89501
+ let e = r.isAdmin ? `admin_${r.pageId || l}` : r.isShared ? `mem_${r.pageId || l}` : r.isPrivate ? `user_${r.pageId || l}` : u;
89502
+ return /* @__PURE__ */ B(n.components?.TemplateTable || $Hn, {
89503
+ title: r.tabTitle || r.tabName || r.title || r.pageName || "Table",
89504
+ subtitle: r.route,
89505
89505
  tableCollection: `${e}_table`,
89506
- config: n
89506
+ config: r
89507
89507
  });
89508
89508
  }
89509
- if (n.template === "calendar") {
89510
- let e = n.isAdmin ? `admin_${n.pageId || c}` : n.isShared ? `mem_${n.pageId || c}` : n.isPrivate ? `user_${n.pageId || c}` : l;
89511
- return /* @__PURE__ */ B(tUn, {
89512
- title: n.tabTitle || n.tabName || n.title || n.pageName || "Calendar",
89513
- subtitle: n.route,
89509
+ if (r.template === "calendar") {
89510
+ let e = r.isAdmin ? `admin_${r.pageId || l}` : r.isShared ? `mem_${r.pageId || l}` : r.isPrivate ? `user_${r.pageId || l}` : u;
89511
+ return /* @__PURE__ */ B(n.components?.TemplateCalendar || tUn, {
89512
+ title: r.tabTitle || r.tabName || r.title || r.pageName || "Calendar",
89513
+ subtitle: r.route,
89514
89514
  eventsCollection: `${e}_events`,
89515
- defaultTimeFormat: n.defaultTimeFormat || n.defaulttimeformat || "12h",
89516
- config: n
89515
+ defaultTimeFormat: r.defaultTimeFormat || r.defaulttimeformat || "12h",
89516
+ config: r
89517
89517
  });
89518
89518
  }
89519
- return n.template === "inline_form" ? /* @__PURE__ */ B("div", {
89519
+ return r.template === "inline_form" ? /* @__PURE__ */ B("div", {
89520
89520
  className: "flex-1 flex flex-col items-center justify-start mt-8 relative z-10 w-full min-h-[60vh]",
89521
89521
  children: /* @__PURE__ */ B(Es, {
89522
89522
  isInline: !0,
89523
- formId: n.form,
89524
- formConfig: n.formConfigOverride
89523
+ formId: r.form,
89524
+ formConfig: r.formConfigOverride
89525
89525
  })
89526
89526
  }) : /* @__PURE__ */ B("div", {
89527
89527
  className: "flex-1 flex flex-col items-center justify-center relative z-10 w-full min-h-[60vh]",
89528
89528
  children: /* @__PURE__ */ B(Es, {
89529
- formId: n.form,
89530
- formConfig: n.formConfigOverride,
89529
+ formId: r.form,
89530
+ formConfig: r.formConfigOverride,
89531
89531
  onClose: () => window.history.back()
89532
89532
  })
89533
89533
  });
@@ -90265,6 +90265,7 @@ function PUn() {
90265
90265
  });
90266
90266
  }
90267
90267
  function FUn() {
90268
+ let e = Ze().components?.Home || fce;
90268
90269
  return /* @__PURE__ */ B(eee, { children: /* @__PURE__ */ V("div", {
90269
90270
  className: "min-h-screen relative flex flex-col overflow-x-hidden",
90270
90271
  children: [
@@ -90272,7 +90273,7 @@ function FUn() {
90272
90273
  /* @__PURE__ */ V(xe, { children: [
90273
90274
  /* @__PURE__ */ B(U, {
90274
90275
  path: "/",
90275
- element: /* @__PURE__ */ B(Q9, { children: /* @__PURE__ */ B(fce, {}) })
90276
+ element: /* @__PURE__ */ B(Q9, { children: /* @__PURE__ */ B(e, {}) })
90276
90277
  }),
90277
90278
  /* @__PURE__ */ B(U, {
90278
90279
  path: "/register",
@@ -90397,30 +90398,18 @@ function RUn(e) {
90397
90398
  firebaseConfig: e.firebaseConfig,
90398
90399
  adminEmails: e.adminEmails,
90399
90400
  basename: e.basename,
90400
- onAuthChange: e.onAuthChange
90401
+ onAuthChange: e.onAuthChange,
90402
+ themeConfig: e.themeConfig,
90403
+ components: e.components
90401
90404
  },
90402
90405
  children: /* @__PURE__ */ B(qe, {
90403
90406
  scopeSelector: ".firebase-os",
90404
- children: /* @__PURE__ */ V("div", {
90405
- className: "firebase-os",
90406
- style: {
90407
- position: "fixed",
90408
- inset: 0,
90409
- width: "100vw",
90410
- height: "100vh",
90411
- zIndex: 9999,
90412
- overflowY: "auto",
90413
- overflowX: "hidden",
90414
- textAlign: "left",
90415
- margin: 0,
90416
- padding: 0,
90417
- background: "var(--bg-gradient)",
90418
- color: "var(--fg-color)"
90419
- },
90420
- children: [/* @__PURE__ */ B("style", { children: "\n /* Defensive Typography Reset: Specificity 0,0,0 */\n /* This ensures we provide base styles without overriding ANY utility classes */\n :where(.firebase-os) :where(h1, h2, h3, h4, h5, h6, p, span, li, label, a, td, th) {\n color: var(--fg-color);\n font-family: var(--font-family, 'Inter', system-ui, sans-serif);\n }\n :where(.firebase-os) :where(h1, h2, h3, h4, h5, h6, p) {\n margin: 0;\n }\n :where(.firebase-os) :where(code, pre) {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n background: transparent;\n border: none;\n }\n " }), /* @__PURE__ */ B(IUn, {})]
90407
+ children: /* @__PURE__ */ B("div", {
90408
+ className: "firebase-os flex-1 flex flex-col min-h-screen",
90409
+ children: /* @__PURE__ */ B(IUn, {})
90421
90410
  })
90422
90411
  })
90423
90412
  });
90424
90413
  }
90425
90414
  //#endregion
90426
- export { RUn as FirebaseOS, $e as useAuth, Ze as useConfig, Je as useTheme };
90415
+ export { Es as ContactPopup, RUn as FirebaseOS, Ie as auth, W as db, Ns as defaultHomeConfig, Le as storage, $e as useAuth, Ze as useConfig, Je as useTheme };
package/dist/index.d.ts CHANGED
@@ -4,3 +4,6 @@ export { useAuth } from './lib/AuthContext';
4
4
  export { useTheme } from './lib/ThemeContext';
5
5
  export { useConfig } from './lib/ConfigContext';
6
6
  export type { FirebaseOSConfig } from './lib/ConfigContext';
7
+ export { db, auth, storage } from './lib/firebase';
8
+ export { ContactPopup } from './components/ContactPopup';
9
+ export { defaultHomeConfig } from './configs/pages/home.config';
@@ -11,6 +11,18 @@ export interface FirebaseOSConfig {
11
11
  adminEmails?: string[];
12
12
  basename?: string;
13
13
  onAuthChange?: (user: any) => void;
14
+ themeConfig?: any;
15
+ components?: {
16
+ Home?: React.ComponentType<any>;
17
+ TemplateBoard?: React.ComponentType<any>;
18
+ TemplateTable?: React.ComponentType<any>;
19
+ TemplateCalendar?: React.ComponentType<any>;
20
+ TemplatePopupForm?: React.ComponentType<any>;
21
+ TemplateInlineForm?: React.ComponentType<any>;
22
+ TemplatePages?: React.ComponentType<any>;
23
+ TemplateStorage?: React.ComponentType<any>;
24
+ TemplateNone?: React.ComponentType<any>;
25
+ };
14
26
  }
15
27
  export declare function ConfigProvider({ config, children }: {
16
28
  config: FirebaseOSConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-os",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "A complete Firebase-powered admin OS — one React component.",
5
5
  "type": "module",
6
6
  "main": "dist/firebase-os.cjs.js",
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "scripts"
24
+ "scripts",
25
+ "src"
25
26
  ],
26
27
  "scripts": {
27
28
  "dev": "vite",
@@ -81,22 +81,54 @@ createRoot(document.getElementById('root')!).render(
81
81
  }
82
82
  }
83
83
 
84
- // ── 3. Reveal the CSS by copying it to the consumer's src directory ──────────
85
- const sourceCssPath = path.join(__dirname, '..', 'dist', 'firebase-os.css');
86
- const targetCssPath = path.join(srcDir, 'firebase-os.css');
84
+ // ── 3. Hybrid Architecture: Create local overrides directory ─────────────
85
+ const fbosDir = path.join(srcDir, 'firebase-os');
86
+ if (!fs.existsSync(fbosDir)) fs.mkdirSync(fbosDir, { recursive: true });
87
+
88
+ // Copy raw index.css to give user full control over Tailwind and variables
89
+ const sourceCssPath = path.join(__dirname, '..', 'src', 'index.css');
87
90
  if (fs.existsSync(sourceCssPath)) {
88
- fs.copyFileSync(sourceCssPath, targetCssPath);
89
- console.log(' ✓ Copied firebase-os.css to your src directory');
91
+ fs.copyFileSync(sourceCssPath, path.join(fbosDir, 'theme.css'));
92
+ console.log(' ✓ Copied theme.css to your src/firebase-os directory');
93
+ }
94
+
95
+ // Copy configurations
96
+ const configsDir = path.join(fbosDir, 'configs');
97
+ if (!fs.existsSync(configsDir)) fs.mkdirSync(configsDir, { recursive: true });
98
+
99
+ const copyConfig = (srcPath, destName) => {
100
+ const fullSrc = path.join(__dirname, '..', 'src', 'configs', srcPath);
101
+ if (fs.existsSync(fullSrc)) {
102
+ fs.copyFileSync(fullSrc, path.join(configsDir, destName));
103
+ }
104
+ };
105
+ copyConfig('theme.config.ts', 'theme.config.ts');
106
+ copyConfig('pages/home.config.ts', 'home.config.ts');
107
+ copyConfig('forms/contactForm.config.ts', 'contactForm.config.ts');
108
+ copyConfig('forms/supportForm.config.ts', 'supportForm.config.ts');
109
+ console.log(' ✓ Copied configuration files to your src/firebase-os/configs directory');
110
+
111
+ // Read and rewrite Home.tsx
112
+ const sourceHomePath = path.join(__dirname, '..', 'src', 'pages', 'Home.tsx');
113
+ if (fs.existsSync(sourceHomePath)) {
114
+ let homeContent = fs.readFileSync(sourceHomePath, 'utf8');
115
+ homeContent = homeContent.replace(/from '\.\.\/components\/ContactPopup'/g, "from 'firebase-os'");
116
+ homeContent = homeContent.replace(/from '\.\.\/lib\/firebase'/g, "from 'firebase-os'");
117
+ homeContent = homeContent.replace(/from '\.\.\/configs\/pages\/home\.config'/g, "from './configs/home.config'");
118
+ fs.writeFileSync(path.join(fbosDir, 'Home.tsx'), homeContent);
119
+ console.log(' ✓ Created local Home.tsx template override');
90
120
  }
91
121
 
92
- // ── 4. Write App.tsx with FirebaseOS already wired up ───────────────────────
122
+ // ── 4. Write App.tsx with Hybrid Overrides Wired Up ───────────────────────
93
123
  const appPath = path.join(srcDir, 'App.tsx');
94
124
  const appContent = `import { FirebaseOS } from 'firebase-os';
95
- import './firebase-os.css';
125
+ import './firebase-os/theme.css';
126
+
127
+ // Local Hybrid Overrides
128
+ import { Home } from './firebase-os/Home';
129
+ import { themeConfig } from './firebase-os/configs/theme.config';
96
130
 
97
131
  // Vite injects these from your .env file at dev/build time.
98
- // The library can't read them directly since it's pre-built,
99
- // so we pass them as props.
100
132
  const firebaseConfig = {
101
133
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY || '',
102
134
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN || '',
@@ -116,21 +148,57 @@ export default function App() {
116
148
  <FirebaseOS
117
149
  firebaseConfig={firebaseConfig}
118
150
  adminEmails={adminEmails}
151
+ themeConfig={themeConfig}
152
+ components={{
153
+ Home: Home
154
+ }}
119
155
  />
120
156
  );
121
157
  }
122
158
  `;
123
159
 
124
- // Only write if the file doesn't already import FirebaseOS
125
160
  const existingApp = fs.existsSync(appPath) ? fs.readFileSync(appPath, 'utf8') : '';
126
161
  if (!existingApp.includes('firebase-os')) {
127
162
  fs.writeFileSync(appPath, appContent);
128
- console.log(' ✓ Wrote App.tsx with <FirebaseOS /> ready to go');
163
+ console.log(' ✓ Wrote App.tsx with <FirebaseOS /> hybrid architecture ready to go');
129
164
  } else {
130
165
  console.log(' ℹ App.tsx already uses firebase-os — skipped');
131
166
  }
132
167
 
133
- // ── 4. Create .env file with placeholders ──────────────────────────────────
168
+ // ── 5. Setup Tailwind V4 in host app ───────────────────────────────────────
169
+ // We'll update the user's package.json to include tailwindcss dependencies
170
+ const pkgPath = path.join(consumerRoot, 'package.json');
171
+ if (fs.existsSync(pkgPath)) {
172
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
173
+ let pkgModified = false;
174
+ pkg.devDependencies = pkg.devDependencies || {};
175
+
176
+ if (!pkg.devDependencies['tailwindcss']) {
177
+ pkg.devDependencies['tailwindcss'] = '^4.0.0';
178
+ pkgModified = true;
179
+ }
180
+ if (!pkg.devDependencies['@tailwindcss/vite']) {
181
+ pkg.devDependencies['@tailwindcss/vite'] = '^4.0.0';
182
+ pkgModified = true;
183
+ }
184
+ if (pkgModified) {
185
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
186
+ console.log(' ✓ Added tailwindcss and @tailwindcss/vite to your package.json');
187
+ }
188
+ }
189
+
190
+ const viteConfigPath = path.join(consumerRoot, 'vite.config.ts');
191
+ if (fs.existsSync(viteConfigPath)) {
192
+ let viteConfig = fs.readFileSync(viteConfigPath, 'utf8');
193
+ if (!viteConfig.includes('@tailwindcss/vite')) {
194
+ viteConfig = "import tailwindcss from '@tailwindcss/vite';\\n" + viteConfig;
195
+ viteConfig = viteConfig.replace('plugins: [', 'plugins: [\\n tailwindcss(),');
196
+ fs.writeFileSync(viteConfigPath, viteConfig);
197
+ console.log(' ✓ Patched vite.config.ts to enable Tailwind CSS v4');
198
+ }
199
+ }
200
+
201
+ // ── 6. Create .env file with placeholders ──────────────────────────────────
134
202
  const envPath = path.join(consumerRoot, '.env');
135
203
  if (!fs.existsSync(envPath)) {
136
204
  const envContent = `# 🔥 Firebase OS Configuration
@@ -145,10 +213,13 @@ VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
145
213
  VITE_FIREBASE_APP_ID=your_app_id
146
214
 
147
215
  # Comma-separated list of admin emails
148
- VITE_ADMIN_EMAILS=your_email@example.com
216
+ VITE_ADMIN_EMAILS=admin@example.com
149
217
  `;
150
218
  fs.writeFileSync(envPath, envContent);
151
- console.log(' ✓ Created .env file with configuration placeholders');
219
+ console.log(' ✓ Created .env template');
152
220
  }
153
221
 
154
- console.log('\n[firebase-os] Setup complete! Run `npm run dev` to start.\n');
222
+ console.log('\n[firebase-os] 🎉 Hybrid Setup complete! You now have full control over styling and configs.\n');
223
+ console.log('🚨 IMPORTANT NEXT STEPS:');
224
+ console.log('1. Run `npm install` again to install the required Tailwind CSS dependencies.');
225
+ console.log('2. Run `npm run dev` to start your app.\n');
package/src/App.css ADDED
@@ -0,0 +1,184 @@
1
+ .counter {
2
+ font-size: 16px;
3
+ padding: 5px 10px;
4
+ border-radius: 5px;
5
+ color: var(--accent);
6
+ background: var(--accent-bg);
7
+ border: 2px solid transparent;
8
+ transition: border-color 0.3s;
9
+ margin-bottom: 24px;
10
+
11
+ &:hover {
12
+ border-color: var(--accent-border);
13
+ }
14
+ &:focus-visible {
15
+ outline: 2px solid var(--accent);
16
+ outline-offset: 2px;
17
+ }
18
+ }
19
+
20
+ .hero {
21
+ position: relative;
22
+
23
+ .base,
24
+ .framework,
25
+ .vite {
26
+ inset-inline: 0;
27
+ margin: 0 auto;
28
+ }
29
+
30
+ .base {
31
+ width: 170px;
32
+ position: relative;
33
+ z-index: 0;
34
+ }
35
+
36
+ .framework,
37
+ .vite {
38
+ position: absolute;
39
+ }
40
+
41
+ .framework {
42
+ z-index: 1;
43
+ top: 34px;
44
+ height: 28px;
45
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
46
+ scale(1.4);
47
+ }
48
+
49
+ .vite {
50
+ z-index: 0;
51
+ top: 107px;
52
+ height: 26px;
53
+ width: auto;
54
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
55
+ scale(0.8);
56
+ }
57
+ }
58
+
59
+ #center {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 25px;
63
+ place-content: center;
64
+ place-items: center;
65
+ flex-grow: 1;
66
+
67
+ @media (max-width: 1024px) {
68
+ padding: 32px 20px 24px;
69
+ gap: 18px;
70
+ }
71
+ }
72
+
73
+ #next-steps {
74
+ display: flex;
75
+ border-top: 1px solid var(--border);
76
+ text-align: left;
77
+
78
+ & > div {
79
+ flex: 1 1 0;
80
+ padding: 32px;
81
+ @media (max-width: 1024px) {
82
+ padding: 24px 20px;
83
+ }
84
+ }
85
+
86
+ .icon {
87
+ margin-bottom: 16px;
88
+ width: 22px;
89
+ height: 22px;
90
+ }
91
+
92
+ @media (max-width: 1024px) {
93
+ flex-direction: column;
94
+ text-align: center;
95
+ }
96
+ }
97
+
98
+ #docs {
99
+ border-right: 1px solid var(--border);
100
+
101
+ @media (max-width: 1024px) {
102
+ border-right: none;
103
+ border-bottom: 1px solid var(--border);
104
+ }
105
+ }
106
+
107
+ #next-steps ul {
108
+ list-style: none;
109
+ padding: 0;
110
+ display: flex;
111
+ gap: 8px;
112
+ margin: 32px 0 0;
113
+
114
+ .logo {
115
+ height: 18px;
116
+ }
117
+
118
+ a {
119
+ color: var(--text-h);
120
+ font-size: 16px;
121
+ border-radius: 6px;
122
+ background: var(--social-bg);
123
+ display: flex;
124
+ padding: 6px 12px;
125
+ align-items: center;
126
+ gap: 8px;
127
+ text-decoration: none;
128
+ transition: box-shadow 0.3s;
129
+
130
+ &:hover {
131
+ box-shadow: var(--shadow);
132
+ }
133
+ .button-icon {
134
+ height: 18px;
135
+ width: 18px;
136
+ }
137
+ }
138
+
139
+ @media (max-width: 1024px) {
140
+ margin-top: 20px;
141
+ flex-wrap: wrap;
142
+ justify-content: center;
143
+
144
+ li {
145
+ flex: 1 1 calc(50% - 8px);
146
+ }
147
+
148
+ a {
149
+ width: 100%;
150
+ justify-content: center;
151
+ box-sizing: border-box;
152
+ }
153
+ }
154
+ }
155
+
156
+ #spacer {
157
+ height: 88px;
158
+ border-top: 1px solid var(--border);
159
+ @media (max-width: 1024px) {
160
+ height: 48px;
161
+ }
162
+ }
163
+
164
+ .ticks {
165
+ position: relative;
166
+ width: 100%;
167
+
168
+ &::before,
169
+ &::after {
170
+ content: '';
171
+ position: absolute;
172
+ top: -4.5px;
173
+ border: 5px solid transparent;
174
+ }
175
+
176
+ &::before {
177
+ left: 0;
178
+ border-left-color: var(--border);
179
+ }
180
+ &::after {
181
+ right: 0;
182
+ border-right-color: var(--border);
183
+ }
184
+ }