create-bw-app 0.13.0 → 0.13.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-bw-app",
3
3
  "private": false,
4
- "version": "0.13.0",
4
+ "version": "0.13.2",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-bw-app": "bin/create-bw-app.mjs",
@@ -25,6 +25,6 @@
25
25
  "node": ">=20"
26
26
  },
27
27
  "dependencies": {
28
- "@inquirer/prompts": "^7.0.0"
28
+ "@inquirer/prompts": "^7.10.1"
29
29
  }
30
30
  }
package/src/constants.mjs CHANGED
@@ -60,9 +60,11 @@ export const MODULE_STARTER_FILES = {
60
60
  crm: [
61
61
  "app/crm/layout.tsx",
62
62
  "app/crm/page.tsx",
63
+ "app/crm/report/page.tsx",
63
64
  "app/api/crm/contacts/route.ts",
64
65
  "app/api/crm/organizations/route.ts",
65
66
  "app/api/crm/owners/route.ts",
67
+ "app/api/crm/report/route.ts",
66
68
  "app/api/crm/stats/route.ts",
67
69
  "app/api/crm/timeline/route.ts",
68
70
  ],
@@ -75,25 +77,27 @@ export const PLATFORM_STARTER_FILES = [
75
77
  ];
76
78
 
77
79
  export const APP_DEPENDENCY_DEFAULTS = {
78
- "@brightweblabs/app-shell": "^0.4.1",
79
- "@brightweblabs/core-auth": "^0.3.4",
80
- "@brightweblabs/infra": "^0.3.1",
81
- "@brightweblabs/module-admin": "^0.4.0",
82
- "@brightweblabs/module-crm": "^0.5.2",
83
- "@brightweblabs/module-orgs": "^0.2.2",
84
- "@brightweblabs/module-projects": "^0.4.3",
85
- "@brightweblabs/theme": "^0.2.1",
86
- "@brightweblabs/ui": "^1.0.1",
80
+ "@brightweblabs/app-shell": "^0.6.0",
81
+ "@brightweblabs/core-auth": "^0.4.0",
82
+ "@brightweblabs/infra": "^0.3.2",
83
+ "@brightweblabs/module-admin": "^0.5.0",
84
+ "@brightweblabs/module-crm": "^0.8.0",
85
+ "@brightweblabs/module-orgs": "^0.3.0",
86
+ "@brightweblabs/module-projects": "^0.5.0",
87
+ "@brightweblabs/theme": "^0.4.0",
88
+ "@brightweblabs/ui": "^1.1.0",
89
+ "geist": "1.7.2",
87
90
  "lucide-react": "^1.8.0",
88
91
  "next": "16.1.6",
89
- "react": "19.2.3",
90
- "react-dom": "19.2.3",
92
+ "react": "19.2.4",
93
+ "react-dom": "19.2.4",
91
94
  };
92
95
 
93
96
  export const SITE_DEPENDENCY_DEFAULTS = {
97
+ "geist": "1.7.2",
94
98
  "next": "16.1.6",
95
- "react": "19.2.3",
96
- "react-dom": "19.2.3",
99
+ "react": "19.2.4",
100
+ "react-dom": "19.2.4",
97
101
  };
98
102
 
99
103
  export const APP_DEV_DEPENDENCY_DEFAULTS = {
package/src/generator.mjs CHANGED
@@ -711,6 +711,7 @@ export function createPackageJson({
711
711
  lint: "tsc --noEmit",
712
712
  },
713
713
  dependencies: sortObjectKeys({
714
+ "geist": versionMap.geist,
714
715
  "next": versionMap.next,
715
716
  "react": versionMap.react,
716
717
  "react-dom": versionMap["react-dom"],
@@ -735,6 +736,7 @@ export function createPackageJson({
735
736
  "@brightweblabs/infra": internalDependencyVersion("@brightweblabs/infra"),
736
737
  "@brightweblabs/theme": internalDependencyVersion("@brightweblabs/theme"),
737
738
  "@brightweblabs/ui": internalDependencyVersion("@brightweblabs/ui"),
739
+ "geist": versionMap.geist,
738
740
  "lucide-react": versionMap["lucide-react"],
739
741
  "next": versionMap.next,
740
742
  "react": versionMap.react,
@@ -0,0 +1,2 @@
1
+ export { GeistMono as geistMono } from "geist/font/mono";
2
+ export { GeistSans as geistSans } from "geist/font/sans";
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { starterBrandConfig } from "../config/brand";
3
+ import { geistMono, geistSans } from "./fonts";
3
4
  import "./globals.css";
4
5
 
5
6
  export const metadata = {
@@ -9,8 +10,10 @@ export const metadata = {
9
10
 
10
11
  export default function RootLayout({ children }: { children: ReactNode }) {
11
12
  return (
12
- <html lang="en">
13
- <body>{children}</body>
13
+ <html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
14
+ <body>
15
+ {children}
16
+ </body>
14
17
  </html>
15
18
  );
16
19
  }
@@ -1,3 +1,4 @@
1
1
  export { handleCrmContactsGetRequest as GET } from "@brightweblabs/module-crm";
2
+ export { handleCrmContactsDeleteRequest as DELETE } from "@brightweblabs/module-crm";
2
3
  export { handleCrmContactsPatchRequest as PATCH } from "@brightweblabs/module-crm";
3
4
  export { handleCrmContactsPostRequest as POST } from "@brightweblabs/module-crm";
@@ -0,0 +1 @@
1
+ export { handleCrmReportGetRequest as GET } from "@brightweblabs/module-crm";
@@ -0,0 +1,5 @@
1
+ import { CrmReportPage } from "@brightweblabs/module-crm/ui";
2
+
3
+ export default function ReportPage() {
4
+ return <CrmReportPage />;
5
+ }
@@ -0,0 +1 @@
1
+ export { GeistSans as geistSans } from "geist/font/sans";
@@ -1,8 +1,8 @@
1
1
  @import "tailwindcss";
2
2
 
3
3
  @theme inline {
4
- --font-sans: var(--font-sans);
5
- --font-display: var(--font-display);
4
+ --font-sans: var(--font-body, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
5
+ --font-display: var(--font-heading, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
6
6
  --color-background: var(--background);
7
7
  --color-foreground: var(--foreground);
8
8
  --color-muted-foreground: var(--muted-foreground);
@@ -18,8 +18,9 @@
18
18
 
19
19
  :root {
20
20
  color-scheme: light;
21
- --font-sans: "IBM Plex Sans", "Segoe UI", sans-serif;
22
- --font-display: Georgia, "Times New Roman", serif;
21
+ --font-body: var(--font-geist-sans);
22
+ --font-sans: var(--font-body);
23
+ --font-display: var(--font-body);
23
24
  --background: #f3efe5;
24
25
  --foreground: #18241d;
25
26
  --muted-foreground: #5a665c;
@@ -39,7 +40,8 @@
39
40
  --line: var(--border);
40
41
  }
41
42
 
42
- * {
43
+ @layer base {
44
+ * {
43
45
  box-sizing: border-box;
44
46
  }
45
47
 
@@ -55,7 +57,7 @@ body {
55
57
  radial-gradient(circle at 85% 12%, rgba(189, 140, 89, 0.12), transparent 18rem),
56
58
  linear-gradient(180deg, #faf8f1 0%, var(--background) 48%, #e8e0cf 100%);
57
59
  color: var(--foreground);
58
- font-family: var(--font-sans), sans-serif;
60
+ font-family: var(--font-sans);
59
61
  }
60
62
 
61
63
  body::before {
@@ -76,6 +78,7 @@ a {
76
78
  text-decoration: none;
77
79
  }
78
80
 
79
- ::selection {
80
- background: rgba(38, 105, 70, 0.18);
81
+ ::selection {
82
+ background: rgba(38, 105, 70, 0.18);
83
+ }
81
84
  }
@@ -1,6 +1,7 @@
1
1
  import type { Metadata } from "next";
2
2
  import type { ReactNode } from "react";
3
3
  import { siteConfig } from "@/config/site";
4
+ import { geistSans } from "./fonts";
4
5
  import "./globals.css";
5
6
 
6
7
  export const metadata: Metadata = {
@@ -11,7 +12,7 @@ export const metadata: Metadata = {
11
12
  export default function RootLayout({ children }: { children: ReactNode }) {
12
13
  return (
13
14
  <html lang="en">
14
- <body>{children}</body>
15
+ <body className={geistSans.variable}>{children}</body>
15
16
  </html>
16
17
  );
17
18
  }
@@ -0,0 +1,81 @@
1
+ -- Admin-managed app user invitations, ported from MQ.
2
+
3
+ CREATE TABLE IF NOT EXISTS public.admin_user_invitations (
4
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5
+ invited_email text NOT NULL,
6
+ role_code text NOT NULL REFERENCES public.roles(code) CHECK (role_code IN ('staff', 'admin')),
7
+ status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'revoked', 'expired')),
8
+ invited_by_profile_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
9
+ accepted_by_profile_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
10
+ accepted_at timestamptz,
11
+ revoked_at timestamptz,
12
+ expires_at timestamptz NOT NULL DEFAULT (now() + interval '14 days'),
13
+ created_at timestamptz NOT NULL DEFAULT now(),
14
+ updated_at timestamptz NOT NULL DEFAULT now()
15
+ );
16
+
17
+ CREATE UNIQUE INDEX IF NOT EXISTS admin_user_invitations_unique_pending_email
18
+ ON public.admin_user_invitations (lower(invited_email))
19
+ WHERE status = 'pending';
20
+
21
+ CREATE INDEX IF NOT EXISTS idx_admin_user_invitations_status
22
+ ON public.admin_user_invitations (status, created_at DESC);
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_admin_user_invitations_email_status
25
+ ON public.admin_user_invitations (lower(invited_email), status);
26
+
27
+ CREATE OR REPLACE FUNCTION public.touch_admin_user_invitations_updated_at()
28
+ RETURNS trigger
29
+ LANGUAGE plpgsql
30
+ SECURITY DEFINER
31
+ SET search_path = public
32
+ AS $$
33
+ BEGIN
34
+ NEW.updated_at := now();
35
+ RETURN NEW;
36
+ END;
37
+ $$;
38
+
39
+ DROP TRIGGER IF EXISTS trg_touch_admin_user_invitations_updated_at ON public.admin_user_invitations;
40
+ CREATE TRIGGER trg_touch_admin_user_invitations_updated_at
41
+ BEFORE UPDATE ON public.admin_user_invitations
42
+ FOR EACH ROW
43
+ EXECUTE FUNCTION public.touch_admin_user_invitations_updated_at();
44
+
45
+ CREATE OR REPLACE FUNCTION public.normalize_admin_user_invitation_email()
46
+ RETURNS trigger
47
+ LANGUAGE plpgsql
48
+ SECURITY DEFINER
49
+ SET search_path = public
50
+ AS $$
51
+ BEGIN
52
+ NEW.invited_email := lower(trim(NEW.invited_email));
53
+ RETURN NEW;
54
+ END;
55
+ $$;
56
+
57
+ DROP TRIGGER IF EXISTS trg_normalize_admin_user_invitation_email ON public.admin_user_invitations;
58
+ CREATE TRIGGER trg_normalize_admin_user_invitation_email
59
+ BEFORE INSERT OR UPDATE OF invited_email ON public.admin_user_invitations
60
+ FOR EACH ROW
61
+ EXECUTE FUNCTION public.normalize_admin_user_invitation_email();
62
+
63
+ ALTER TABLE public.admin_user_invitations ENABLE ROW LEVEL SECURITY;
64
+
65
+ DROP POLICY IF EXISTS "Admins manage app user invitations" ON public.admin_user_invitations;
66
+ CREATE POLICY "Admins manage app user invitations"
67
+ ON public.admin_user_invitations
68
+ FOR ALL
69
+ TO authenticated
70
+ USING (public.is_admin())
71
+ WITH CHECK (public.is_admin());
72
+
73
+ DROP POLICY IF EXISTS "Invited app users view own invitations" ON public.admin_user_invitations;
74
+ CREATE POLICY "Invited app users view own invitations"
75
+ ON public.admin_user_invitations
76
+ FOR SELECT
77
+ TO authenticated
78
+ USING (
79
+ status = 'pending'
80
+ AND lower(invited_email) = lower(coalesce(auth.jwt() ->> 'email', ''))
81
+ );
@@ -0,0 +1,59 @@
1
+ -- Restrict the CRM status transition RPC to staff and trusted service calls.
2
+
3
+ CREATE OR REPLACE FUNCTION public.set_crm_status(
4
+ p_contact_id uuid,
5
+ p_new_status text,
6
+ p_reason text DEFAULT NULL
7
+ )
8
+ RETURNS void
9
+ LANGUAGE plpgsql
10
+ SECURITY DEFINER
11
+ SET search_path = public
12
+ AS $$
13
+ DECLARE
14
+ v_current text;
15
+ BEGIN
16
+ IF COALESCE(auth.role(), '') <> 'service_role'
17
+ AND NOT COALESCE(public.is_staff(), false)
18
+ THEN
19
+ RAISE EXCEPTION 'Staff access required'
20
+ USING ERRCODE = '42501';
21
+ END IF;
22
+
23
+ SELECT status INTO v_current
24
+ FROM public.crm_contacts
25
+ WHERE id = p_contact_id;
26
+
27
+ IF v_current IS NULL THEN
28
+ v_current := 'none';
29
+ END IF;
30
+
31
+ IF v_current = p_new_status THEN
32
+ RETURN;
33
+ END IF;
34
+
35
+ UPDATE public.crm_contacts
36
+ SET status = p_new_status,
37
+ updated_at = now()
38
+ WHERE id = p_contact_id;
39
+
40
+ INSERT INTO public.crm_status_log (
41
+ contact_id,
42
+ previous_status,
43
+ new_status,
44
+ reason,
45
+ changed_by_user_id
46
+ ) VALUES (
47
+ p_contact_id,
48
+ v_current,
49
+ p_new_status,
50
+ p_reason,
51
+ auth.uid()
52
+ );
53
+ END;
54
+ $$;
55
+
56
+ REVOKE EXECUTE ON FUNCTION public.set_crm_status(uuid, text, text) FROM PUBLIC;
57
+ REVOKE EXECUTE ON FUNCTION public.set_crm_status(uuid, text, text) FROM anon;
58
+ GRANT EXECUTE ON FUNCTION public.set_crm_status(uuid, text, text) TO authenticated;
59
+ GRANT EXECUTE ON FUNCTION public.set_crm_status(uuid, text, text) TO service_role;