@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,114 +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
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardContent,
|
|
7
|
-
CardDescription,
|
|
8
|
-
CardHeader,
|
|
9
|
-
CardTitle,
|
|
10
|
-
} from "@/components/ui/card";
|
|
11
|
-
|
|
12
|
-
// SEO metadata. Export `metadata` (static) or `generateMetadata(props)`
|
|
13
|
-
// (dynamic) from any page or layout — Pylon renders the <title>/<meta>
|
|
14
|
-
// into <head> server-side. The `Metadata` type is exported from
|
|
15
|
-
// @pylonsync/react.
|
|
16
|
-
export const metadata: Metadata = {
|
|
17
|
-
title: "__APP_NAME__ — full-stack Pylon app",
|
|
18
|
-
description:
|
|
19
|
-
"A server-rendered homepage, email/password auth, and a live client dashboard over one synced backend.",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// `app/page.tsx` → `/`. This page is server-rendered: view source and the copy
|
|
23
|
-
// is in the HTML, not fetched later — good for SEO and first paint. It reads
|
|
24
|
-
// `auth` (resolved from the session cookie during the render) to show the
|
|
25
|
-
// right call to action. Every page receives `PageProps` from the SSR runtime:
|
|
26
|
-
// `{ url, params, searchParams, auth, response, serverData }` — typed, no
|
|
27
|
-
// hand-rolled interface.
|
|
28
|
-
export default function IndexPage({ auth }: PageProps) {
|
|
29
|
-
const signedIn = Boolean(auth.user_id);
|
|
30
|
-
return (
|
|
31
|
-
<div className="space-y-12">
|
|
32
|
-
<section className="space-y-5">
|
|
33
|
-
<span className="inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium text-muted-foreground">
|
|
34
|
-
Server-rendered · authenticated · synced
|
|
35
|
-
</span>
|
|
36
|
-
<h1 className="text-4xl font-semibold tracking-tight">
|
|
37
|
-
Full-stack apps, one binary.
|
|
38
|
-
</h1>
|
|
39
|
-
<p className="max-w-xl text-lg text-muted-foreground">
|
|
40
|
-
This homepage is server-rendered React. Sign in and your dashboard
|
|
41
|
-
becomes a live, local-first view over the same Pylon backend — writes
|
|
42
|
-
appear instantly and sync across tabs. No Next.js, no separate API
|
|
43
|
-
server, no realtime sidecar.
|
|
44
|
-
</p>
|
|
45
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
46
|
-
{signedIn ? (
|
|
47
|
-
<Button asChild>
|
|
48
|
-
<Link href="/dashboard">Go to your dashboard →</Link>
|
|
49
|
-
</Button>
|
|
50
|
-
) : (
|
|
51
|
-
<>
|
|
52
|
-
<Button asChild>
|
|
53
|
-
<Link href="/signup">Get started</Link>
|
|
54
|
-
</Button>
|
|
55
|
-
<Button asChild variant="outline">
|
|
56
|
-
<Link href="/login">Sign in</Link>
|
|
57
|
-
</Button>
|
|
58
|
-
</>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
</section>
|
|
62
|
-
|
|
63
|
-
<section className="grid gap-4 sm:grid-cols-3">
|
|
64
|
-
<Feature title="Server-rendered">
|
|
65
|
-
File-based routes under <Code>app/</Code>. Pages render to HTML on the
|
|
66
|
-
server with <Code>metadata</Code> in <Code>{"<head>"}</Code>, then
|
|
67
|
-
hydrate. Drop <Code>app/about/page.tsx</Code> to add{" "}
|
|
68
|
-
<Code>/about</Code>.
|
|
69
|
-
</Feature>
|
|
70
|
-
<Feature title="Auth included">
|
|
71
|
-
Email/password is built in. <Code>/login</Code> and{" "}
|
|
72
|
-
<Code>/signup</Code> hit <Code>/api/auth/password/*</Code>; the server
|
|
73
|
-
sets an HttpOnly session cookie. <Code>/dashboard</Code> gates on it
|
|
74
|
-
server-side.
|
|
75
|
-
</Feature>
|
|
76
|
-
<Feature title="Synced database">
|
|
77
|
-
Every <Code>entity()</Code> in <Code>app.ts</Code> gets a REST +
|
|
78
|
-
realtime API and a typed client. <Code>db.useQuery</Code> is live;{" "}
|
|
79
|
-
<Code>db.insert</Code> is optimistic.
|
|
80
|
-
</Feature>
|
|
81
|
-
</section>
|
|
82
|
-
|
|
83
|
-
<p className="text-xs text-muted-foreground">
|
|
84
|
-
Edit <Code>app/page.tsx</Code> and save — the page reloads instantly.
|
|
85
|
-
The data model and access policies live in <Code>app.ts</Code>.
|
|
86
|
-
</p>
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function Feature({
|
|
92
|
-
title,
|
|
93
|
-
children,
|
|
94
|
-
}: {
|
|
95
|
-
title: string;
|
|
96
|
-
children: React.ReactNode;
|
|
97
|
-
}) {
|
|
98
|
-
return (
|
|
99
|
-
<Card>
|
|
100
|
-
<CardHeader>
|
|
101
|
-
<CardTitle className="text-base">{title}</CardTitle>
|
|
102
|
-
</CardHeader>
|
|
103
|
-
<CardContent>
|
|
104
|
-
<CardDescription className="text-sm leading-relaxed">
|
|
105
|
-
{children}
|
|
106
|
-
</CardDescription>
|
|
107
|
-
</CardContent>
|
|
108
|
-
</Card>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function Code({ children }: { children: React.ReactNode }) {
|
|
113
|
-
return <code className="rounded bg-muted px-1 text-xs">{children}</code>;
|
|
114
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Robots } from "@pylonsync/react";
|
|
2
|
-
|
|
3
|
-
// app/robots.ts → served at /robots.txt. The default export may also be async.
|
|
4
|
-
const SITE = process.env.SITE_URL ?? "http://localhost:4321";
|
|
5
|
-
|
|
6
|
-
export default function robots(): Robots {
|
|
7
|
-
return {
|
|
8
|
-
// Keep the authenticated app and the API out of the index.
|
|
9
|
-
rules: { userAgent: "*", allow: "/", disallow: ["/dashboard", "/api/"] },
|
|
10
|
-
sitemap: `${SITE}/sitemap.xml`,
|
|
11
|
-
};
|
|
12
|
-
}
|
|
@@ -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 — __APP_NAME__",
|
|
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
|
-
}
|
package/templates/b2b/app.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
entity,
|
|
3
|
-
field,
|
|
4
|
-
policy,
|
|
5
|
-
auth,
|
|
6
|
-
buildManifest,
|
|
7
|
-
discoverAppRoutes,
|
|
8
|
-
} from "@pylonsync/sdk";
|
|
9
|
-
|
|
10
|
-
// Accounts — email/password is built in (the entity named "User" is the
|
|
11
|
-
// account table; passwordHash is server-only). Each user can belong to many
|
|
12
|
-
// organizations.
|
|
13
|
-
const User = entity(
|
|
14
|
-
"User",
|
|
15
|
-
{
|
|
16
|
-
email: field.string(),
|
|
17
|
-
displayName: field.string().optional(),
|
|
18
|
-
passwordHash: field.string().serverOnly().optional(),
|
|
19
|
-
// The framework's /api/auth/password/register stamps a generated avatar
|
|
20
|
-
// color here, so the User entity must declare it.
|
|
21
|
-
avatarColor: field.string().optional(),
|
|
22
|
-
createdAt: field.datetime().defaultNow(),
|
|
23
|
-
},
|
|
24
|
-
{ indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Organizations — multi-tenancy is a framework primitive. Declaring these
|
|
29
|
-
// three entities with the names + fields below lights up the built-in
|
|
30
|
-
// `/api/auth/orgs/*` routes (create/list orgs, members, invites) and
|
|
31
|
-
// `/api/auth/select-org` (switch your active tenant). The framework writes
|
|
32
|
-
// only the fields it manages; add your own (logo, plan, billingEmail…) freely.
|
|
33
|
-
// The `@pylonsync/client` `<OrganizationSwitcher>` drives all of this for you.
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
const Org = entity(
|
|
36
|
-
"Org",
|
|
37
|
-
{
|
|
38
|
-
name: field.string(),
|
|
39
|
-
createdBy: field.id("User"),
|
|
40
|
-
createdAt: field.datetime(),
|
|
41
|
-
},
|
|
42
|
-
{ indexes: [{ name: "by_created_by", fields: ["createdBy"], unique: false }] },
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
// User ↔ Org edge with a role. `select-org` checks this table before letting
|
|
46
|
-
// you switch tenants, so a client can't impersonate an org it doesn't belong
|
|
47
|
-
// to. role ∈ "owner" | "admin" | "member".
|
|
48
|
-
const OrgMember = entity(
|
|
49
|
-
"OrgMember",
|
|
50
|
-
{
|
|
51
|
-
orgId: field.id("Org"),
|
|
52
|
-
userId: field.id("User"),
|
|
53
|
-
role: field.string(),
|
|
54
|
-
joinedAt: field.datetime(),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
indexes: [
|
|
58
|
-
{ name: "by_org_user", fields: ["orgId", "userId"], unique: true },
|
|
59
|
-
{ name: "by_user", fields: ["userId"], unique: false },
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Pending invite. The framework's /api/auth/orgs/:id/invites endpoints write
|
|
65
|
-
// these (tokenHash is server-only — the raw token only ever goes to the
|
|
66
|
-
// invitee). accepted* are filled in when the invite is redeemed.
|
|
67
|
-
const OrgInvite = entity(
|
|
68
|
-
"OrgInvite",
|
|
69
|
-
{
|
|
70
|
-
orgId: field.id("Org"),
|
|
71
|
-
email: field.string(),
|
|
72
|
-
role: field.string(),
|
|
73
|
-
invitedBy: field.id("User"),
|
|
74
|
-
tokenHash: field.string().serverOnly(),
|
|
75
|
-
tokenPrefix: field.string(),
|
|
76
|
-
createdAt: field.datetime(),
|
|
77
|
-
expiresAt: field.datetime(),
|
|
78
|
-
acceptedAt: field.datetime().optional(),
|
|
79
|
-
acceptedByUserId: field.id("User").optional(),
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
indexes: [
|
|
83
|
-
{ name: "by_org", fields: ["orgId"], unique: false },
|
|
84
|
-
{ name: "by_email_org", fields: ["email", "orgId"], unique: false },
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Your app's data — one tenant-scoped resource. `orgId` carries the tenant,
|
|
91
|
-
// and the policy scopes every read AND write to your ACTIVE org
|
|
92
|
-
// (`auth.tenantId`, set by select-org). Switch orgs in the UI and the project
|
|
93
|
-
// list changes — clients literally cannot read or write another tenant's rows.
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
const Project = entity(
|
|
96
|
-
"Project",
|
|
97
|
-
{
|
|
98
|
-
orgId: field.id("Org"),
|
|
99
|
-
name: field.string(),
|
|
100
|
-
createdAt: field.datetime().defaultNow(),
|
|
101
|
-
},
|
|
102
|
-
{ indexes: [{ name: "by_org", fields: ["orgId"], unique: false }] },
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// User rows: read your own; the auth subsystem owns writes.
|
|
106
|
-
const userPolicy = policy({
|
|
107
|
-
name: "user_self",
|
|
108
|
-
entity: "User",
|
|
109
|
-
allowRead: "auth.userId == data.id",
|
|
110
|
-
allowInsert: "false",
|
|
111
|
-
allowUpdate: "false",
|
|
112
|
-
allowDelete: "false",
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Org / OrgMember / OrgInvite are managed by the framework's /api/auth/orgs
|
|
116
|
-
// routes (which bypass these policies via the OrgStore). Clients reach them
|
|
117
|
-
// through the `@pylonsync/client` org helpers, not the entity API — so deny
|
|
118
|
-
// direct writes, and scope reads to your own membership / active org.
|
|
119
|
-
const orgPolicy = policy({
|
|
120
|
-
name: "org_access",
|
|
121
|
-
entity: "Org",
|
|
122
|
-
allowRead: "auth.tenantId == data.id",
|
|
123
|
-
allowInsert: "false",
|
|
124
|
-
allowUpdate: "false",
|
|
125
|
-
allowDelete: "false",
|
|
126
|
-
});
|
|
127
|
-
const orgMemberPolicy = policy({
|
|
128
|
-
name: "org_member_access",
|
|
129
|
-
entity: "OrgMember",
|
|
130
|
-
allowRead: "auth.userId == data.userId || auth.tenantId == data.orgId",
|
|
131
|
-
allowInsert: "false",
|
|
132
|
-
allowUpdate: "false",
|
|
133
|
-
allowDelete: "false",
|
|
134
|
-
});
|
|
135
|
-
const orgInvitePolicy = policy({
|
|
136
|
-
name: "org_invite_access",
|
|
137
|
-
entity: "OrgInvite",
|
|
138
|
-
allowRead: "auth.tenantId == data.orgId",
|
|
139
|
-
allowInsert: "false",
|
|
140
|
-
allowUpdate: "false",
|
|
141
|
-
allowDelete: "false",
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Projects are scoped to your ACTIVE tenant. `auth.tenantId == data.orgId`
|
|
145
|
-
// gates read AND write — and because orgId is client-supplied at insert time
|
|
146
|
-
// (not stamped later), checking it here means you can only create a project in
|
|
147
|
-
// the org you've selected. Switch orgs → a different project list.
|
|
148
|
-
const projectPolicy = policy({
|
|
149
|
-
name: "project_tenant",
|
|
150
|
-
entity: "Project",
|
|
151
|
-
allowRead: "auth.tenantId == data.orgId",
|
|
152
|
-
allowInsert: "auth.tenantId == data.orgId",
|
|
153
|
-
allowUpdate: "auth.tenantId == data.orgId",
|
|
154
|
-
allowDelete: "auth.tenantId == data.orgId",
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const manifest = buildManifest({
|
|
158
|
-
name: "__APP_NAME__",
|
|
159
|
-
version: "0.1.0",
|
|
160
|
-
entities: [User, Org, OrgMember, OrgInvite, Project],
|
|
161
|
-
queries: [],
|
|
162
|
-
actions: [],
|
|
163
|
-
policies: [
|
|
164
|
-
userPolicy,
|
|
165
|
-
orgPolicy,
|
|
166
|
-
orgMemberPolicy,
|
|
167
|
-
orgInvitePolicy,
|
|
168
|
-
projectPolicy,
|
|
169
|
-
],
|
|
170
|
-
// Email/password is on by default against the User entity. The org entities
|
|
171
|
-
// above are named with the framework defaults (Org / OrgMember / OrgInvite),
|
|
172
|
-
// so `/api/auth/orgs/*` + `/api/auth/select-org` work with no extra config.
|
|
173
|
-
auth: auth(),
|
|
174
|
-
routes: await discoverAppRoutes(),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
console.log(JSON.stringify(manifest, null, 2));
|
|
178
|
-
|
|
179
|
-
export default manifest;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
13
|
-
destructive:
|
|
14
|
-
"bg-destructive text-white hover:bg-destructive/90",
|
|
15
|
-
outline:
|
|
16
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
17
|
-
secondary:
|
|
18
|
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
20
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
21
|
-
},
|
|
22
|
-
size: {
|
|
23
|
-
default: "h-9 px-4 py-2",
|
|
24
|
-
sm: "h-8 rounded-md px-3 text-xs",
|
|
25
|
-
lg: "h-10 rounded-md px-8",
|
|
26
|
-
icon: "h-9 w-9",
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
defaultVariants: {
|
|
30
|
-
variant: "default",
|
|
31
|
-
size: "default",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
export interface ButtonProps
|
|
37
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
38
|
-
VariantProps<typeof buttonVariants> {
|
|
39
|
-
asChild?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
43
|
-
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
44
|
-
const Comp = asChild ? Slot : "button";
|
|
45
|
-
return (
|
|
46
|
-
<Comp
|
|
47
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
-
ref={ref}
|
|
49
|
-
{...props}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
},
|
|
53
|
-
);
|
|
54
|
-
Button.displayName = "Button";
|
|
55
|
-
|
|
56
|
-
export { Button, buttonVariants };
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
|
|
5
|
-
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
6
|
-
return (
|
|
7
|
-
<div
|
|
8
|
-
data-slot="card"
|
|
9
|
-
className={cn(
|
|
10
|
-
"rounded-xl border bg-card text-card-foreground shadow-sm",
|
|
11
|
-
className,
|
|
12
|
-
)}
|
|
13
|
-
{...props}
|
|
14
|
-
/>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function CardHeader({
|
|
19
|
-
className,
|
|
20
|
-
...props
|
|
21
|
-
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
data-slot="card-header"
|
|
25
|
-
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
-
{...props}
|
|
27
|
-
/>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function CardTitle({
|
|
32
|
-
className,
|
|
33
|
-
...props
|
|
34
|
-
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
data-slot="card-title"
|
|
38
|
-
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
39
|
-
{...props}
|
|
40
|
-
/>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function CardDescription({
|
|
45
|
-
className,
|
|
46
|
-
...props
|
|
47
|
-
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
48
|
-
return (
|
|
49
|
-
<div
|
|
50
|
-
data-slot="card-description"
|
|
51
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
52
|
-
{...props}
|
|
53
|
-
/>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function CardContent({
|
|
58
|
-
className,
|
|
59
|
-
...props
|
|
60
|
-
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
61
|
-
return (
|
|
62
|
-
<div
|
|
63
|
-
data-slot="card-content"
|
|
64
|
-
className={cn("p-6 pt-0", className)}
|
|
65
|
-
{...props}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function CardFooter({
|
|
71
|
-
className,
|
|
72
|
-
...props
|
|
73
|
-
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
74
|
-
return (
|
|
75
|
-
<div
|
|
76
|
-
data-slot="card-footer"
|
|
77
|
-
className={cn("flex items-center p-6 pt-0", className)}
|
|
78
|
-
{...props}
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
Card,
|
|
85
|
-
CardHeader,
|
|
86
|
-
CardFooter,
|
|
87
|
-
CardTitle,
|
|
88
|
-
CardDescription,
|
|
89
|
-
CardContent,
|
|
90
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "new-york",
|
|
4
|
-
"rsc": false,
|
|
5
|
-
"tsx": true,
|
|
6
|
-
"tailwind": {
|
|
7
|
-
"config": "",
|
|
8
|
-
"css": "app/globals.css",
|
|
9
|
-
"baseColor": "zinc",
|
|
10
|
-
"cssVariables": true
|
|
11
|
-
},
|
|
12
|
-
"aliases": {
|
|
13
|
-
"components": "@/components",
|
|
14
|
-
"utils": "@/lib/utils",
|
|
15
|
-
"ui": "@/components/ui",
|
|
16
|
-
"lib": "@/lib",
|
|
17
|
-
"hooks": "@/hooks"
|
|
18
|
-
},
|
|
19
|
-
"iconLibrary": "lucide"
|
|
20
|
-
}
|
|
@@ -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 {};
|
package/templates/b2b/gitignore
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from "clsx";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
// `cn` — the shadcn class merger. clsx resolves conditional/array class
|
|
5
|
-
// inputs; tailwind-merge then dedupes conflicting Tailwind utilities so
|
|
6
|
-
// the last one wins (e.g. `cn("px-2", "px-4")` → "px-4"). Every shadcn
|
|
7
|
-
// component routes its className through this.
|
|
8
|
-
export function cn(...inputs: ClassValue[]) {
|
|
9
|
-
return twMerge(clsx(inputs));
|
|
10
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "__APP_NAME_KEBAB__",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "pylon dev",
|
|
8
|
-
"deploy": "pylon deploy",
|
|
9
|
-
"check": "tsc --noEmit"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"@pylonsync/react": "^__PYLON_VERSION__",
|
|
13
|
-
"@pylonsync/sdk": "^__PYLON_VERSION__",
|
|
14
|
-
"@pylonsync/functions": "^__PYLON_VERSION__",
|
|
15
|
-
"@pylonsync/client": "^__PYLON_VERSION__",
|
|
16
|
-
"react": "^19.0.0",
|
|
17
|
-
"react-dom": "^19.0.0",
|
|
18
|
-
"tailwindcss": "^4.3.0",
|
|
19
|
-
"@tailwindcss/cli": "^4.3.0",
|
|
20
|
-
"tw-animate-css": "^1.2.0",
|
|
21
|
-
"class-variance-authority": "^0.7.1",
|
|
22
|
-
"clsx": "^2.1.1",
|
|
23
|
-
"tailwind-merge": "^2.5.0",
|
|
24
|
-
"lucide-react": "^0.460.0",
|
|
25
|
-
"@radix-ui/react-slot": "^1.1.0"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@pylonsync/cli": "^__PYLON_VERSION__",
|
|
29
|
-
"@types/react": "^19.0.0",
|
|
30
|
-
"@types/react-dom": "^19.0.0",
|
|
31
|
-
"typescript": "^5.6.0"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"jsx": "react",
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"lib": ["ES2022", "DOM"],
|
|
11
|
-
"types": ["react", "react-dom"],
|
|
12
|
-
"baseUrl": ".",
|
|
13
|
-
"paths": {
|
|
14
|
-
"@/*": ["./*"]
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"include": ["app.ts", "app/**/*", "components/**/*", "lib/**/*", "functions/**/*"]
|
|
18
|
-
}
|