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,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're excited to have [Client Company] on board. This portal is
|
|
34
|
+
your central hub for getting started. Here you'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
|
+
}
|