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,283 @@
1
+ "use client";
2
+
3
+ /**
4
+ * TEMPLATE: Sales Follow-Up Portal
5
+ *
6
+ * Use this template after a sales call or introductory meeting.
7
+ * Structure: Overview (welcome + next steps) → Meeting Notes → Documents
8
+ *
9
+ * This file is READ by Claude Code as a reference, not used directly.
10
+ * /portal create reads this to understand the tab structure and content patterns.
11
+ */
12
+
13
+ import { type ReactNode } from "react";
14
+ import {
15
+ CalendarDays,
16
+ ChevronDown,
17
+ Download,
18
+ FileText,
19
+ Presentation,
20
+ } from "lucide-react";
21
+ import { cn } from "@/lib/utils";
22
+ import { PortalShell } from "@/components/portal-shell";
23
+
24
+ // ─── Tab 1: Overview ──────────────────────────────────────────
25
+ // Welcome message from the account manager, followed by a numbered
26
+ // "next steps" timeline. This is always the first tab.
27
+ function OverviewTab() {
28
+ // Contact card: shows the account manager's name, role, and a short
29
+ // personal message to the client. Keep it warm but professional.
30
+ const contactMessage = (
31
+ <div className="mb-6 overflow-hidden rounded-2xl border bg-white shadow-sm">
32
+ <div className="flex items-center gap-3 border-b border-gray-100 px-5 py-4 sm:px-6">
33
+ <div className="flex h-9 w-9 items-center justify-center rounded-full bg-blue-100">
34
+ <span className="text-sm font-bold text-blue-600">JS</span>
35
+ </div>
36
+ <div className="flex-1">
37
+ <span className="text-sm font-semibold text-gray-900">
38
+ Jane Smith
39
+ </span>
40
+ <span className="ml-2 rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium text-gray-500">
41
+ Account Manager
42
+ </span>
43
+ <div className="mt-0.5">
44
+ <span className="text-xs text-gray-400">jane@company.com</span>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div className="px-4 py-3 sm:px-5">
49
+ <p className="text-sm leading-relaxed text-gray-600">
50
+ <span className="font-semibold text-gray-900">
51
+ Welcome [Client Name].
52
+ </span>{" "}
53
+ I&apos;ve put everything together here: our discussion summary, next
54
+ steps, and documents. Looking forward to our next conversation.
55
+ </p>
56
+ </div>
57
+ </div>
58
+ );
59
+
60
+ // Next steps: numbered timeline with completion status.
61
+ // Each step has: done (boolean), label, and descriptive text.
62
+ // Replace these with actual action items from the meeting.
63
+ const steps = [
64
+ {
65
+ done: false,
66
+ label: "Review and sign the NDA",
67
+ text: "Download from the Documents tab, sign, and return via email",
68
+ },
69
+ {
70
+ done: false,
71
+ label: "Share the opportunity details",
72
+ text: "Send the project brief so we can prepare our analysis",
73
+ },
74
+ {
75
+ done: false,
76
+ label: "Follow-up call to review findings",
77
+ text: "We'll walk through our research and recommendations",
78
+ },
79
+ ];
80
+
81
+ return (
82
+ <>
83
+ {contactMessage}
84
+ <div className="mt-6">
85
+ <h3 className="mb-4 text-base font-bold tracking-tight text-gray-900">
86
+ Next steps
87
+ </h3>
88
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
89
+ <ol className="space-y-0">
90
+ {steps.map((step, index, items) => (
91
+ <li key={step.label} className="flex items-stretch gap-3 sm:gap-4">
92
+ <div className="flex flex-col items-center">
93
+ <span
94
+ className={cn(
95
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold",
96
+ step.done
97
+ ? "bg-green-500 text-white"
98
+ : "bg-gray-900 text-white"
99
+ )}
100
+ >
101
+ {step.done ? "\u2713" : index + 1}
102
+ </span>
103
+ {index < items.length - 1 ? (
104
+ <div className="w-px flex-1 bg-gray-100" />
105
+ ) : null}
106
+ </div>
107
+ <div className="pb-5">
108
+ <p className="text-sm font-semibold text-gray-900">
109
+ {step.label}
110
+ </p>
111
+ <p className="mt-0.5 text-sm leading-relaxed text-gray-500">
112
+ {step.text}
113
+ </p>
114
+ </div>
115
+ </li>
116
+ ))}
117
+ </ol>
118
+ </div>
119
+ </div>
120
+ </>
121
+ );
122
+ }
123
+
124
+ // ─── Tab 2: Meetings ──────────────────────────────────────────
125
+ // Collapsible sections for each meeting. Include: date, attendees,
126
+ // key discussion points, and agreed actions.
127
+ function MeetingSection({
128
+ title,
129
+ children,
130
+ defaultOpen = true,
131
+ }: {
132
+ title: string;
133
+ children: ReactNode;
134
+ defaultOpen?: boolean;
135
+ }) {
136
+ return (
137
+ <details open={defaultOpen} className="group">
138
+ <summary className="flex cursor-pointer list-none items-center gap-1.5 text-left">
139
+ <ChevronDown className="h-3.5 w-3.5 shrink-0 text-gray-400 transition-transform group-open:rotate-180" />
140
+ <h4 className="text-sm font-semibold text-gray-900">{title}</h4>
141
+ </summary>
142
+ <div className="mt-2 pl-5">{children}</div>
143
+ </details>
144
+ );
145
+ }
146
+
147
+ function MeetingsTab() {
148
+ return (
149
+ <div className="w-full">
150
+ <div className="mb-4">
151
+ <h3 className="text-base font-bold tracking-tight text-gray-900">
152
+ Meetings
153
+ </h3>
154
+ </div>
155
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
156
+ <div className="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between sm:gap-4">
157
+ <h4 className="text-sm font-semibold text-gray-900">
158
+ Introductory Call
159
+ </h4>
160
+ <span className="text-xs font-medium text-gray-500">
161
+ [Meeting Date]
162
+ </span>
163
+ </div>
164
+ <p className="mt-1 text-xs text-gray-500">
165
+ [Attendee 1] &amp; [Attendee 2] - ~[duration] mins
166
+ </p>
167
+ <div className="mt-5 space-y-4">
168
+ <MeetingSection title="What we discussed">
169
+ <ul className="space-y-2 text-sm text-gray-600">
170
+ <li className="flex gap-2.5">
171
+ <span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary/60" />
172
+ [Key discussion point 1]
173
+ </li>
174
+ <li className="flex gap-2.5">
175
+ <span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary/60" />
176
+ [Key discussion point 2]
177
+ </li>
178
+ </ul>
179
+ </MeetingSection>
180
+ <MeetingSection title="Agreed next steps">
181
+ <ul className="space-y-2 text-sm text-gray-600">
182
+ <li className="flex gap-2.5">
183
+ <span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" />
184
+ [Action item 1]
185
+ </li>
186
+ <li className="flex gap-2.5">
187
+ <span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" />
188
+ [Action item 2]
189
+ </li>
190
+ </ul>
191
+ </MeetingSection>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ );
196
+ }
197
+
198
+ // ─── Tab 3: Documents ─────────────────────────────────────────
199
+ // Document cards with download buttons. Each document has a name,
200
+ // description, and action button.
201
+ function DocumentsTab() {
202
+ return (
203
+ <div className="w-full">
204
+ <div className="mb-4 flex items-center justify-between">
205
+ <h3 className="text-base font-bold tracking-tight text-gray-900">
206
+ Documents
207
+ </h3>
208
+ <span className="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700">
209
+ <span className="h-1.5 w-1.5 rounded-full bg-amber-400" />
210
+ Action required
211
+ </span>
212
+ </div>
213
+ <div className="rounded-2xl border bg-white p-5 shadow-sm sm:p-6">
214
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
215
+ <div className="flex items-start gap-3">
216
+ <FileText className="mt-0.5 h-5 w-5 shrink-0 text-gray-400" />
217
+ <div>
218
+ <p className="text-sm font-medium text-gray-900">
219
+ [Document Name]
220
+ </p>
221
+ <p className="mt-1 text-sm text-gray-500">
222
+ [Brief description or instructions]
223
+ </p>
224
+ </div>
225
+ </div>
226
+ <button
227
+ type="button"
228
+ className="flex w-full items-center justify-center gap-1.5 rounded-lg bg-gray-900 px-5 py-2 text-xs font-semibold text-white transition-colors hover:bg-gray-800 sm:w-auto"
229
+ >
230
+ <Download className="h-3.5 w-3.5" />
231
+ Download PDF
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ );
237
+ }
238
+
239
+ // ─── Assembled Portal ─────────────────────────────────────────
240
+ // The exported component wires everything into PortalShell.
241
+ // Replace all placeholder values with actual client/company data.
242
+ export function SalesFollowupClient() {
243
+ return (
244
+ <PortalShell
245
+ companyName="[Your Company]"
246
+ companyLogo={
247
+ <span className="text-xs font-bold text-white">C</span>
248
+ }
249
+ clientName="[Client Company]"
250
+ clientLogoSrc="/client-logo.svg"
251
+ clientLogoAlt="[Client Company]"
252
+ lastUpdated="[Date]"
253
+ hideFooterOnTab="overview"
254
+ contact={{
255
+ name: "[Account Manager Name]",
256
+ title: "[Title]",
257
+ avatarSrc: "/avatar.svg",
258
+ email: "[email]",
259
+ }}
260
+ tabs={[
261
+ {
262
+ id: "overview",
263
+ label: "Overview",
264
+ icon: Presentation,
265
+ content: <OverviewTab />,
266
+ },
267
+ {
268
+ id: "meetings",
269
+ label: "Meetings",
270
+ icon: CalendarDays,
271
+ content: <MeetingsTab />,
272
+ },
273
+ {
274
+ id: "documents",
275
+ label: "Documents",
276
+ icon: FileText,
277
+ badge: "amber",
278
+ content: <DocumentsTab />,
279
+ },
280
+ ]}
281
+ />
282
+ );
283
+ }