@pylonsync/create-pylon 0.3.269 → 0.3.270

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 (74) hide show
  1. package/bin/create-pylon.js +11 -9
  2. package/package.json +1 -1
  3. package/templates/b2b/app/layout.tsx +1 -1
  4. package/templates/b2b/app/page.tsx +2 -2
  5. package/templates/b2b/tsconfig.json +1 -1
  6. package/templates/barebones/app/page.tsx +1 -1
  7. package/templates/barebones/tsconfig.json +1 -1
  8. package/templates/chat/app/page.tsx +1 -1
  9. package/templates/chat/tsconfig.json +1 -1
  10. package/templates/consumer/app/page.tsx +1 -1
  11. package/templates/consumer/tsconfig.json +1 -1
  12. package/templates/default/.env.example +19 -0
  13. package/templates/{ssr → default}/README.md +20 -6
  14. package/templates/default/app/auth-form.tsx +218 -0
  15. package/templates/default/app/auth-shell.tsx +76 -0
  16. package/templates/default/app/company/[slug]/page.tsx +28 -0
  17. package/templates/default/app/compare/[slug]/page.tsx +27 -0
  18. package/templates/default/app/dashboard/billing/page.tsx +49 -0
  19. package/templates/default/app/dashboard/dashboard-client.tsx +832 -0
  20. package/templates/default/app/dashboard/members/page.tsx +37 -0
  21. package/templates/default/app/dashboard/page.tsx +64 -0
  22. package/templates/default/app/dashboard/projects/page.tsx +37 -0
  23. package/templates/default/app/dashboard/settings/page.tsx +45 -0
  24. package/templates/{ssr → default}/app/globals.css +14 -0
  25. package/templates/default/app/layout.tsx +466 -0
  26. package/templates/default/app/login/page.tsx +27 -0
  27. package/templates/default/app/onboarding/onboarding-client.tsx +261 -0
  28. package/templates/default/app/onboarding/page.tsx +29 -0
  29. package/templates/default/app/page.tsx +653 -0
  30. package/templates/default/app/products/[slug]/page.tsx +134 -0
  31. package/templates/default/app/resources/[slug]/page.tsx +28 -0
  32. package/templates/default/app/signup/page.tsx +24 -0
  33. package/templates/default/app/sitemap.ts +40 -0
  34. package/templates/default/app/solutions/[slug]/page.tsx +28 -0
  35. package/templates/{ssr → default}/app.ts +17 -2
  36. package/templates/default/components/dashboard-shell.tsx +150 -0
  37. package/templates/default/components/marketing.tsx +370 -0
  38. package/templates/default/functions/_pylonStripeFindActiveSubForReference.ts +3 -0
  39. package/templates/default/functions/_pylonStripeFindByCustomerId.ts +3 -0
  40. package/templates/default/functions/_pylonStripeGetCustomerHolder.ts +3 -0
  41. package/templates/default/functions/_pylonStripeListSubsForReference.ts +3 -0
  42. package/templates/default/functions/_pylonStripeOrgMembership.ts +3 -0
  43. package/templates/default/functions/_pylonStripeSetCustomerId.ts +3 -0
  44. package/templates/default/functions/_pylonStripeUpsertSubscription.ts +3 -0
  45. package/templates/default/functions/cancelSubscription.ts +3 -0
  46. package/templates/default/functions/createBillingPortalSession.ts +3 -0
  47. package/templates/default/functions/createCheckoutSession.ts +3 -0
  48. package/templates/default/functions/restoreSubscription.ts +3 -0
  49. package/templates/default/functions/stripeWebhook.ts +3 -0
  50. package/templates/default/lib/billing.ts +46 -0
  51. package/templates/default/lib/products.ts +122 -0
  52. package/templates/default/lib/site.ts +261 -0
  53. package/templates/{ssr → default}/package.json +2 -0
  54. package/templates/{ssr → default}/tsconfig.json +2 -2
  55. package/templates/todo/app/page.tsx +1 -1
  56. package/templates/todo/tsconfig.json +1 -1
  57. package/templates/ssr/app/auth-form.tsx +0 -142
  58. package/templates/ssr/app/dashboard/dashboard-client.tsx +0 -192
  59. package/templates/ssr/app/dashboard/page.tsx +0 -26
  60. package/templates/ssr/app/layout.tsx +0 -78
  61. package/templates/ssr/app/login/page.tsx +0 -47
  62. package/templates/ssr/app/page.tsx +0 -212
  63. package/templates/ssr/app/signup/page.tsx +0 -44
  64. package/templates/ssr/app/sitemap.ts +0 -27
  65. package/templates/ssr/functions/_keep.ts +0 -13
  66. /package/templates/{ssr → default}/AGENTS.md +0 -0
  67. /package/templates/{ssr → default}/app/error.tsx +0 -0
  68. /package/templates/{ssr → default}/app/not-found.tsx +0 -0
  69. /package/templates/{ssr → default}/app/robots.ts +0 -0
  70. /package/templates/{ssr → default}/components/ui/button.tsx +0 -0
  71. /package/templates/{ssr → default}/components/ui/card.tsx +0 -0
  72. /package/templates/{ssr → default}/components.json +0 -0
  73. /package/templates/{ssr → default}/gitignore +0 -0
  74. /package/templates/{ssr → default}/lib/utils.ts +0 -0
@@ -1,78 +0,0 @@
1
- import React from "react";
2
- import { Link, type PageAuth } from "@pylonsync/react";
3
- import { Button } from "@/components/ui/button";
4
-
5
- // A layout receives the page props plus `children`. `auth.user_id` is null for
6
- // anonymous visitors and the signed-in user's id otherwise — resolved
7
- // server-side from the session cookie before any HTML is sent, so the nav
8
- // renders the right links on the first byte (no flash, no client fetch).
9
- interface LayoutProps {
10
- children: React.ReactNode;
11
- url: string;
12
- auth: PageAuth;
13
- }
14
-
15
- // The root layout wraps every page: a marketing nav up top, a footer below.
16
- // Rebrand "Acme" to your product.
17
- export default function RootLayout({ children, auth }: LayoutProps) {
18
- const signedIn = Boolean(auth?.user_id);
19
- return (
20
- <html lang="en">
21
- <head>
22
- <meta charSet="utf-8" />
23
- <meta name="viewport" content="width=device-width, initial-scale=1" />
24
- <title>Acme</title>
25
- {/* Tailwind is compiled by Pylon from app/globals.css and the
26
- stylesheet link is injected here automatically. */}
27
- </head>
28
- <body className="flex min-h-screen flex-col bg-background text-foreground antialiased">
29
- <header className="sticky top-0 z-20 border-b bg-background/70 backdrop-blur">
30
- <div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
31
- <Link href="/" className="flex items-center gap-2">
32
- <span className="flex size-7 items-center justify-center rounded-md bg-primary text-sm font-bold text-primary-foreground">
33
- A
34
- </span>
35
- <span className="text-sm font-semibold tracking-tight">Acme</span>
36
- </Link>
37
- <nav className="flex items-center gap-2 text-sm">
38
- {signedIn ? (
39
- <Button asChild size="sm">
40
- <Link href="/dashboard">Dashboard</Link>
41
- </Button>
42
- ) : (
43
- <>
44
- <Button asChild size="sm" variant="ghost">
45
- <Link href="/login">Sign in</Link>
46
- </Button>
47
- <Button asChild size="sm">
48
- <Link href="/signup">Get started</Link>
49
- </Button>
50
- </>
51
- )}
52
- </nav>
53
- </div>
54
- </header>
55
-
56
- <main className="mx-auto w-full max-w-5xl flex-1 px-4 py-10">
57
- {children}
58
- </main>
59
-
60
- <footer className="border-t">
61
- <div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-2 px-4 py-6 text-xs text-muted-foreground sm:flex-row">
62
- <span>© Acme, Inc.</span>
63
- <span>
64
- Built with{" "}
65
- <a
66
- href="https://pylonsync.com"
67
- className="font-medium text-foreground hover:underline"
68
- >
69
- Pylon
70
- </a>{" "}
71
- · one binary, one port
72
- </span>
73
- </div>
74
- </footer>
75
- </body>
76
- </html>
77
- );
78
- }
@@ -1,47 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import {
4
- Card,
5
- CardContent,
6
- CardDescription,
7
- CardHeader,
8
- CardTitle,
9
- } from "@/components/ui/card";
10
- import { AuthForm } from "../auth-form";
11
-
12
- export const metadata: Metadata = {
13
- title: "Sign in — Acme",
14
- // Auth pages shouldn't be indexed.
15
- robots: "noindex",
16
- };
17
-
18
- // `app/login/page.tsx` → `/login`. A server-rendered shell around the
19
- // client-side <AuthForm> island.
20
- export default function LoginPage({ auth, response }: PageProps) {
21
- // Already signed in? Skip the form. `response.redirect` runs in the
22
- // synchronous shell render, so it's a real 307 before any HTML is sent
23
- // (no flash, works with JS disabled).
24
- if (auth.user_id) response.redirect("/dashboard");
25
- return (
26
- <div className="mx-auto max-w-sm">
27
- <Card>
28
- <CardHeader>
29
- <CardTitle>Sign in</CardTitle>
30
- <CardDescription>Welcome back.</CardDescription>
31
- </CardHeader>
32
- <CardContent className="space-y-4">
33
- <AuthForm mode="login" />
34
- <p className="text-center text-sm text-muted-foreground">
35
- No account?{" "}
36
- <Link
37
- href="/signup"
38
- className="font-medium text-foreground hover:underline"
39
- >
40
- Create one
41
- </Link>
42
- </p>
43
- </CardContent>
44
- </Card>
45
- </div>
46
- );
47
- }
@@ -1,212 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import { Button } from "@/components/ui/button";
4
-
5
- // SEO metadata. Exported `metadata` is rendered into <head> on the server, so
6
- // this marketing page is fully indexable — view source and the copy is in the
7
- // HTML. Swap "Acme" for your product throughout.
8
- export const metadata: Metadata = {
9
- title: "Acme — the workspace your team actually wants",
10
- description:
11
- "Acme is a collaborative workspace for fast-moving teams. Organize projects by team, collaborate in real time, and keep every tenant's data private. Built on Pylon.",
12
- };
13
-
14
- // `app/page.tsx` → `/`. A server-rendered marketing landing page. It reads
15
- // `auth` (resolved from the session cookie during the render) so the call to
16
- // action is right on the first byte — "Get started" for visitors, "Open
17
- // dashboard" once you're signed in. No client fetch, no flash.
18
- export default function LandingPage({ auth }: PageProps) {
19
- const signedIn = Boolean(auth.user_id);
20
-
21
- return (
22
- <div className="space-y-24 pb-24">
23
- {/* Hero */}
24
- <section className="relative overflow-hidden">
25
- {/* Soft gradient wash behind the hero. */}
26
- <div
27
- aria-hidden
28
- className="pointer-events-none absolute -top-40 left-1/2 h-[32rem] w-[60rem] -translate-x-1/2 rounded-full bg-gradient-to-tr from-primary/15 via-primary/5 to-transparent blur-3xl"
29
- />
30
- <div className="relative mx-auto max-w-3xl px-2 pt-16 text-center sm:pt-24">
31
- <span className="inline-flex items-center gap-2 rounded-full border bg-background/60 px-3 py-1 text-xs font-medium text-muted-foreground backdrop-blur">
32
- <span className="size-1.5 rounded-full bg-emerald-500" />
33
- New · Real-time projects for every team
34
- </span>
35
- <h1 className="mt-6 text-balance text-5xl font-semibold tracking-tight sm:text-6xl">
36
- The workspace your team{" "}
37
- <span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
38
- actually wants
39
- </span>
40
- .
41
- </h1>
42
- <p className="mx-auto mt-5 max-w-xl text-balance text-lg text-muted-foreground">
43
- Acme keeps your team&apos;s projects organized, in sync, and private
44
- to your organization. Invite your teammates, switch between orgs, and
45
- watch every change land live.
46
- </p>
47
- <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
48
- {signedIn ? (
49
- <Button asChild size="lg">
50
- <Link href="/dashboard">Open dashboard →</Link>
51
- </Button>
52
- ) : (
53
- <>
54
- <Button asChild size="lg">
55
- <Link href="/signup">Get started — it&apos;s free</Link>
56
- </Button>
57
- <Button asChild size="lg" variant="outline">
58
- <Link href="/login">Sign in</Link>
59
- </Button>
60
- </>
61
- )}
62
- </div>
63
- <p className="mt-4 text-xs text-muted-foreground">
64
- No credit card required · Set up your first org in seconds.
65
- </p>
66
- </div>
67
-
68
- {/* Product peek — a stylized dashboard mock so the hero has a subject. */}
69
- <div className="relative mx-auto mt-14 max-w-4xl px-4">
70
- <div className="rounded-xl border bg-card shadow-2xl shadow-primary/5">
71
- <div className="flex items-center gap-1.5 border-b px-4 py-3">
72
- <span className="size-3 rounded-full bg-red-400/70" />
73
- <span className="size-3 rounded-full bg-yellow-400/70" />
74
- <span className="size-3 rounded-full bg-green-400/70" />
75
- <span className="ml-3 text-xs text-muted-foreground">
76
- acme.app/dashboard
77
- </span>
78
- </div>
79
- <div className="grid gap-4 p-5 sm:grid-cols-[1fr_1.4fr]">
80
- <div className="space-y-2">
81
- <div className="text-xs font-medium text-muted-foreground">
82
- Organization
83
- </div>
84
- <div className="flex items-center gap-2 rounded-md border px-3 py-2 text-sm">
85
- <span className="flex size-6 items-center justify-center rounded bg-primary/10 text-xs font-semibold text-primary">
86
- A
87
- </span>
88
- Acme Inc
89
- </div>
90
- <div className="pt-2 text-xs font-medium text-muted-foreground">
91
- Members
92
- </div>
93
- {["you · owner", "jordan · admin", "sam · member"].map((m) => (
94
- <div
95
- key={m}
96
- className="rounded-md border px-3 py-1.5 font-mono text-xs text-muted-foreground"
97
- >
98
- {m}
99
- </div>
100
- ))}
101
- </div>
102
- <div className="space-y-2">
103
- <div className="text-xs font-medium text-muted-foreground">
104
- Projects
105
- </div>
106
- {["Website redesign", "Mobile app", "Q3 launch"].map((p, i) => (
107
- <div
108
- key={p}
109
- className="flex items-center justify-between rounded-md border px-3 py-2 text-sm"
110
- >
111
- {p}
112
- {i === 0 && (
113
- <span className="rounded bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-medium text-emerald-600">
114
- live
115
- </span>
116
- )}
117
- </div>
118
- ))}
119
- <div className="rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground">
120
- + New project
121
- </div>
122
- </div>
123
- </div>
124
- </div>
125
- </div>
126
- </section>
127
-
128
- {/* Logos / social proof */}
129
- <section className="mx-auto max-w-3xl px-4 text-center">
130
- <p className="text-xs font-medium uppercase tracking-widest text-muted-foreground">
131
- Trusted by teams at
132
- </p>
133
- <div className="mt-4 flex flex-wrap items-center justify-center gap-x-10 gap-y-3 text-lg font-semibold text-muted-foreground/60">
134
- <span>Globex</span>
135
- <span>Initech</span>
136
- <span>Hooli</span>
137
- <span>Soylent</span>
138
- <span>Stark</span>
139
- </div>
140
- </section>
141
-
142
- {/* Features */}
143
- <section className="mx-auto max-w-4xl px-4">
144
- <div className="mx-auto max-w-2xl text-center">
145
- <h2 className="text-3xl font-semibold tracking-tight">
146
- Everything a growing team needs
147
- </h2>
148
- <p className="mt-3 text-muted-foreground">
149
- Acme is multi-tenant from day one — every org is an isolated,
150
- real-time workspace.
151
- </p>
152
- </div>
153
- <div className="mt-12 grid gap-6 sm:grid-cols-3">
154
- <Feature title="Organize by org" icon="▦">
155
- Spin up an organization, invite your team, and switch between them in
156
- a click. Each org&apos;s projects and members are completely private.
157
- </Feature>
158
- <Feature title="Real-time by default" icon="✦">
159
- Changes sync instantly across everyone&apos;s screens — no refresh,
160
- no stale data. Open two tabs and watch it happen.
161
- </Feature>
162
- <Feature title="Secure tenant isolation" icon="◆">
163
- Access is enforced at the data layer. A member of one org can&apos;t
164
- read or write another&apos;s rows — not by convention, by policy.
165
- </Feature>
166
- </div>
167
- </section>
168
-
169
- {/* CTA */}
170
- <section className="mx-auto max-w-3xl px-4">
171
- <div className="rounded-2xl border bg-gradient-to-br from-primary/10 to-transparent px-8 py-12 text-center">
172
- <h2 className="text-3xl font-semibold tracking-tight">
173
- Ready to get your team in sync?
174
- </h2>
175
- <p className="mx-auto mt-3 max-w-md text-muted-foreground">
176
- Create your organization and invite your first teammate in under a
177
- minute.
178
- </p>
179
- <div className="mt-6">
180
- <Button asChild size="lg">
181
- <Link href={signedIn ? "/dashboard" : "/signup"}>
182
- {signedIn ? "Open dashboard →" : "Start for free →"}
183
- </Link>
184
- </Button>
185
- </div>
186
- </div>
187
- </section>
188
- </div>
189
- );
190
- }
191
-
192
- function Feature({
193
- title,
194
- icon,
195
- children,
196
- }: {
197
- title: string;
198
- icon: string;
199
- children: React.ReactNode;
200
- }) {
201
- return (
202
- <div className="rounded-xl border bg-card p-5">
203
- <div className="flex size-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
204
- {icon}
205
- </div>
206
- <h3 className="mt-4 text-base font-semibold tracking-tight">{title}</h3>
207
- <p className="mt-1.5 text-sm leading-relaxed text-muted-foreground">
208
- {children}
209
- </p>
210
- </div>
211
- );
212
- }
@@ -1,44 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import {
4
- Card,
5
- CardContent,
6
- CardDescription,
7
- CardHeader,
8
- CardTitle,
9
- } from "@/components/ui/card";
10
- import { AuthForm } from "../auth-form";
11
-
12
- export const metadata: Metadata = {
13
- title: "Create your account — Acme",
14
- robots: "noindex",
15
- };
16
-
17
- // `app/signup/page.tsx` → `/signup`. Same shell as /login, register mode.
18
- export default function SignupPage({ auth, response }: PageProps) {
19
- if (auth.user_id) response.redirect("/dashboard");
20
- return (
21
- <div className="mx-auto max-w-sm">
22
- <Card>
23
- <CardHeader>
24
- <CardTitle>Create your account</CardTitle>
25
- <CardDescription>
26
- Email + password. No credit card, no email verification step in dev.
27
- </CardDescription>
28
- </CardHeader>
29
- <CardContent className="space-y-4">
30
- <AuthForm mode="signup" />
31
- <p className="text-center text-sm text-muted-foreground">
32
- Already have an account?{" "}
33
- <Link
34
- href="/login"
35
- className="font-medium text-foreground hover:underline"
36
- >
37
- Sign in
38
- </Link>
39
- </p>
40
- </CardContent>
41
- </Card>
42
- </div>
43
- );
44
- }
@@ -1,27 +0,0 @@
1
- import type { Sitemap } from "@pylonsync/react";
2
-
3
- // app/sitemap.ts → served at /sitemap.xml. The default export can be async, so
4
- // it can enumerate dynamic pages from your database. Point SITE_URL at your
5
- // domain in production.
6
- const SITE = process.env.SITE_URL ?? "http://localhost:4321";
7
-
8
- export default async function sitemap(): Promise<Sitemap> {
9
- // Only public pages belong here — /dashboard is private (and noindex), so
10
- // it's intentionally left out.
11
- const staticRoutes: Sitemap = [
12
- { url: `${SITE}/`, changeFrequency: "weekly", priority: 1 },
13
- { url: `${SITE}/login`, changeFrequency: "yearly", priority: 0.3 },
14
- { url: `${SITE}/signup`, changeFrequency: "yearly", priority: 0.5 },
15
- ];
16
-
17
- // The export is async, so you can enumerate dynamic pages from a DB read:
18
- //
19
- // const posts = await fetchPublishedPosts();
20
- // const postRoutes: Sitemap = posts.map((p) => ({
21
- // url: `${SITE}/blog/${p.slug}`,
22
- // lastModified: p.updatedAt,
23
- // }));
24
- // return [...staticRoutes, ...postRoutes];
25
-
26
- return staticRoutes;
27
- }
@@ -1,13 +0,0 @@
1
- // Server functions go here. Each file in this directory that exports a
2
- // query() or action() becomes a typed RPC endpoint, callable from your
3
- // pages and client with full type inference. Delete this placeholder when
4
- // you add your first one.
5
- //
6
- // Example (functions/notes.ts):
7
- //
8
- // import { query } from "@pylonsync/functions";
9
- //
10
- // export const listNotes = query(async (ctx) => {
11
- // return ctx.db.list("Note");
12
- // });
13
- export {};
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes