feat: add initial login view
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
|||||||
242
apps/react/src/components/ui/field.tsx
Normal file
242
apps/react/src/components/ui/field.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import { useMemo } from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/libraries/utils"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
|
||||||
|
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
||||||
|
return (
|
||||||
|
<fieldset
|
||||||
|
data-slot="field-set"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-6",
|
||||||
|
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldLegend({
|
||||||
|
className,
|
||||||
|
variant = "legend",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
||||||
|
return (
|
||||||
|
<legend
|
||||||
|
data-slot="field-legend"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(
|
||||||
|
"mb-3 font-medium",
|
||||||
|
"data-[variant=legend]:text-base",
|
||||||
|
"data-[variant=label]:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-group"
|
||||||
|
className={cn(
|
||||||
|
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldVariants = cva(
|
||||||
|
"group/field data-[invalid=true]:text-destructive flex w-full gap-3",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
orientation: {
|
||||||
|
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
||||||
|
horizontal: [
|
||||||
|
"flex-row items-center",
|
||||||
|
"[&>[data-slot=field-label]]:flex-auto",
|
||||||
|
"has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start",
|
||||||
|
],
|
||||||
|
responsive: [
|
||||||
|
"@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
|
||||||
|
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
||||||
|
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
orientation: "vertical",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Field({
|
||||||
|
className,
|
||||||
|
orientation = "vertical",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
data-slot="field"
|
||||||
|
data-orientation={orientation}
|
||||||
|
className={cn(fieldVariants({ orientation }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-content"
|
||||||
|
className={cn(
|
||||||
|
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldLabel({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Label>) {
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
data-slot="field-label"
|
||||||
|
className={cn(
|
||||||
|
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
||||||
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4",
|
||||||
|
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-label"
|
||||||
|
className={cn(
|
||||||
|
"flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
data-slot="field-description"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground text-sm font-normal leading-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
|
||||||
|
"nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
|
||||||
|
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldSeparator({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
children?: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-separator"
|
||||||
|
data-content={!!children}
|
||||||
|
className={cn(
|
||||||
|
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Separator className="absolute inset-0 top-1/2" />
|
||||||
|
{children && (
|
||||||
|
<span
|
||||||
|
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
|
||||||
|
data-slot="field-separator-content"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldError({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
errors,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
errors?: Array<{ message?: string } | undefined>
|
||||||
|
}) {
|
||||||
|
const content = useMemo(() => {
|
||||||
|
if (children) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errors) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors?.length === 1 && errors[0]?.message) {
|
||||||
|
return errors[0].message
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="ml-4 flex list-disc flex-col gap-1">
|
||||||
|
{errors.map(
|
||||||
|
(error, index) =>
|
||||||
|
error?.message && <li key={index}>{error.message}</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}, [children, errors])
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
data-slot="field-error"
|
||||||
|
className={cn("text-destructive text-sm font-normal", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Field,
|
||||||
|
FieldLabel,
|
||||||
|
FieldDescription,
|
||||||
|
FieldError,
|
||||||
|
FieldGroup,
|
||||||
|
FieldLegend,
|
||||||
|
FieldSeparator,
|
||||||
|
FieldSet,
|
||||||
|
FieldContent,
|
||||||
|
FieldTitle,
|
||||||
|
}
|
||||||
22
apps/react/src/components/ui/label.tsx
Normal file
22
apps/react/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
|
||||||
|
import { cn } from "@/libraries/utils"
|
||||||
|
|
||||||
|
function Label({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
data-slot="label"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Label }
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import { createRouter, RouterProvider } from "@tanstack/react-router";
|
import { RouterProvider } from "@tanstack/react-router";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
import { ThemeProvider } from "./components/theme-provider.tsx";
|
import { ThemeProvider } from "./components/theme-provider.tsx";
|
||||||
import { routeTree } from "./routes.tsx";
|
import { router } from "./router.tsx";
|
||||||
|
|
||||||
const router = createRouter({ routeTree });
|
|
||||||
|
|
||||||
declare module "@tanstack/react-router" {
|
declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { createRootRoute, createRoute } from "@tanstack/react-router";
|
import { createRootRoute, createRoute, createRouter } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AppView } from "./views/app.view.tsx";
|
import { AppView } from "./views/app.view.tsx";
|
||||||
import { CallbackView } from "./views/auth/callback.view.tsx";
|
import { CallbackView } from "./views/auth/callback.view.tsx";
|
||||||
|
import { LoginView } from "./views/auth/login.view.tsx";
|
||||||
import { DashboardView } from "./views/dashboard/dashboard.view.tsx";
|
import { DashboardView } from "./views/dashboard/dashboard.view.tsx";
|
||||||
|
|
||||||
const root = createRootRoute();
|
const root = createRootRoute();
|
||||||
@@ -12,6 +13,12 @@ const callback = createRoute({
|
|||||||
component: CallbackView,
|
component: CallbackView,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const login = createRoute({
|
||||||
|
getParentRoute: () => root,
|
||||||
|
path: "/login",
|
||||||
|
component: LoginView,
|
||||||
|
});
|
||||||
|
|
||||||
const app = createRoute({
|
const app = createRoute({
|
||||||
id: "app",
|
id: "app",
|
||||||
getParentRoute: () => root,
|
getParentRoute: () => root,
|
||||||
@@ -24,7 +31,7 @@ const dashboard = createRoute({
|
|||||||
component: DashboardView,
|
component: DashboardView,
|
||||||
});
|
});
|
||||||
|
|
||||||
root.addChildren([app, callback]);
|
root.addChildren([app, login, callback]);
|
||||||
app.addChildren([dashboard]);
|
app.addChildren([dashboard]);
|
||||||
|
|
||||||
export const routeTree = root;
|
export const router = createRouter({ routeTree: root });
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Controller } from "../libraries/controller.ts";
|
import { Controller } from "../libraries/controller.ts";
|
||||||
|
import { router } from "../router.tsx";
|
||||||
import { zitadel } from "../services/zitadel.ts";
|
import { zitadel } from "../services/zitadel.ts";
|
||||||
|
|
||||||
export class AppController extends Controller<{
|
export class AppController extends Controller<{
|
||||||
@@ -13,7 +14,7 @@ export class AppController extends Controller<{
|
|||||||
async #getAuthenticatedState(): Promise<boolean> {
|
async #getAuthenticatedState(): Promise<boolean> {
|
||||||
const user = await zitadel.userManager.getUser();
|
const user = await zitadel.userManager.getUser();
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
zitadel.authorize();
|
router.navigate({ to: "/login" });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
72
apps/react/src/views/auth/components/login-form.tsx
Normal file
72
apps/react/src/views/auth/components/login-form.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { GalleryVerticalEnd } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSeparator } from "@/components/ui/field";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { cn } from "@/libraries/utils";
|
||||||
|
|
||||||
|
export function LoginForm({
|
||||||
|
className,
|
||||||
|
passkey,
|
||||||
|
...props
|
||||||
|
}: { passkey: (email: string) => Promise<void> } & React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const email = e.currentTarget.elements.namedItem("email");
|
||||||
|
if (email instanceof HTMLInputElement) {
|
||||||
|
passkey(email.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FieldGroup>
|
||||||
|
<div className="flex flex-col items-center gap-2 text-center">
|
||||||
|
<a href="#" className="flex flex-col items-center gap-2 font-medium">
|
||||||
|
<div className="flex size-8 items-center justify-center rounded-md">
|
||||||
|
<GalleryVerticalEnd className="size-6" />
|
||||||
|
</div>
|
||||||
|
<span className="sr-only">Valkyr Sandbox</span>
|
||||||
|
</a>
|
||||||
|
<h1 className="text-xl font-bold">Welcome to Valkyr Sandbox</h1>
|
||||||
|
<FieldDescription>
|
||||||
|
Don't have an account? <a href="#">Sign up</a>
|
||||||
|
</FieldDescription>
|
||||||
|
</div>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" type="email" placeholder="m@example.com" required />
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Button type="submit">Login</Button>
|
||||||
|
</Field>
|
||||||
|
<FieldSeparator>Or</FieldSeparator>
|
||||||
|
<Field className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<Button variant="outline" type="button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Continue with Apple
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" type="button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Continue with Google
|
||||||
|
</Button>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</form>
|
||||||
|
<FieldDescription className="px-6 text-center">
|
||||||
|
By clicking continue, you agree to our <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
|
||||||
|
</FieldDescription>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,29 +1,27 @@
|
|||||||
import { Controller } from "../../libraries/controller.ts";
|
import { Controller } from "../../libraries/controller.ts";
|
||||||
import { type User, zitadel } from "../../services/zitadel.ts";
|
|
||||||
|
|
||||||
export class LoginController extends Controller<{
|
export class LoginController extends Controller {
|
||||||
user?: User;
|
async passkey(email: string) {
|
||||||
}> {
|
const result = await fetch("https://auth.valkyrjs.com/v2/sessions", {
|
||||||
async onInit() {
|
method: "POST",
|
||||||
return {
|
headers: {
|
||||||
user: await this.#getAuthenticationState(),
|
"content-type": "application/json",
|
||||||
};
|
},
|
||||||
}
|
body: JSON.stringify({
|
||||||
|
checks: {
|
||||||
async #getAuthenticationState(): Promise<User | undefined> {
|
user: {
|
||||||
return zitadel.userManager.getUser().then((user) => {
|
loginName: email,
|
||||||
if (user === null) {
|
},
|
||||||
return undefined;
|
},
|
||||||
}
|
challenges: {
|
||||||
return user;
|
webAuthN: {
|
||||||
|
domain: "auth.valkyrjs.com",
|
||||||
|
userVerificationRequirement: "USER_VERIFICATION_REQUIREMENT_REQUIRED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
login() {
|
console.log(await result.text());
|
||||||
zitadel.authorize();
|
|
||||||
}
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
zitadel.signout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useController } from "../../libraries/controller.ts";
|
import { useController } from "../../libraries/controller.ts";
|
||||||
|
import { LoginForm } from "./components/login-form.tsx";
|
||||||
import { LoginController } from "./login.controller.ts";
|
import { LoginController } from "./login.controller.ts";
|
||||||
|
|
||||||
export function LoginView() {
|
export function LoginView() {
|
||||||
const [{ user }, { login, logout }] = useController(LoginController);
|
const [, , { passkey }] = useController(LoginController);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||||
<button type="button" onClick={() => (user === undefined ? login() : logout())}>
|
<div className="w-full max-w-sm">
|
||||||
{user === undefined ? "Login" : "Logout"}
|
<LoginForm passkey={passkey} />
|
||||||
</button>
|
</div>
|
||||||
{user !== undefined ? <pre>{JSON.stringify(user, null, 2)}</pre> : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
16
deno.lock
generated
16
deno.lock
generated
@@ -16,6 +16,7 @@
|
|||||||
"npm:@radix-ui/react-avatar@^1.1.11": "1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@radix-ui/react-avatar@^1.1.11": "1.1.11_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@radix-ui/react-dialog@^1.1.15": "1.1.15_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@radix-ui/react-dialog@^1.1.15": "1.1.15_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@radix-ui/react-dropdown-menu@^2.1.16": "2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@radix-ui/react-dropdown-menu@^2.1.16": "2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
|
"npm:@radix-ui/react-label@^2.1.8": "2.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@radix-ui/react-scroll-area@^1.2.10": "1.2.10_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@radix-ui/react-scroll-area@^1.2.10": "1.2.10_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@radix-ui/react-separator@^1.1.8": "1.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@radix-ui/react-separator@^1.1.8": "1.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@radix-ui/react-slot@^1.2.4": "1.2.4_@types+react@19.1.13_react@19.1.1",
|
"npm:@radix-ui/react-slot@^1.2.4": "1.2.4_@types+react@19.1.13_react@19.1.1",
|
||||||
@@ -851,6 +852,20 @@
|
|||||||
"@types/react"
|
"@types/react"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@radix-ui/react-label@2.1.8_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
|
||||||
|
"integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
|
||||||
|
"dependencies": [
|
||||||
|
"@radix-ui/react-primitive@2.1.4_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
|
"@types/react",
|
||||||
|
"@types/react-dom",
|
||||||
|
"react",
|
||||||
|
"react-dom"
|
||||||
|
],
|
||||||
|
"optionalPeers": [
|
||||||
|
"@types/react",
|
||||||
|
"@types/react-dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
"@radix-ui/react-menu@2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
|
"@radix-ui/react-menu@2.1.16_@types+react@19.1.13_@types+react-dom@19.1.9__@types+react@19.1.13_react@19.1.1_react-dom@19.1.1__react@19.1.1": {
|
||||||
"integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
|
"integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -2858,6 +2873,7 @@
|
|||||||
"npm:@radix-ui/react-avatar@^1.1.11",
|
"npm:@radix-ui/react-avatar@^1.1.11",
|
||||||
"npm:@radix-ui/react-dialog@^1.1.15",
|
"npm:@radix-ui/react-dialog@^1.1.15",
|
||||||
"npm:@radix-ui/react-dropdown-menu@^2.1.16",
|
"npm:@radix-ui/react-dropdown-menu@^2.1.16",
|
||||||
|
"npm:@radix-ui/react-label@^2.1.8",
|
||||||
"npm:@radix-ui/react-scroll-area@^1.2.10",
|
"npm:@radix-ui/react-scroll-area@^1.2.10",
|
||||||
"npm:@radix-ui/react-separator@^1.1.8",
|
"npm:@radix-ui/react-separator@^1.1.8",
|
||||||
"npm:@radix-ui/react-slot@^1.2.4",
|
"npm:@radix-ui/react-slot@^1.2.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user