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.
- package/README.md +22 -1
- package/bundle/meta/scaffold-manifest.json +73 -0
- package/bundle/scaffold/VERSION +1 -0
- package/bundle/scaffold/__dot__env.example +24 -0
- package/bundle/scaffold/__dot__gitignore +41 -0
- package/bundle/scaffold/docker/Caddyfile +3 -0
- package/bundle/scaffold/docker/Dockerfile +30 -0
- package/bundle/scaffold/docker-compose.yml +53 -0
- package/bundle/scaffold/next.config.ts +20 -0
- package/bundle/scaffold/package-lock.json +5843 -0
- package/bundle/scaffold/package.json +42 -0
- package/bundle/scaffold/postcss.config.js +6 -0
- package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
- package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
- package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
- package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
- package/bundle/scaffold/prisma/schema.local.prisma +131 -0
- package/bundle/scaffold/prisma/schema.prisma +128 -0
- package/bundle/scaffold/prisma/seed.ts +49 -0
- package/bundle/scaffold/public/example-avatar.svg +4 -0
- package/bundle/scaffold/public/example-logo.svg +4 -0
- package/bundle/scaffold/public/robots.txt +2 -0
- package/bundle/scaffold/scripts/backup.sh +19 -0
- package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
- package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
- package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
- package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
- package/bundle/scaffold/scripts/restore.sh +31 -0
- package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
- package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
- package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
- package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
- package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
- package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
- package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
- package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
- package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
- package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
- package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
- package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
- package/bundle/scaffold/src/app/api/health/route.ts +19 -0
- package/bundle/scaffold/src/app/globals.css +7 -0
- package/bundle/scaffold/src/app/layout.tsx +25 -0
- package/bundle/scaffold/src/app/page.tsx +171 -0
- package/bundle/scaffold/src/components/portal-login.tsx +169 -0
- package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
- package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
- package/bundle/scaffold/src/lib/branding.ts +50 -0
- package/bundle/scaffold/src/lib/client-auth.ts +98 -0
- package/bundle/scaffold/src/lib/client-portals.ts +134 -0
- package/bundle/scaffold/src/lib/control-plane.ts +100 -0
- package/bundle/scaffold/src/lib/db.ts +7 -0
- package/bundle/scaffold/src/lib/files.ts +124 -0
- package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
- package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
- package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
- package/bundle/scaffold/src/lib/storage.ts +204 -0
- package/bundle/scaffold/src/lib/token.ts +186 -0
- package/bundle/scaffold/src/lib/utils.ts +6 -0
- package/bundle/scaffold/src/middleware.ts +61 -0
- package/bundle/scaffold/tailwind.config.ts +15 -0
- package/bundle/scaffold/tests/__dot__gitkeep +0 -0
- package/bundle/scaffold/tsconfig.json +23 -0
- package/bundle/scaffold/vitest.config.ts +13 -0
- package/bundle/toolchain/VERSION +1 -0
- package/bundle/toolchain/bin/check-slug.ts +59 -0
- package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
- package/bundle/toolchain/bin/create-portal.ts +71 -0
- package/bundle/toolchain/bin/delete-portal.ts +48 -0
- package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
- package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
- package/bundle/toolchain/bin/generate-share-link.ts +68 -0
- package/bundle/toolchain/bin/list-portals.ts +53 -0
- package/bundle/toolchain/bin/materialize-file.ts +35 -0
- package/bundle/toolchain/bin/query-analytics.ts +88 -0
- package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
- package/bundle/toolchain/bin/showpane-config +63 -0
- package/bundle/toolchain/bin/tsconfig.json +13 -0
- package/bundle/toolchain/skills/VERSION +1 -0
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
- package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
- package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
- package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
- package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
- package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
- package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
- package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
- package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
- package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
- package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
- package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
- package/bundle/toolchain/skills/shared/preamble.md +137 -0
- package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
- package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
- package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
- package/dist/index.js +875 -159
- 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'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] & [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
|
+
}
|