@pylonsync/create-pylon 0.3.280 → 0.3.281
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/bin/create-pylon.js +6 -13
- package/package.json +1 -1
- package/templates/backend/chat/apps/api/schema.ts +1 -1
- package/templates/default/app/auth-form.tsx +36 -13
- package/templates/default/app/dashboard/billing/page.tsx +6 -6
- package/templates/default/app/dashboard/dashboard-client.tsx +4 -4
- package/templates/default/app/dashboard/members/page.tsx +5 -3
- package/templates/default/app/dashboard/page.tsx +9 -6
- package/templates/default/app/dashboard/projects/page.tsx +5 -3
- package/templates/default/app/dashboard/provision-workspace.tsx +74 -0
- package/templates/default/app/dashboard/settings/page.tsx +5 -3
- package/templates/default/app/layout.tsx +1 -1
- package/templates/default/components/dashboard-shell.tsx +1 -0
- package/templates/b2b/AGENTS.md +0 -61
- package/templates/b2b/README.md +0 -62
- package/templates/b2b/app/auth-form.tsx +0 -142
- package/templates/b2b/app/dashboard/dashboard-client.tsx +0 -192
- package/templates/b2b/app/dashboard/page.tsx +0 -63
- package/templates/b2b/app/error.tsx +0 -43
- package/templates/b2b/app/globals.css +0 -139
- package/templates/b2b/app/layout.tsx +0 -71
- package/templates/b2b/app/login/page.tsx +0 -47
- package/templates/b2b/app/not-found.tsx +0 -29
- package/templates/b2b/app/page.tsx +0 -114
- package/templates/b2b/app/robots.ts +0 -12
- package/templates/b2b/app/signup/page.tsx +0 -44
- package/templates/b2b/app/sitemap.ts +0 -27
- package/templates/b2b/app.ts +0 -179
- package/templates/b2b/components/ui/button.tsx +0 -56
- package/templates/b2b/components/ui/card.tsx +0 -90
- package/templates/b2b/components.json +0 -20
- package/templates/b2b/functions/_keep.ts +0 -13
- package/templates/b2b/gitignore +0 -10
- package/templates/b2b/lib/utils.ts +0 -10
- package/templates/b2b/package.json +0 -33
- package/templates/b2b/tsconfig.json +0 -18
- package/templates/default/app/onboarding/onboarding-client.tsx +0 -261
- package/templates/default/app/onboarding/page.tsx +0 -29
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useState } from "react";
|
|
4
|
-
import { db } from "@pylonsync/react";
|
|
5
|
-
import {
|
|
6
|
-
CreateOrganization,
|
|
7
|
-
InviteMembers,
|
|
8
|
-
useAuth,
|
|
9
|
-
type OrgSummary,
|
|
10
|
-
} from "@pylonsync/client";
|
|
11
|
-
import { Button } from "@/components/ui/button";
|
|
12
|
-
|
|
13
|
-
// First-run onboarding. Three steps, each built on a real @pylonsync/client
|
|
14
|
-
// primitive so the flow does actual work end-to-end (no mock screens):
|
|
15
|
-
// 1. Workspace → <CreateOrganization> (createOrg) + selectOrg to make it the
|
|
16
|
-
// active tenant.
|
|
17
|
-
// 2. Teammates → <InviteMembers> (POST /api/auth/orgs/:id/invites, gated to
|
|
18
|
-
// admins by the framework — the creator is the admin).
|
|
19
|
-
// 3. Project → db.insert("Project", { orgId }) — the same tenant-scoped,
|
|
20
|
-
// optimistic write the dashboard uses.
|
|
21
|
-
// The marketing nav/footer are suppressed for /onboarding in the root layout,
|
|
22
|
-
// so this owns the whole screen. The client components theme off --pylon-* vars,
|
|
23
|
-
// which we map to the template's zinc palette on the wrapper below so they blend.
|
|
24
|
-
const STEPS = ["Workspace", "Invite", "Project"] as const;
|
|
25
|
-
|
|
26
|
-
export function Onboarding({ email }: { email: string }) {
|
|
27
|
-
const { selectOrg } = useAuth();
|
|
28
|
-
const [step, setStep] = useState(0);
|
|
29
|
-
const [org, setOrg] = useState<OrgSummary | null>(null);
|
|
30
|
-
|
|
31
|
-
async function onOrgCreated(created: OrgSummary) {
|
|
32
|
-
// CreateOrganization creates the org but doesn't switch into it — make it
|
|
33
|
-
// the active tenant so the invite + project steps (and the dashboard after)
|
|
34
|
-
// operate on it.
|
|
35
|
-
await selectOrg(created.id);
|
|
36
|
-
setOrg(created);
|
|
37
|
-
setStep(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function finish() {
|
|
41
|
-
// Full navigation so the SSR dashboard re-renders with the now-active tenant
|
|
42
|
-
// resolved from the session cookie.
|
|
43
|
-
window.location.assign("/dashboard");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div
|
|
48
|
-
className="flex min-h-screen flex-col bg-zinc-50"
|
|
49
|
-
style={
|
|
50
|
-
{
|
|
51
|
-
"--pylon-ink": "#18181b",
|
|
52
|
-
"--pylon-ink-2": "#52525b",
|
|
53
|
-
"--pylon-ink-3": "#a1a1aa",
|
|
54
|
-
"--pylon-paper": "#ffffff",
|
|
55
|
-
"--pylon-paper-2": "#f4f4f5",
|
|
56
|
-
"--pylon-rule": "#e4e4e7",
|
|
57
|
-
} as React.CSSProperties
|
|
58
|
-
}
|
|
59
|
-
>
|
|
60
|
-
<header className="flex h-14 items-center justify-between px-6">
|
|
61
|
-
<a href="/" className="flex items-center gap-2">
|
|
62
|
-
<span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
|
|
63
|
-
A
|
|
64
|
-
</span>
|
|
65
|
-
<span className="text-[15px] font-semibold tracking-tight text-zinc-900">
|
|
66
|
-
Acme
|
|
67
|
-
</span>
|
|
68
|
-
</a>
|
|
69
|
-
<span className="truncate text-[13px] text-zinc-500">{email}</span>
|
|
70
|
-
</header>
|
|
71
|
-
|
|
72
|
-
<main className="flex flex-1 items-center justify-center px-6 py-10">
|
|
73
|
-
<div className="w-full max-w-md">
|
|
74
|
-
<Stepper current={step} />
|
|
75
|
-
|
|
76
|
-
<div className="mt-7 rounded-2xl border border-zinc-200 bg-white p-7 shadow-[0_24px_60px_-32px_rgba(0,0,0,0.25)]">
|
|
77
|
-
{step === 0 && (
|
|
78
|
-
<Step
|
|
79
|
-
eyebrow="Step 1 of 3"
|
|
80
|
-
title="Create your workspace"
|
|
81
|
-
subtitle="A workspace is an isolated tenant — its projects and members are private to it. You can make more later."
|
|
82
|
-
>
|
|
83
|
-
<CreateOrganization title="" onCreated={onOrgCreated} />
|
|
84
|
-
</Step>
|
|
85
|
-
)}
|
|
86
|
-
|
|
87
|
-
{step === 1 && org && (
|
|
88
|
-
<Step
|
|
89
|
-
eyebrow="Step 2 of 3"
|
|
90
|
-
title="Invite your team"
|
|
91
|
-
subtitle={`Add people to ${org.name}. They'll get an email invite — skip this if you're flying solo for now.`}
|
|
92
|
-
>
|
|
93
|
-
<InviteMembers orgId={org.id} hideMembers hidePending />
|
|
94
|
-
<StepNav onBack={() => setStep(0)} onNext={() => setStep(2)} />
|
|
95
|
-
</Step>
|
|
96
|
-
)}
|
|
97
|
-
|
|
98
|
-
{step === 2 && org && (
|
|
99
|
-
<Step
|
|
100
|
-
eyebrow="Step 3 of 3"
|
|
101
|
-
title="Create your first project"
|
|
102
|
-
subtitle="Projects hold your work. Add one to land in the dashboard with something already there."
|
|
103
|
-
>
|
|
104
|
-
<FirstProject orgId={org.id} onDone={finish} />
|
|
105
|
-
</Step>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
{step === 0 && (
|
|
110
|
-
<p className="mt-5 text-center text-[13px] text-zinc-400">
|
|
111
|
-
Wrong account?{" "}
|
|
112
|
-
<a href="/login" className="text-zinc-600 underline">
|
|
113
|
-
Switch
|
|
114
|
-
</a>
|
|
115
|
-
</p>
|
|
116
|
-
)}
|
|
117
|
-
</div>
|
|
118
|
-
</main>
|
|
119
|
-
</div>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function Stepper({ current }: { current: number }) {
|
|
124
|
-
return (
|
|
125
|
-
<ol className="flex items-center gap-2">
|
|
126
|
-
{STEPS.map((label, i) => {
|
|
127
|
-
const state =
|
|
128
|
-
i < current ? "done" : i === current ? "active" : "todo";
|
|
129
|
-
return (
|
|
130
|
-
<li key={label} className="flex flex-1 items-center gap-2">
|
|
131
|
-
<span
|
|
132
|
-
className={
|
|
133
|
-
"flex size-6 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold transition-colors " +
|
|
134
|
-
(state === "active"
|
|
135
|
-
? "bg-zinc-900 text-white"
|
|
136
|
-
: state === "done"
|
|
137
|
-
? "bg-brand text-white"
|
|
138
|
-
: "bg-zinc-200 text-zinc-500")
|
|
139
|
-
}
|
|
140
|
-
>
|
|
141
|
-
{state === "done" ? "✓" : i + 1}
|
|
142
|
-
</span>
|
|
143
|
-
<span
|
|
144
|
-
className={
|
|
145
|
-
"text-[12px] font-medium " +
|
|
146
|
-
(state === "todo" ? "text-zinc-400" : "text-zinc-700")
|
|
147
|
-
}
|
|
148
|
-
>
|
|
149
|
-
{label}
|
|
150
|
-
</span>
|
|
151
|
-
{i < STEPS.length - 1 && (
|
|
152
|
-
<span className="ml-1 h-px flex-1 bg-zinc-200" />
|
|
153
|
-
)}
|
|
154
|
-
</li>
|
|
155
|
-
);
|
|
156
|
-
})}
|
|
157
|
-
</ol>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function Step({
|
|
162
|
-
eyebrow,
|
|
163
|
-
title,
|
|
164
|
-
subtitle,
|
|
165
|
-
children,
|
|
166
|
-
}: {
|
|
167
|
-
eyebrow: string;
|
|
168
|
-
title: string;
|
|
169
|
-
subtitle: string;
|
|
170
|
-
children: React.ReactNode;
|
|
171
|
-
}) {
|
|
172
|
-
return (
|
|
173
|
-
<div>
|
|
174
|
-
<p className="text-[11px] font-semibold uppercase tracking-wide text-brand">
|
|
175
|
-
{eyebrow}
|
|
176
|
-
</p>
|
|
177
|
-
<h1 className="mt-1.5 text-xl font-semibold tracking-tight text-zinc-900">
|
|
178
|
-
{title}
|
|
179
|
-
</h1>
|
|
180
|
-
<p className="mt-1.5 text-[13.5px] leading-relaxed text-zinc-500">
|
|
181
|
-
{subtitle}
|
|
182
|
-
</p>
|
|
183
|
-
<div className="mt-5">{children}</div>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function StepNav({
|
|
189
|
-
onBack,
|
|
190
|
-
onNext,
|
|
191
|
-
}: {
|
|
192
|
-
onBack: () => void;
|
|
193
|
-
onNext: () => void;
|
|
194
|
-
}) {
|
|
195
|
-
return (
|
|
196
|
-
<div className="mt-5 flex items-center justify-between">
|
|
197
|
-
<button
|
|
198
|
-
type="button"
|
|
199
|
-
onClick={onBack}
|
|
200
|
-
className="text-[13px] font-medium text-zinc-500 transition-colors hover:text-zinc-900"
|
|
201
|
-
>
|
|
202
|
-
← Back
|
|
203
|
-
</button>
|
|
204
|
-
<Button type="button" size="sm" onClick={onNext}>
|
|
205
|
-
Continue
|
|
206
|
-
</Button>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function FirstProject({
|
|
212
|
-
orgId,
|
|
213
|
-
onDone,
|
|
214
|
-
}: {
|
|
215
|
-
orgId: string;
|
|
216
|
-
onDone: () => void;
|
|
217
|
-
}) {
|
|
218
|
-
const [name, setName] = useState("");
|
|
219
|
-
const [saving, setSaving] = useState(false);
|
|
220
|
-
|
|
221
|
-
async function create() {
|
|
222
|
-
const value = name.trim();
|
|
223
|
-
if (!value) return;
|
|
224
|
-
setSaving(true);
|
|
225
|
-
await db.insert("Project", { orgId, name: value });
|
|
226
|
-
onDone();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<div>
|
|
231
|
-
<input
|
|
232
|
-
value={name}
|
|
233
|
-
onChange={(e) => setName(e.target.value)}
|
|
234
|
-
onKeyDown={(e) => {
|
|
235
|
-
if (e.key === "Enter") void create();
|
|
236
|
-
}}
|
|
237
|
-
placeholder="e.g. Website redesign"
|
|
238
|
-
aria-label="Project name"
|
|
239
|
-
autoFocus
|
|
240
|
-
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-zinc-900 focus:ring-2 focus:ring-zinc-900/10"
|
|
241
|
-
/>
|
|
242
|
-
<div className="mt-5 flex items-center justify-between">
|
|
243
|
-
<button
|
|
244
|
-
type="button"
|
|
245
|
-
onClick={onDone}
|
|
246
|
-
className="text-[13px] font-medium text-zinc-500 transition-colors hover:text-zinc-900"
|
|
247
|
-
>
|
|
248
|
-
Skip for now
|
|
249
|
-
</button>
|
|
250
|
-
<Button
|
|
251
|
-
type="button"
|
|
252
|
-
size="sm"
|
|
253
|
-
onClick={create}
|
|
254
|
-
disabled={saving || !name.trim()}
|
|
255
|
-
>
|
|
256
|
-
{saving ? "…" : "Finish →"}
|
|
257
|
-
</Button>
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import React, { use } from "react";
|
|
2
|
-
import { type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
-
import { Onboarding } from "./onboarding-client";
|
|
4
|
-
|
|
5
|
-
export const metadata: Metadata = {
|
|
6
|
-
title: "Get started — Acme",
|
|
7
|
-
robots: "noindex",
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// `/onboarding` — first-run flow. Server-side gate: signed-out users go to
|
|
11
|
-
// /login; users who already have an active workspace (auth.tenant_id) skip
|
|
12
|
-
// straight to the dashboard, so this is only ever the brand-new-account screen.
|
|
13
|
-
// The signed-in email is resolved server-side (serverData.get) and passed in.
|
|
14
|
-
export default function OnboardingPage({
|
|
15
|
-
auth,
|
|
16
|
-
response,
|
|
17
|
-
serverData,
|
|
18
|
-
}: PageProps) {
|
|
19
|
-
if (!auth.user_id) {
|
|
20
|
-
response.redirect("/login");
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
if (auth.tenant_id) {
|
|
24
|
-
response.redirect("/dashboard");
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
|
|
28
|
-
return <Onboarding email={me?.email ?? ""} />;
|
|
29
|
-
}
|