@pylonsync/create-pylon 0.3.375 → 0.3.376
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonsync/create-pylon",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.376",
|
|
4
4
|
"description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Settings as SettingsIcon,
|
|
12
12
|
LogOut,
|
|
13
13
|
ExternalLink,
|
|
14
|
+
ChevronsUpDown,
|
|
14
15
|
type LucideIcon,
|
|
15
16
|
} from "lucide-react";
|
|
16
17
|
|
|
@@ -18,21 +19,28 @@ export type NavKey = "overview" | "projects" | "members" | "billing" | "settings
|
|
|
18
19
|
|
|
19
20
|
// Exported so `app/dashboard/layout.tsx` can derive the active item + page
|
|
20
21
|
// title from the request URL — one table drives both the sidebar and the
|
|
21
|
-
// layout's routing awareness.
|
|
22
|
-
|
|
22
|
+
// layout's routing awareness. `group` splits the sidebar into sections:
|
|
23
|
+
// ungrouped items render first, then each named group under an eyebrow label.
|
|
24
|
+
export const NAV: {
|
|
25
|
+
key: NavKey;
|
|
26
|
+
label: string;
|
|
27
|
+
href: string;
|
|
28
|
+
Icon: LucideIcon;
|
|
29
|
+
group?: string;
|
|
30
|
+
}[] = [
|
|
23
31
|
{ key: "overview", label: "Overview", href: "/dashboard", Icon: LayoutDashboard },
|
|
24
32
|
{ key: "projects", label: "Projects", href: "/dashboard/projects", Icon: FolderKanban },
|
|
25
|
-
{ key: "members", label: "Members", href: "/dashboard/members", Icon: Users },
|
|
26
|
-
{ key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard },
|
|
27
|
-
{ key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon },
|
|
33
|
+
{ key: "members", label: "Members", href: "/dashboard/members", Icon: Users, group: "Workspace" },
|
|
34
|
+
{ key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard, group: "Workspace" },
|
|
35
|
+
{ key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon, group: "Workspace" },
|
|
28
36
|
];
|
|
29
37
|
|
|
30
|
-
// Dashboard chrome: a fixed sidebar (logo, workspace switcher, nav
|
|
31
|
-
//
|
|
32
|
-
// every /dashboard page — /dashboard
|
|
33
|
-
// group, so this shell is the only chrome
|
|
34
|
-
// server (serverData.get("User", …)) and
|
|
35
|
-
// email instead of a raw id.
|
|
38
|
+
// Dashboard chrome: a fixed sidebar (logo, workspace switcher, grouped nav,
|
|
39
|
+
// account card pinned to the bottom) plus a slim top bar. Rendered by
|
|
40
|
+
// `app/dashboard/layout.tsx`, which wraps every /dashboard page — /dashboard
|
|
41
|
+
// sits outside the `(marketing)` route group, so this shell is the only chrome
|
|
42
|
+
// here. `userEmail` is resolved on the server (serverData.get("User", …)) and
|
|
43
|
+
// passed in, so the menu shows a real email instead of a raw id.
|
|
36
44
|
export function DashboardShell({
|
|
37
45
|
active,
|
|
38
46
|
title,
|
|
@@ -49,10 +57,12 @@ export function DashboardShell({
|
|
|
49
57
|
children: React.ReactNode;
|
|
50
58
|
}) {
|
|
51
59
|
const router = useRouter();
|
|
60
|
+
const ungrouped = NAV.filter((n) => !n.group);
|
|
61
|
+
const groups = [...new Set(NAV.map((n) => n.group).filter(Boolean))] as string[];
|
|
52
62
|
return (
|
|
53
63
|
<div className="flex min-h-screen bg-white text-zinc-900">
|
|
54
|
-
<aside className="hidden w-60 shrink-0 flex-col border-r border-zinc-200 bg-zinc-50/
|
|
55
|
-
<div className="flex h-14 items-center
|
|
64
|
+
<aside className="hidden w-60 shrink-0 flex-col border-r border-zinc-200/70 bg-zinc-50/80 md:flex">
|
|
65
|
+
<div className="flex h-14 shrink-0 items-center px-4">
|
|
56
66
|
<Link href="/" className="flex items-center gap-2">
|
|
57
67
|
<span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
|
|
58
68
|
A
|
|
@@ -61,7 +71,7 @@ export function DashboardShell({
|
|
|
61
71
|
</Link>
|
|
62
72
|
</div>
|
|
63
73
|
|
|
64
|
-
<div className="px-3
|
|
74
|
+
<div className="px-3 pb-1">
|
|
65
75
|
{/* Every view's data is resolved server-side for the active tenant,
|
|
66
76
|
so switching orgs re-renders the dashboard for the new workspace.
|
|
67
77
|
A soft client navigation (router.push) re-fetches the SSR page —
|
|
@@ -73,37 +83,42 @@ export function DashboardShell({
|
|
|
73
83
|
/>
|
|
74
84
|
</div>
|
|
75
85
|
|
|
76
|
-
<nav className="flex-1
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
strokeWidth={2}
|
|
95
|
-
/>
|
|
96
|
-
{n.label}
|
|
97
|
-
</Link>
|
|
98
|
-
);
|
|
99
|
-
})}
|
|
86
|
+
<nav className="flex-1 overflow-y-auto px-3 pt-3">
|
|
87
|
+
<div className="space-y-0.5">
|
|
88
|
+
{ungrouped.map((n) => (
|
|
89
|
+
<NavItem key={n.key} item={n} isActive={active === n.key} />
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
{groups.map((g) => (
|
|
93
|
+
<div key={g} className="mt-6">
|
|
94
|
+
<div className="mb-1.5 px-2.5 text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
|
|
95
|
+
{g}
|
|
96
|
+
</div>
|
|
97
|
+
<div className="space-y-0.5">
|
|
98
|
+
{NAV.filter((n) => n.group === g).map((n) => (
|
|
99
|
+
<NavItem key={n.key} item={n} isActive={active === n.key} />
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
))}
|
|
100
104
|
</nav>
|
|
105
|
+
|
|
106
|
+
{/* Account card pinned to the bottom of the column — the sidebar's
|
|
107
|
+
base holds the session controls instead of trailing off into
|
|
108
|
+
empty space. The dropdown opens UPWARD (bottom-full). */}
|
|
109
|
+
<div className="border-t border-zinc-200/70 p-3">
|
|
110
|
+
<UserMenu email={userEmail} direction="up" />
|
|
111
|
+
</div>
|
|
101
112
|
</aside>
|
|
102
113
|
|
|
103
114
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
104
|
-
<header className="flex h-14 items-center justify-between border-b border-zinc-200 px-6">
|
|
115
|
+
<header className="flex h-14 shrink-0 items-center justify-between border-b border-zinc-200/70 px-6">
|
|
105
116
|
<h1 className="text-[15px] font-semibold">{title}</h1>
|
|
106
|
-
|
|
117
|
+
{/* The sidebar (and its account card) is hidden below md, so the
|
|
118
|
+
top bar carries the account menu on small screens only. */}
|
|
119
|
+
<div className="md:hidden">
|
|
120
|
+
<UserMenu email={userEmail} direction="down" />
|
|
121
|
+
</div>
|
|
107
122
|
</header>
|
|
108
123
|
<main className="flex-1 p-6">
|
|
109
124
|
<div className="mx-auto max-w-4xl">{children}</div>
|
|
@@ -113,30 +128,89 @@ export function DashboardShell({
|
|
|
113
128
|
);
|
|
114
129
|
}
|
|
115
130
|
|
|
131
|
+
// A single sidebar row. The active row reads as a raised white card — layered
|
|
132
|
+
// translucent shadows instead of a hard border, so it sits naturally on the
|
|
133
|
+
// zinc-50 column. Inactive rows tint on hover only.
|
|
134
|
+
function NavItem({
|
|
135
|
+
item,
|
|
136
|
+
isActive,
|
|
137
|
+
}: {
|
|
138
|
+
item: (typeof NAV)[number];
|
|
139
|
+
isActive: boolean;
|
|
140
|
+
}) {
|
|
141
|
+
return (
|
|
142
|
+
<Link
|
|
143
|
+
href={item.href}
|
|
144
|
+
className={
|
|
145
|
+
"flex items-center gap-2.5 rounded-lg px-2.5 py-[7px] text-[13px] font-medium transition-colors " +
|
|
146
|
+
(isActive
|
|
147
|
+
? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.06),0_0_0_1px_rgba(0,0,0,0.05)]"
|
|
148
|
+
: "text-zinc-600 hover:bg-zinc-200/50 hover:text-zinc-900")
|
|
149
|
+
}
|
|
150
|
+
>
|
|
151
|
+
<item.Icon
|
|
152
|
+
className={"size-[17px] " + (isActive ? "text-zinc-700" : "text-zinc-400")}
|
|
153
|
+
strokeWidth={2}
|
|
154
|
+
/>
|
|
155
|
+
{item.label}
|
|
156
|
+
</Link>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
// Avatar + dropdown (email, View site, Sign out). Native <details> so it opens
|
|
117
|
-
// on click with no extra state
|
|
118
|
-
//
|
|
119
|
-
|
|
161
|
+
// on click with no extra state. In the sidebar it renders as a full-width
|
|
162
|
+
// account card whose menu opens upward; in the mobile top bar it's a compact
|
|
163
|
+
// avatar with a downward menu.
|
|
164
|
+
function UserMenu({
|
|
165
|
+
email,
|
|
166
|
+
direction,
|
|
167
|
+
}: {
|
|
168
|
+
email: string;
|
|
169
|
+
direction: "up" | "down";
|
|
170
|
+
}) {
|
|
120
171
|
const { signOut } = useAuth();
|
|
121
172
|
const initial = (email.trim()[0] || "?").toUpperCase();
|
|
122
173
|
async function onSignOut() {
|
|
123
174
|
await signOut();
|
|
124
175
|
window.location.assign("/");
|
|
125
176
|
}
|
|
177
|
+
const up = direction === "up";
|
|
126
178
|
return (
|
|
127
179
|
<details className="group relative">
|
|
128
|
-
<summary
|
|
129
|
-
{
|
|
180
|
+
<summary
|
|
181
|
+
className={
|
|
182
|
+
"cursor-pointer select-none list-none marker:hidden [&::-webkit-details-marker]:hidden " +
|
|
183
|
+
(up
|
|
184
|
+
? "flex w-full items-center gap-2.5 rounded-lg px-2 py-2 transition-colors hover:bg-zinc-200/50"
|
|
185
|
+
: "flex size-9 items-center justify-center")
|
|
186
|
+
}
|
|
187
|
+
>
|
|
188
|
+
<span className="flex size-7 shrink-0 items-center justify-center rounded-full bg-zinc-900 text-[11px] font-semibold text-white">
|
|
189
|
+
{initial}
|
|
190
|
+
</span>
|
|
191
|
+
{up ? (
|
|
192
|
+
<>
|
|
193
|
+
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-zinc-700">
|
|
194
|
+
{email || "Signed in"}
|
|
195
|
+
</span>
|
|
196
|
+
<ChevronsUpDown className="size-3.5 shrink-0 text-zinc-400" strokeWidth={2} />
|
|
197
|
+
</>
|
|
198
|
+
) : null}
|
|
130
199
|
</summary>
|
|
131
|
-
<div
|
|
132
|
-
|
|
200
|
+
<div
|
|
201
|
+
className={
|
|
202
|
+
"absolute z-40 w-56 overflow-hidden rounded-xl bg-white p-1 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_16px_48px_-16px_rgba(0,0,0,0.25)] " +
|
|
203
|
+
(up ? "bottom-full left-0 mb-2" : "right-0 top-full mt-2")
|
|
204
|
+
}
|
|
205
|
+
>
|
|
206
|
+
<div className="border-b border-zinc-100 px-2.5 py-2">
|
|
133
207
|
<div className="truncate text-[13px] font-medium text-zinc-900">
|
|
134
208
|
{email || "Signed in"}
|
|
135
209
|
</div>
|
|
136
210
|
</div>
|
|
137
211
|
<a
|
|
138
212
|
href="/"
|
|
139
|
-
className="flex items-center gap-2 px-
|
|
213
|
+
className="flex items-center gap-2 rounded-lg px-2.5 py-2 text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
|
|
140
214
|
>
|
|
141
215
|
<ExternalLink className="size-4 text-zinc-400" strokeWidth={2} />
|
|
142
216
|
View site
|
|
@@ -144,7 +218,7 @@ function UserMenu({ email }: { email: string }) {
|
|
|
144
218
|
<button
|
|
145
219
|
type="button"
|
|
146
220
|
onClick={onSignOut}
|
|
147
|
-
className="flex w-full items-center gap-2 px-
|
|
221
|
+
className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
|
|
148
222
|
>
|
|
149
223
|
<LogOut className="size-4 text-zinc-400" strokeWidth={2} />
|
|
150
224
|
Sign out
|