@flaredev/cli 0.12.1 → 0.13.0
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/dist/index.js +114 -25
- package/package.json +2 -2
- package/templates/app/README.md +1 -1
- package/templates/app/_gitignore +7 -0
- package/templates/app/app/dashboard/layout.tsx +2 -2
- package/templates/app/app/dashboard/page.tsx +101 -26
- package/templates/app/app/dashboard/users/actions.ts +86 -0
- package/templates/app/app/dashboard/users/page.tsx +54 -0
- package/templates/app/components/dashboard/dashboard-header.tsx +2 -2
- package/templates/app/components/dashboard/dashboard-sidebar.tsx +13 -10
- package/templates/app/components/dashboard/user-actions.tsx +171 -0
- package/templates/app/components/dashboard/user-menu.tsx +17 -23
- package/templates/app/components/dashboard/users-table.tsx +109 -0
- package/templates/app/lib/api-docs.ts +1 -1
- package/templates/app/lib/auth.ts +3 -3
- package/templates/app/lib/dashboard.ts +5 -1
- package/templates/app/lib/mail.ts +2 -2
- package/templates/app/lib/site.ts +2 -2
- package/templates/app/vite.config.ts +3 -1
- package/templates/locks/next/package-lock.json +92 -91
- package/templates/locks/next/pnpm-lock.yaml +84 -93
- package/templates/locks/next/yarn.lock +83 -95
- package/templates/next/README.md +1 -1
- package/templates/next/app/dashboard/layout.tsx +2 -2
- package/templates/next/app/dashboard/observability/page.tsx +156 -0
- package/templates/next/app/dashboard/page.tsx +87 -23
- package/templates/next/app/dashboard/users/page.tsx +51 -0
- package/templates/next/lib/auth.ts +3 -3
- package/templates/next/lib/dashboard-nav.ts +29 -0
- package/templates/next/lib/dashboard.ts +5 -1
- package/templates/next/lib/mail.ts +2 -2
- package/templates/next/prisma/schema/base.prisma +6 -0
- package/templates/next/tsconfig.json +2 -2
- /package/templates/app/app/{forgot-password → (auth)/forgot-password}/page.tsx +0 -0
- /package/templates/app/app/{invite → (auth)/invite}/[token]/route.ts +0 -0
- /package/templates/app/app/{reset-password → (auth)/reset-password}/page.tsx +0 -0
- /package/templates/app/app/{sign-in → (auth)/sign-in}/page.tsx +0 -0
- /package/templates/app/app/{sign-up → (auth)/sign-up}/page.tsx +0 -0
- /package/templates/app/app/{two-factor → (auth)/two-factor}/page.tsx +0 -0
- /package/templates/app/app/{verify-email → (auth)/verify-email}/page.tsx +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { redirect } from "next/navigation";
|
|
2
|
+
import { PageHeader } from "@/components/dashboard/page-header";
|
|
3
|
+
import { CreateUserButton } from "@/components/dashboard/user-actions";
|
|
4
|
+
import { UsersTable } from "@/components/dashboard/users-table";
|
|
5
|
+
import { prisma } from "@/lib/db";
|
|
6
|
+
import { ADMIN_ROLES, dashboardSession } from "@/lib/dashboard";
|
|
7
|
+
|
|
8
|
+
export const metadata = { title: "Users" };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Everyone with an account — there from the first sign-in, before any resource exists.
|
|
12
|
+
*
|
|
13
|
+
* Admin only: the resource policies decide who sees data, but the account list is about
|
|
14
|
+
* the people themselves, so it follows ADMIN_ROLES rather than any one policy.
|
|
15
|
+
*/
|
|
16
|
+
export default async function UsersPage() {
|
|
17
|
+
const { session, role } = await dashboardSession();
|
|
18
|
+
if (!session) redirect("/sign-in?next=/dashboard/users");
|
|
19
|
+
if (!role || !ADMIN_ROLES.includes(role)) redirect("/dashboard");
|
|
20
|
+
|
|
21
|
+
const [rows, total] = await Promise.all([
|
|
22
|
+
prisma.user.findMany({
|
|
23
|
+
orderBy: { createdAt: "desc" },
|
|
24
|
+
take: 200,
|
|
25
|
+
select: {
|
|
26
|
+
id: true,
|
|
27
|
+
name: true,
|
|
28
|
+
email: true,
|
|
29
|
+
image: true,
|
|
30
|
+
role: true,
|
|
31
|
+
banned: true,
|
|
32
|
+
emailVerified: true,
|
|
33
|
+
twoFactorEnabled: true,
|
|
34
|
+
createdAt: true,
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
prisma.user.count(),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<PageHeader
|
|
43
|
+
title="Users"
|
|
44
|
+
description="Everyone who can sign in, newest first — and what each of them is allowed to be."
|
|
45
|
+
crumbs={[{ label: "Dashboard", href: "/dashboard" }, { label: "Users" }]}
|
|
46
|
+
actions={<CreateUserButton />}
|
|
47
|
+
/>
|
|
48
|
+
<UsersTable rows={rows} total={total} currentUserId={session.user.id} />
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -190,7 +190,7 @@ async function promoteFirstUser() {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export const auth = betterAuth({
|
|
193
|
-
appName: "
|
|
193
|
+
appName: "__APP_TITLE__",
|
|
194
194
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
195
195
|
// Without BETTER_AUTH_URL, accept local dev on any port, a `flare dev --tunnel` URL and
|
|
196
196
|
// this app's workers.dev host. Cloudflare only routes hosts this Worker serves, so a
|
|
@@ -263,7 +263,7 @@ export const auth = betterAuth({
|
|
|
263
263
|
}),
|
|
264
264
|
emailOTP({ sendVerificationOTP: (data) => sendEmailCode(data), otpLength: 6, expiresIn: 5 * 60, allowedAttempts: 5 }),
|
|
265
265
|
twoFactor({
|
|
266
|
-
issuer: "
|
|
266
|
+
issuer: "__APP_TITLE__",
|
|
267
267
|
otpOptions: { sendOTP: ({ user, otp }) => sendTwoFactorCode({ user, otp }), allowedAttempts: 5 },
|
|
268
268
|
// With only email codes switched on there's no authenticator app to confirm first.
|
|
269
269
|
skipVerificationOnEnable: !authConfig.twoFactor.authenticator,
|
|
@@ -271,7 +271,7 @@ export const auth = betterAuth({
|
|
|
271
271
|
// accounts that do have one must still give it.
|
|
272
272
|
allowPasswordless: true,
|
|
273
273
|
}),
|
|
274
|
-
passkey({ rpName: "
|
|
274
|
+
passkey({ rpName: "__APP_TITLE__" }),
|
|
275
275
|
// Must stay last: lets server actions set auth cookies.
|
|
276
276
|
nextCookies(),
|
|
277
277
|
],
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Extra dashboard sidebar links (maintained by flare generators such as `flare gen security`).
|
|
2
|
+
// Add your own links outside the generated block.
|
|
3
|
+
// generated:start
|
|
4
|
+
export interface DashboardLink {
|
|
5
|
+
label: string;
|
|
6
|
+
href: string;
|
|
7
|
+
/** A resource icon name, e.g. "shield" (see components/dashboard/resource-icon.tsx). */
|
|
8
|
+
icon: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const generatedDashboardLinks: DashboardLink[] = [];
|
|
12
|
+
// generated:end
|
|
13
|
+
|
|
14
|
+
import { authConfig } from "./auth-config";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Links shown under "Platform" in the dashboard sidebar.
|
|
18
|
+
*
|
|
19
|
+
* No Costs page on this stack: the estimator prices Workers, D1 and R2 from Cloudflare's
|
|
20
|
+
* price sheet, and this app runs on Vercel and Neon, whose bills live in their own
|
|
21
|
+
* dashboards.
|
|
22
|
+
*/
|
|
23
|
+
export const dashboardLinks: DashboardLink[] = [
|
|
24
|
+
// Only where invitations are the way in; the page explains itself for the other two
|
|
25
|
+
// modes, but there is no reason to send anybody to it.
|
|
26
|
+
...(authConfig.signup === "invite" ? [{ label: "Invitations", href: "/dashboard/invitations", icon: "mail" }] : []),
|
|
27
|
+
...generatedDashboardLinks,
|
|
28
|
+
{ label: "API reference", href: "/api/reference", icon: "book" },
|
|
29
|
+
];
|
|
@@ -162,7 +162,11 @@ export async function resourceStats(name: string, field?: string, days = 7): Pro
|
|
|
162
162
|
return stats;
|
|
163
163
|
},
|
|
164
164
|
["flare", "resource-stats"],
|
|
165
|
-
|
|
165
|
+
// TTL.short, by the TTL table's own words: "changes often, cheap to recompute:
|
|
166
|
+
// counts, dashboards". These are those counts — and `flare seed:resource` writes
|
|
167
|
+
// from outside the app, where no tag revalidates them, so at medium the overview
|
|
168
|
+
// said "0 records" for five minutes after seeding a thousand.
|
|
169
|
+
{ tags: [resourceTag(name)], revalidate: TTL.short },
|
|
166
170
|
);
|
|
167
171
|
return read(name, field, days, limit ?? undefined);
|
|
168
172
|
}
|
|
@@ -9,7 +9,7 @@ import { createMailer, renderTransactionalEmail, type TransactionalEmail } from
|
|
|
9
9
|
*/
|
|
10
10
|
export const mailer = createMailer({
|
|
11
11
|
apiKey: process.env.RESEND_API_KEY,
|
|
12
|
-
from: process.env.MAIL_FROM || "
|
|
12
|
+
from: process.env.MAIL_FROM || "__APP_TITLE__ <onboarding@resend.dev>",
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -30,6 +30,6 @@ export async function sendTransactionalEmail({
|
|
|
30
30
|
idempotencyKey,
|
|
31
31
|
...content
|
|
32
32
|
}: Omit<TransactionalEmail, "appName"> & { to: string | string[]; subject: string; idempotencyKey?: string }) {
|
|
33
|
-
const { html, text } = renderTransactionalEmail({ appName: "
|
|
33
|
+
const { html, text } = renderTransactionalEmail({ appName: "__APP_TITLE__", ...content });
|
|
34
34
|
return mailer.send({ to, subject, html, text, idempotencyKey });
|
|
35
35
|
}
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
// regenerated: the generator, the datasource, the tables Better Auth needs and the two
|
|
6
6
|
// Flare's own dashboard uses are all here, and yours to edit.
|
|
7
7
|
|
|
8
|
+
// Prisma 7's generator, not a leftover from 6: `prisma-client` (the v7 provider; v6's
|
|
9
|
+
// was `prisma-client-js`), and `output` is required — v7 no longer generates into
|
|
10
|
+
// node_modules. The client lands in lib/generated/, git-ignored and rebuilt on every
|
|
11
|
+
// install and `flare migrate`.
|
|
8
12
|
generator client {
|
|
9
13
|
provider = "prisma-client"
|
|
10
14
|
output = "../../lib/generated/prisma"
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
// No `url` here — that is also Prisma 7: the connection string lives in
|
|
18
|
+
// prisma.config.ts, which reads DATABASE_URL from .env.
|
|
13
19
|
datasource db {
|
|
14
20
|
provider = "postgresql"
|
|
15
21
|
}
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
13
|
"isolatedModules": true,
|
|
14
14
|
"incremental": true,
|
|
15
|
-
"jsx": "
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
16
|
"types": ["node"],
|
|
17
17
|
"plugins": [{ "name": "next" }],
|
|
18
18
|
"paths": {
|
|
19
19
|
"@/*": ["./*"]
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|
|
23
23
|
"exclude": ["node_modules", ".next", "lib/generated"]
|
|
24
24
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|