@flaredev/cli 0.13.0 → 0.14.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 +72 -37
- package/package.json +2 -2
- package/templates/app/app/dashboard/error.tsx +18 -5
- package/templates/app/app/dashboard/roles/actions.ts +57 -0
- package/templates/app/app/dashboard/roles/page.tsx +56 -0
- package/templates/app/app/error.tsx +14 -5
- package/templates/app/components/account/sections.tsx +23 -3
- package/templates/app/components/auth/password-field.tsx +51 -32
- package/templates/app/components/auth/sign-in-flow.tsx +306 -306
- package/templates/app/components/dashboard/bulk-add-sheet.tsx +377 -0
- package/templates/app/components/dashboard/cell-value.ts +67 -0
- package/templates/app/components/dashboard/dashboard-sidebar.tsx +2 -1
- package/templates/app/components/dashboard/fields/field-widget.tsx +20 -16
- package/templates/app/components/dashboard/fields/file-field.tsx +23 -1
- package/templates/app/components/dashboard/import-dialog.tsx +1 -58
- package/templates/app/components/dashboard/resource-table.tsx +2 -0
- package/templates/app/components/dashboard/role-matrix.tsx +268 -0
- package/templates/app/components/dashboard/user-actions.tsx +55 -9
- package/templates/app/components/dashboard/user-menu.tsx +1 -1
- package/templates/app/components/ui/combobox.tsx +133 -0
- package/templates/app/db/flare-schema.ts +27 -1
- package/templates/app/lib/api.ts +4 -1
- package/templates/app/lib/dashboard.ts +15 -4
- package/templates/app/lib/permissions.ts +105 -0
- package/templates/app/lib/stale-chunk.ts +50 -0
- package/templates/app/migrations/0000_init.sql +9 -0
- package/templates/app/migrations/meta/0000_snapshot.json +57 -1
- package/templates/app/migrations/meta/_journal.json +1 -1
- package/templates/locks/cloudflare/pnpm-lock.yaml +8 -8
- package/templates/locks/next/pnpm-lock.yaml +4 -4
- package/templates/next/lib/dashboard.ts +15 -4
- package/templates/next/lib/permissions.ts +100 -0
- package/templates/next/prisma/schema/base.prisma +31 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useId, useMemo, useState } from "react";
|
|
3
|
+
import { forwardRef, useId, useMemo, useState } from "react";
|
|
4
4
|
import { CheckIcon, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
5
5
|
import { checkPassword, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH } from "@/lib/password-rules";
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
@@ -22,6 +22,40 @@ interface Props {
|
|
|
22
22
|
avoid?: (string | null | undefined)[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* A password input with the eye that reveals what you typed.
|
|
27
|
+
*
|
|
28
|
+
* The one place that knows how to do this, because a password field people cannot see
|
|
29
|
+
* into is where typos go to hide — and a form that offers the eye in one place and not
|
|
30
|
+
* the next teaches people not to trust it. {@link PasswordField} wraps this with the
|
|
31
|
+
* rules and the strength meter; forms that only need the box use it directly.
|
|
32
|
+
*/
|
|
33
|
+
export const PasswordInput = forwardRef<HTMLInputElement, Omit<React.ComponentProps<typeof AuthInput>, "type">>(
|
|
34
|
+
function PasswordInput({ className, ...props }, ref) {
|
|
35
|
+
const [visible, setVisible] = useState(false);
|
|
36
|
+
return (
|
|
37
|
+
<div className="relative">
|
|
38
|
+
<AuthInput ref={ref} {...props} type={visible ? "text" : "password"} className={cn("pr-11", className)} />
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={() => setVisible(!visible)}
|
|
42
|
+
aria-pressed={visible}
|
|
43
|
+
aria-controls={props.id}
|
|
44
|
+
aria-label={visible ? "Hide password" : "Show password"}
|
|
45
|
+
title={visible ? "Hide password" : "Show password"}
|
|
46
|
+
// Not in the tab order: the input it belongs to is, and a keyboard user
|
|
47
|
+
// tabbing from the password to a toggle before the submit button is a worse
|
|
48
|
+
// path than the one they expect.
|
|
49
|
+
tabIndex={-1}
|
|
50
|
+
className="absolute inset-y-0 right-0 grid w-11 place-items-center text-foreground-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
|
51
|
+
>
|
|
52
|
+
{visible ? <EyeOffIcon className="size-4.5" /> : <EyeIcon className="size-4.5" />}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
25
59
|
const BAR_COLOURS = ["bg-danger", "bg-danger", "bg-warning", "bg-success/70", "bg-success"];
|
|
26
60
|
|
|
27
61
|
/**
|
|
@@ -46,43 +80,28 @@ export function PasswordField({
|
|
|
46
80
|
const generatedId = useId();
|
|
47
81
|
const fieldId = id ?? generatedId;
|
|
48
82
|
const hintId = `${fieldId}-hint`;
|
|
49
|
-
const [visible, setVisible] = useState(false);
|
|
50
83
|
const check = useMemo(() => checkPassword(value, avoid), [value, avoid.join("\u0000")]);
|
|
51
84
|
const started = value.length > 0;
|
|
52
85
|
|
|
53
86
|
return (
|
|
54
87
|
<div className="flex flex-col gap-2">
|
|
55
88
|
<AuthLabel htmlFor={fieldId}>{label}</AuthLabel>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
className={cn("pr-11", showRules && check.valid && "border-success/60")}
|
|
73
|
-
/>
|
|
74
|
-
<button
|
|
75
|
-
type="button"
|
|
76
|
-
onClick={() => setVisible(!visible)}
|
|
77
|
-
aria-pressed={visible}
|
|
78
|
-
aria-controls={fieldId}
|
|
79
|
-
aria-label={visible ? "Hide password" : "Show password"}
|
|
80
|
-
title={visible ? "Hide password" : "Show password"}
|
|
81
|
-
className="absolute inset-y-0 right-0 grid w-11 place-items-center text-foreground-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
|
82
|
-
>
|
|
83
|
-
{visible ? <EyeOffIcon className="size-4.5" /> : <EyeIcon className="size-4.5" />}
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
89
|
+
<PasswordInput
|
|
90
|
+
id={fieldId}
|
|
91
|
+
name={name}
|
|
92
|
+
autoComplete={autoComplete}
|
|
93
|
+
autoFocus={autoFocus}
|
|
94
|
+
required={required}
|
|
95
|
+
placeholder={placeholder}
|
|
96
|
+
value={value}
|
|
97
|
+
onChange={(event) => onChange(event.target.value)}
|
|
98
|
+
minLength={autoComplete === "new-password" ? PASSWORD_MIN_LENGTH : undefined}
|
|
99
|
+
maxLength={PASSWORD_MAX_LENGTH}
|
|
100
|
+
aria-describedby={showRules ? hintId : undefined}
|
|
101
|
+
// A styling hook, so the field can go green without any state plumbing.
|
|
102
|
+
data-has-passed-validation={showRules && check.valid ? "" : undefined}
|
|
103
|
+
className={cn(showRules && check.valid && "border-success/60")}
|
|
104
|
+
/>
|
|
86
105
|
|
|
87
106
|
{showRules && (
|
|
88
107
|
<div id={hintId} className="flex flex-col gap-2">
|