showpane 0.4.0 → 0.4.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.
Files changed (106) hide show
  1. package/README.md +22 -1
  2. package/bundle/meta/scaffold-manifest.json +73 -0
  3. package/bundle/scaffold/VERSION +1 -0
  4. package/bundle/scaffold/__dot__env.example +24 -0
  5. package/bundle/scaffold/__dot__gitignore +41 -0
  6. package/bundle/scaffold/docker/Caddyfile +3 -0
  7. package/bundle/scaffold/docker/Dockerfile +30 -0
  8. package/bundle/scaffold/docker-compose.yml +53 -0
  9. package/bundle/scaffold/next.config.ts +20 -0
  10. package/bundle/scaffold/package-lock.json +5843 -0
  11. package/bundle/scaffold/package.json +42 -0
  12. package/bundle/scaffold/postcss.config.js +6 -0
  13. package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
  14. package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
  15. package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
  16. package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
  17. package/bundle/scaffold/prisma/schema.local.prisma +131 -0
  18. package/bundle/scaffold/prisma/schema.prisma +128 -0
  19. package/bundle/scaffold/prisma/seed.ts +49 -0
  20. package/bundle/scaffold/public/example-avatar.svg +4 -0
  21. package/bundle/scaffold/public/example-logo.svg +4 -0
  22. package/bundle/scaffold/public/robots.txt +2 -0
  23. package/bundle/scaffold/scripts/backup.sh +19 -0
  24. package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
  25. package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
  26. package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
  27. package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
  28. package/bundle/scaffold/scripts/restore.sh +31 -0
  29. package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
  30. package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
  31. package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
  32. package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
  33. package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
  34. package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
  35. package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
  36. package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
  37. package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
  38. package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
  39. package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
  40. package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
  41. package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
  42. package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
  43. package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
  44. package/bundle/scaffold/src/app/api/health/route.ts +19 -0
  45. package/bundle/scaffold/src/app/globals.css +7 -0
  46. package/bundle/scaffold/src/app/layout.tsx +25 -0
  47. package/bundle/scaffold/src/app/page.tsx +171 -0
  48. package/bundle/scaffold/src/components/portal-login.tsx +169 -0
  49. package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
  50. package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
  51. package/bundle/scaffold/src/lib/branding.ts +50 -0
  52. package/bundle/scaffold/src/lib/client-auth.ts +98 -0
  53. package/bundle/scaffold/src/lib/client-portals.ts +134 -0
  54. package/bundle/scaffold/src/lib/control-plane.ts +100 -0
  55. package/bundle/scaffold/src/lib/db.ts +7 -0
  56. package/bundle/scaffold/src/lib/files.ts +124 -0
  57. package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
  58. package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
  59. package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
  60. package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
  61. package/bundle/scaffold/src/lib/storage.ts +204 -0
  62. package/bundle/scaffold/src/lib/token.ts +186 -0
  63. package/bundle/scaffold/src/lib/utils.ts +6 -0
  64. package/bundle/scaffold/src/middleware.ts +61 -0
  65. package/bundle/scaffold/tailwind.config.ts +15 -0
  66. package/bundle/scaffold/tests/__dot__gitkeep +0 -0
  67. package/bundle/scaffold/tsconfig.json +23 -0
  68. package/bundle/scaffold/vitest.config.ts +13 -0
  69. package/bundle/toolchain/VERSION +1 -0
  70. package/bundle/toolchain/bin/check-slug.ts +59 -0
  71. package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
  72. package/bundle/toolchain/bin/create-portal.ts +71 -0
  73. package/bundle/toolchain/bin/delete-portal.ts +48 -0
  74. package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
  75. package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
  76. package/bundle/toolchain/bin/generate-share-link.ts +68 -0
  77. package/bundle/toolchain/bin/list-portals.ts +53 -0
  78. package/bundle/toolchain/bin/materialize-file.ts +35 -0
  79. package/bundle/toolchain/bin/query-analytics.ts +88 -0
  80. package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
  81. package/bundle/toolchain/bin/showpane-config +63 -0
  82. package/bundle/toolchain/bin/tsconfig.json +13 -0
  83. package/bundle/toolchain/skills/VERSION +1 -0
  84. package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
  85. package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
  86. package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
  87. package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
  88. package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
  89. package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
  90. package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
  91. package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
  92. package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
  93. package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
  94. package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
  95. package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
  96. package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
  97. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
  98. package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
  99. package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
  100. package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
  101. package/bundle/toolchain/skills/shared/preamble.md +137 -0
  102. package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
  103. package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
  104. package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
  105. package/dist/index.js +875 -159
  106. package/package.json +4 -2
@@ -0,0 +1,137 @@
1
+ # Showpane Skill Preamble
2
+
3
+ Before executing any skill, run the following setup in a Bash tool call:
4
+
5
+ ```bash
6
+ # --- Showpane Preamble ---
7
+
8
+ # 1. Read config (env vars override config.json)
9
+ SHOWPANE_CONFIG="${HOME}/.showpane/config.json"
10
+ if [ -f "$SHOWPANE_CONFIG" ]; then
11
+ _cfg_app_path=$(python3 -c "import json; d=json.load(open('$SHOWPANE_CONFIG')); print(d.get('app_path',''))" 2>/dev/null || true)
12
+ _cfg_deploy_mode=$(python3 -c "import json; d=json.load(open('$SHOWPANE_CONFIG')); print(d.get('deploy_mode',''))" 2>/dev/null || true)
13
+ _cfg_org_slug=$(python3 -c "import json; d=json.load(open('$SHOWPANE_CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null || true)
14
+ fi
15
+
16
+ export APP_PATH="${SHOWPANE_APP_PATH:-${_cfg_app_path:-}}"
17
+ export DEPLOY_MODE="${_cfg_deploy_mode:-local}"
18
+ export ORG_SLUG="${_cfg_org_slug:-}"
19
+
20
+ if [ -z "$APP_PATH" ]; then
21
+ echo "ERROR: APP_PATH not set. Run: showpane-config set app_path /path/to/showpane-project" >&2
22
+ exit 1
23
+ fi
24
+
25
+ # 2. Source app .env
26
+ if [ -f "$APP_PATH/.env" ]; then
27
+ set -a && source "$APP_PATH/.env" && set +a
28
+ fi
29
+
30
+ # Allow DATABASE_URL env override
31
+ export DATABASE_URL="${DATABASE_URL:-}"
32
+
33
+ # 3. Verify app installed
34
+ if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
35
+ echo "ERROR: Prisma client not generated. Run: cd $APP_PATH && npm install && npx prisma generate" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # 4. Version check
40
+ SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
41
+ SKILL_VERSION=$(head -1 "$SKILL_DIR/skills/VERSION" 2>/dev/null | cut -d' ' -f1 || echo "unknown")
42
+ APP_VERSION=$(head -1 "$APP_PATH/VERSION" 2>/dev/null || echo "unknown")
43
+ APP_REQUIRED=$(head -1 "$SKILL_DIR/skills/VERSION" 2>/dev/null | grep -oP '(?<=app >= )\S+' || echo "unknown")
44
+
45
+ if [ "$APP_VERSION" != "unknown" ] && [ "$APP_REQUIRED" != "unknown" ]; then
46
+ if [ "$(printf '%s\n' "$APP_REQUIRED" "$APP_VERSION" | sort -V | head -1)" != "$APP_REQUIRED" ]; then
47
+ echo "WARNING: App version $APP_VERSION may not meet skills requirement (>= $APP_REQUIRED)" >&2
48
+ fi
49
+ fi
50
+
51
+ # 5. Load learnings count
52
+ LEARNINGS_FILE="${HOME}/.showpane/learnings.jsonl"
53
+ LEARNINGS_COUNT=0
54
+ if [ -f "$LEARNINGS_FILE" ]; then
55
+ LEARNINGS_COUNT=$(wc -l < "$LEARNINGS_FILE" | tr -d ' ')
56
+ fi
57
+
58
+ # 5b. Predictive next-skill suggestion
59
+ if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
60
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
61
+ [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
62
+ fi
63
+
64
+ # 5c. Search relevant learnings
65
+ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
66
+ if [ -f "$LEARN_FILE" ]; then
67
+ _LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
68
+ echo "LEARNINGS: $_LEARN_COUNT entries"
69
+ if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
70
+ echo "RECENT_LEARNINGS:"
71
+ tail -5 "$LEARN_FILE" 2>/dev/null
72
+ fi
73
+ fi
74
+
75
+ # 5d. Checkpoint: save/resume state
76
+ SHOWPANE_CHECKPOINTS="$HOME/.showpane/checkpoints"
77
+ mkdir -p "$SHOWPANE_CHECKPOINTS"
78
+
79
+ save_checkpoint() {
80
+ local skill="$1" step="$2" data="$3"
81
+ echo '{"skill":"'"$skill"'","step":"'"$step"'","data":'"$data"',"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > "$SHOWPANE_CHECKPOINTS/${skill}.json"
82
+ }
83
+
84
+ load_checkpoint() {
85
+ local skill="$1"
86
+ local cp="$SHOWPANE_CHECKPOINTS/${skill}.json"
87
+ if [ -f "$cp" ]; then
88
+ cat "$cp"
89
+ fi
90
+ }
91
+
92
+ clear_checkpoint() {
93
+ local skill="$1"
94
+ rm -f "$SHOWPANE_CHECKPOINTS/${skill}.json"
95
+ }
96
+
97
+ # 6. Load telemetry setting
98
+ TELEMETRY=$(python3 -c "import json; d=json.load(open('$SHOWPANE_CONFIG')); print(d.get('telemetry','off'))" 2>/dev/null || echo "off")
99
+
100
+ # 7. Print status
101
+ echo "SHOWPANE_VERSION: $SKILL_VERSION"
102
+ echo "DEPLOY_MODE: $DEPLOY_MODE"
103
+ echo "APP_PATH: $APP_PATH"
104
+ echo "LEARNINGS: $LEARNINGS_COUNT entries"
105
+
106
+ # --- End Preamble ---
107
+ ```
108
+
109
+ If RECENT_SKILLS is shown, suggest the likely next skill:
110
+ - After portal-create → suggest /portal-preview
111
+ - After portal-preview → suggest /portal-deploy or /portal-share
112
+ - After portal-deploy → suggest /portal-status or /portal-verify
113
+ - After portal-setup → suggest /portal-create
114
+ - After portal-credentials → suggest /portal-share
115
+ - After portal-update → suggest /portal-deploy
116
+
117
+ If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
118
+ relevant warnings or tips for this operation. Apply them where relevant but don't
119
+ mention them unless they directly affect the current task.
120
+
121
+ If `skills/shared/platform-constraints.md` exists, read it once near the start of the skill
122
+ and apply only the constraints relevant to the current request. Mention them only when the
123
+ user is about to do something unsupported or limited.
124
+
125
+ ## Checkpoint/Resume
126
+
127
+ Skills can save progress with `save_checkpoint "skill-name" "step-name" '{"key":"value"}'`.
128
+ On restart, `load_checkpoint "skill-name"` returns the saved state.
129
+ After successful completion, `clear_checkpoint "skill-name"` cleans up.
130
+
131
+ If a checkpoint exists when a skill starts, offer to resume:
132
+ "Found a checkpoint from [timestamp]. Resume from step [step-name]?"
133
+
134
+ All bin/ scripts run via:
135
+ ```bash
136
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/script.ts" [args]
137
+ ```
@@ -0,0 +1,205 @@
1
+ "use client";
2
+
3
+ /**
4
+ * TEMPLATE: Consulting Engagement Portal
5
+ *
6
+ * Use for ongoing consulting/advisory engagements.
7
+ * Structure: Project Overview → Deliverables & Timeline → Documents → Meetings
8
+ *
9
+ * This file is READ by Claude Code as a reference, not used directly.
10
+ */
11
+
12
+ import { type ReactNode } from "react";
13
+ import {
14
+ CalendarDays,
15
+ CheckCircle2,
16
+ ChevronDown,
17
+ Clock,
18
+ Download,
19
+ FileText,
20
+ LayoutDashboard,
21
+ ListChecks,
22
+ } from "lucide-react";
23
+ import { cn } from "@/lib/utils";
24
+ import { PortalShell } from "@/components/portal-shell";
25
+
26
+ // ─── Tab 1: Project Overview ──────────────────────────────────
27
+ // High-level summary: what the engagement covers, current phase,
28
+ // key contacts, and engagement terms.
29
+ function ProjectOverviewTab() {
30
+ return (
31
+ <>
32
+ <div className="mb-6 rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
33
+ <div className="flex items-center justify-between">
34
+ <h3 className="text-base font-bold tracking-tight text-gray-900">
35
+ Engagement summary
36
+ </h3>
37
+ <span className="inline-flex items-center gap-1.5 rounded-full bg-green-50 px-2 py-0.5 text-[11px] font-medium text-green-700">
38
+ <span className="h-1.5 w-1.5 rounded-full bg-green-400" />
39
+ Active
40
+ </span>
41
+ </div>
42
+ <p className="mt-3 text-sm leading-relaxed text-gray-600">
43
+ [Brief description of the consulting engagement: what problem you are
44
+ solving, the approach, and expected outcomes. 2-3 sentences.]
45
+ </p>
46
+ <div className="mt-4 grid gap-3 sm:grid-cols-3">
47
+ {[
48
+ { label: "Started", value: "[Start Date]" },
49
+ { label: "Phase", value: "[Current Phase]" },
50
+ { label: "Next milestone", value: "[Milestone]" },
51
+ ].map((item) => (
52
+ <div
53
+ key={item.label}
54
+ className="rounded-xl border border-gray-100 p-3"
55
+ >
56
+ <p className="text-xs text-gray-500">{item.label}</p>
57
+ <p className="mt-0.5 text-sm font-semibold text-gray-900">
58
+ {item.value}
59
+ </p>
60
+ </div>
61
+ ))}
62
+ </div>
63
+ </div>
64
+
65
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
66
+ <h3 className="mb-3 text-base font-bold tracking-tight text-gray-900">
67
+ Scope of work
68
+ </h3>
69
+ <ul className="space-y-2">
70
+ {[
71
+ "[Workstream 1: description]",
72
+ "[Workstream 2: description]",
73
+ "[Workstream 3: description]",
74
+ ].map((item) => (
75
+ <li key={item} className="flex items-start gap-2.5 text-sm text-gray-600">
76
+ <CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-gray-300" />
77
+ {item}
78
+ </li>
79
+ ))}
80
+ </ul>
81
+ </div>
82
+ </>
83
+ );
84
+ }
85
+
86
+ // ─── Tab 2: Deliverables & Timeline ───────────────────────────
87
+ // Phase-based timeline showing what has been delivered and what is
88
+ // coming next. Each phase has status (complete/in-progress/upcoming).
89
+ function DeliverablesTab() {
90
+ const phases = [
91
+ {
92
+ name: "Phase 1: Discovery",
93
+ status: "complete" as const,
94
+ items: ["Stakeholder interviews", "Current state assessment", "Gap analysis report"],
95
+ },
96
+ {
97
+ name: "Phase 2: Recommendations",
98
+ status: "in-progress" as const,
99
+ items: ["Strategy document", "Implementation roadmap", "Cost-benefit analysis"],
100
+ },
101
+ {
102
+ name: "Phase 3: Implementation Support",
103
+ status: "upcoming" as const,
104
+ items: ["Vendor selection", "Migration plan", "Go-live support"],
105
+ },
106
+ ];
107
+
108
+ const statusConfig = {
109
+ complete: { bg: "bg-green-50", text: "text-green-700", dot: "bg-green-400", label: "Complete" },
110
+ "in-progress": { bg: "bg-blue-50", text: "text-blue-700", dot: "bg-blue-400", label: "In progress" },
111
+ upcoming: { bg: "bg-gray-50", text: "text-gray-500", dot: "bg-gray-300", label: "Upcoming" },
112
+ };
113
+
114
+ return (
115
+ <div className="w-full space-y-4">
116
+ <h3 className="text-base font-bold tracking-tight text-gray-900">
117
+ Deliverables & timeline
118
+ </h3>
119
+ {phases.map((phase) => {
120
+ const config = statusConfig[phase.status];
121
+ return (
122
+ <div key={phase.name} className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
123
+ <div className="flex items-center justify-between">
124
+ <h4 className="text-sm font-semibold text-gray-900">{phase.name}</h4>
125
+ <span className={cn("inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium", config.bg, config.text)}>
126
+ <span className={cn("h-1.5 w-1.5 rounded-full", config.dot)} />
127
+ {config.label}
128
+ </span>
129
+ </div>
130
+ <ul className="mt-3 space-y-1.5">
131
+ {phase.items.map((item) => (
132
+ <li key={item} className="flex items-center gap-2 text-sm text-gray-600">
133
+ {phase.status === "complete" ? (
134
+ <CheckCircle2 className="h-3.5 w-3.5 shrink-0 text-green-500" />
135
+ ) : (
136
+ <Clock className="h-3.5 w-3.5 shrink-0 text-gray-300" />
137
+ )}
138
+ {item}
139
+ </li>
140
+ ))}
141
+ </ul>
142
+ </div>
143
+ );
144
+ })}
145
+ </div>
146
+ );
147
+ }
148
+
149
+ // ─── Tab 3: Documents ─────────────────────────────────────────
150
+ function DocumentsTab() {
151
+ const docs = [
152
+ { name: "[Deliverable 1]", type: "PDF", date: "[Date]" },
153
+ { name: "[Deliverable 2]", type: "PDF", date: "[Date]" },
154
+ ];
155
+
156
+ return (
157
+ <div className="w-full">
158
+ <h3 className="mb-4 text-base font-bold tracking-tight text-gray-900">
159
+ Documents
160
+ </h3>
161
+ <div className="space-y-3">
162
+ {docs.map((doc) => (
163
+ <div key={doc.name} className="flex items-center justify-between rounded-2xl border bg-white p-4 shadow-sm sm:p-5">
164
+ <div className="flex items-center gap-3">
165
+ <FileText className="h-5 w-5 shrink-0 text-gray-400" />
166
+ <div>
167
+ <p className="text-sm font-medium text-gray-900">{doc.name}</p>
168
+ <p className="text-xs text-gray-500">{doc.type} - {doc.date}</p>
169
+ </div>
170
+ </div>
171
+ <button type="button" className="flex items-center gap-1.5 rounded-lg bg-gray-900 px-4 py-1.5 text-xs font-semibold text-white hover:bg-gray-800">
172
+ <Download className="h-3.5 w-3.5" />
173
+ Download
174
+ </button>
175
+ </div>
176
+ ))}
177
+ </div>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ // ─── Assembled Portal ─────────────────────────────────────────
183
+ export function ConsultingClient() {
184
+ return (
185
+ <PortalShell
186
+ companyName="[Your Company]"
187
+ companyLogo={<span className="text-xs font-bold text-white">C</span>}
188
+ clientName="[Client Company]"
189
+ clientLogoSrc="/client-logo.svg"
190
+ clientLogoAlt="[Client Company]"
191
+ lastUpdated="[Date]"
192
+ contact={{
193
+ name: "[Consultant Name]",
194
+ title: "[Title]",
195
+ avatarSrc: "/avatar.svg",
196
+ email: "[email]",
197
+ }}
198
+ tabs={[
199
+ { id: "overview", label: "Project overview", icon: LayoutDashboard, content: <ProjectOverviewTab /> },
200
+ { id: "deliverables", label: "Deliverables", icon: ListChecks, content: <DeliverablesTab /> },
201
+ { id: "documents", label: "Documents", icon: FileText, content: <DocumentsTab /> },
202
+ ]}
203
+ />
204
+ );
205
+ }
@@ -0,0 +1,237 @@
1
+ "use client";
2
+
3
+ /**
4
+ * TEMPLATE: Client Onboarding Portal
5
+ *
6
+ * Use when onboarding a new client to your service/product.
7
+ * Structure: Welcome & Setup → Getting Started Steps → Resources → Support
8
+ *
9
+ * This file is READ by Claude Code as a reference, not used directly.
10
+ */
11
+
12
+ import {
13
+ BookOpen,
14
+ CheckCircle2,
15
+ ExternalLink,
16
+ HelpCircle,
17
+ LifeBuoy,
18
+ Rocket,
19
+ } from "lucide-react";
20
+ import { cn } from "@/lib/utils";
21
+ import { PortalShell } from "@/components/portal-shell";
22
+
23
+ // ─── Tab 1: Welcome ──────────────────────────────────────────
24
+ // Hero welcome with overview of what the client can expect.
25
+ function WelcomeTab() {
26
+ return (
27
+ <>
28
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
29
+ <h3 className="text-base font-bold tracking-tight text-gray-900">
30
+ Welcome to [Your Company]
31
+ </h3>
32
+ <p className="mt-3 text-sm leading-relaxed text-gray-600">
33
+ We&apos;re excited to have [Client Company] on board. This portal is
34
+ your central hub for getting started. Here you&apos;ll find setup
35
+ steps, resources, and a direct line to your account team.
36
+ </p>
37
+ <div className="mt-5 grid gap-3 sm:grid-cols-2">
38
+ {[
39
+ {
40
+ title: "Your account manager",
41
+ desc: "[Name] — [email]",
42
+ },
43
+ {
44
+ title: "Plan",
45
+ desc: "[Plan name / tier]",
46
+ },
47
+ {
48
+ title: "Onboarding started",
49
+ desc: "[Date]",
50
+ },
51
+ {
52
+ title: "Target go-live",
53
+ desc: "[Date]",
54
+ },
55
+ ].map((item) => (
56
+ <div
57
+ key={item.title}
58
+ className="rounded-xl border border-gray-100 p-4"
59
+ >
60
+ <p className="text-xs text-gray-500">{item.title}</p>
61
+ <p className="mt-0.5 text-sm font-semibold text-gray-900">
62
+ {item.desc}
63
+ </p>
64
+ </div>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ </>
69
+ );
70
+ }
71
+
72
+ // ─── Tab 2: Getting Started ───────────────────────────────────
73
+ // Step-by-step setup checklist. Each step has a status and instructions.
74
+ function GettingStartedTab() {
75
+ const steps = [
76
+ {
77
+ done: true,
78
+ label: "Account created",
79
+ desc: "Your account is active and ready to use.",
80
+ },
81
+ {
82
+ done: false,
83
+ label: "Complete your profile",
84
+ desc: "Add your team members and set up your organization details.",
85
+ },
86
+ {
87
+ done: false,
88
+ label: "Connect your data",
89
+ desc: "Follow our integration guide to connect your existing systems.",
90
+ },
91
+ {
92
+ done: false,
93
+ label: "Review the setup with us",
94
+ desc: "Book a 30-minute call with your account manager to walk through everything.",
95
+ },
96
+ {
97
+ done: false,
98
+ label: "Go live",
99
+ desc: "Once you're comfortable, switch to production mode.",
100
+ },
101
+ ];
102
+
103
+ return (
104
+ <div className="w-full">
105
+ <h3 className="mb-4 text-base font-bold tracking-tight text-gray-900">
106
+ Setup checklist
107
+ </h3>
108
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
109
+ <div className="space-y-0">
110
+ {steps.map((step, index, items) => (
111
+ <div key={step.label} className="flex items-stretch gap-3 sm:gap-4">
112
+ <div className="flex flex-col items-center">
113
+ <span
114
+ className={cn(
115
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold",
116
+ step.done
117
+ ? "bg-green-500 text-white"
118
+ : "bg-gray-200 text-gray-600"
119
+ )}
120
+ >
121
+ {step.done ? (
122
+ <CheckCircle2 className="h-3.5 w-3.5" />
123
+ ) : (
124
+ index + 1
125
+ )}
126
+ </span>
127
+ {index < items.length - 1 ? (
128
+ <div className="w-px flex-1 bg-gray-100" />
129
+ ) : null}
130
+ </div>
131
+ <div className="pb-5">
132
+ <p
133
+ className={cn(
134
+ "text-sm font-semibold",
135
+ step.done ? "text-green-700" : "text-gray-900"
136
+ )}
137
+ >
138
+ {step.label}
139
+ </p>
140
+ <p className="mt-0.5 text-sm leading-relaxed text-gray-500">
141
+ {step.desc}
142
+ </p>
143
+ </div>
144
+ </div>
145
+ ))}
146
+ </div>
147
+ <div className="mt-4 border-t border-gray-100 pt-4">
148
+ <p className="text-xs text-gray-400">
149
+ 1 of 5 steps complete. Your account manager will follow up on
150
+ remaining items.
151
+ </p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ // ─── Tab 3: Resources ─────────────────────────────────────────
159
+ // Links to documentation, guides, and helpful resources.
160
+ function ResourcesTab() {
161
+ const resources = [
162
+ {
163
+ title: "Getting Started Guide",
164
+ desc: "Step-by-step walkthrough for new users",
165
+ url: "#",
166
+ },
167
+ {
168
+ title: "API Documentation",
169
+ desc: "Technical reference for integrations",
170
+ url: "#",
171
+ },
172
+ {
173
+ title: "Best Practices",
174
+ desc: "Tips and recommendations from our team",
175
+ url: "#",
176
+ },
177
+ {
178
+ title: "FAQ",
179
+ desc: "Answers to common questions",
180
+ url: "#",
181
+ },
182
+ ];
183
+
184
+ return (
185
+ <div className="w-full">
186
+ <h3 className="mb-4 text-base font-bold tracking-tight text-gray-900">
187
+ Resources
188
+ </h3>
189
+ <div className="space-y-3">
190
+ {resources.map((resource) => (
191
+ <a
192
+ key={resource.title}
193
+ href={resource.url}
194
+ className="flex items-center justify-between rounded-2xl border bg-white p-4 shadow-sm transition-colors hover:border-gray-300 sm:p-5"
195
+ >
196
+ <div className="flex items-center gap-3">
197
+ <BookOpen className="h-5 w-5 shrink-0 text-gray-400" />
198
+ <div>
199
+ <p className="text-sm font-medium text-gray-900">
200
+ {resource.title}
201
+ </p>
202
+ <p className="text-xs text-gray-500">{resource.desc}</p>
203
+ </div>
204
+ </div>
205
+ <ExternalLink className="h-4 w-4 shrink-0 text-gray-300" />
206
+ </a>
207
+ ))}
208
+ </div>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ // ─── Assembled Portal ─────────────────────────────────────────
214
+ export function OnboardingClient() {
215
+ return (
216
+ <PortalShell
217
+ companyName="[Your Company]"
218
+ companyLogo={<span className="text-xs font-bold text-white">C</span>}
219
+ clientName="[Client Company]"
220
+ clientLogoSrc="/client-logo.svg"
221
+ clientLogoAlt="[Client Company]"
222
+ lastUpdated="[Date]"
223
+ contact={{
224
+ name: "[Account Manager Name]",
225
+ title: "[Title]",
226
+ avatarSrc: "/avatar.svg",
227
+ email: "[email]",
228
+ }}
229
+ tabs={[
230
+ { id: "welcome", label: "Welcome", icon: Rocket, content: <WelcomeTab /> },
231
+ { id: "getting-started", label: "Getting started", icon: CheckCircle2, content: <GettingStartedTab /> },
232
+ { id: "resources", label: "Resources", icon: BookOpen, content: <ResourcesTab /> },
233
+ { id: "support", label: "Support", icon: LifeBuoy, content: <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6"><p className="text-sm text-gray-600">Need help? Contact your account manager at <span className="font-medium text-gray-900">[email]</span> or book a call.</p></div> },
234
+ ]}
235
+ />
236
+ );
237
+ }