Skip to content

Commit

Permalink
Add Checkbox component and update admin message types; bump bits-ui v…
Browse files Browse the repository at this point in the history
…ersion
  • Loading branch information
haukened committed Nov 7, 2024
1 parent 88dea5d commit dd91343
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 55 deletions.
35 changes: 25 additions & 10 deletions messages/en.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"_delete": "Delete",
"_save": "Save",
"adminActions": "Actions",
"adminDescription": "All users here have equal privilege level. Use caution.",
"adminConfirmDelete": "I am ABSOLUTELY sure",
"adminDescription": "All users here have equal privilege level. Use caution",
"adminEmailBody": "Hello, please use this link to create a password for wireguard-web",
"adminEmailSubject": "WireGuard-Web Registration",
"adminNewUser": "New User",
"adminNewUserDescription": "Send the user this registration link, so they can create a password.",
"adminNotShownAgain": "For security, this will not be shown again.",
"adminTitle": "User Management",
"adminUserCreate": "Create User",
"adminUserCreated": "User {email} has been created.",
"adminUserCreated": "User {email} has been created",
"adminUserDelete": "Delete User",
"adminUserDeleteConfirm": "Are you sure you want to delete this user?",
"adminUserDeleted": "User {email} has been deleted.",
"adminUserDeleted": "User {email} has been deleted",
"adminUserDeleteDescription": "User {email} will be permanently deleted",
"adminUserDeleteWarning": "Are you ABSOLUTELY sure? This action cannot be undone.",
"adminUserDisabled": "User is disabled",
"adminUserEdit": "Edit User",
"adminUserEnabled": "User is enabled",
"adminUserToggle": "Enable / Disable User",
"adminUserUpdated": "User updated",
"cancel": "Cancel",
"close": "Close",
"copiedToClipboard": "Copied to clipboard",
"copyLink": "Copy Link",
"deploy": "Deploy",
"email": "Email",
"emailAddress": "Email Address",
Expand All @@ -34,24 +47,25 @@
"home": "Home",
"lastLogin": "Last Login",
"lastName": "Last Name",
"loggingMultipleUsersCreated": "Multiple users created",
"loggingNoUserCreated": "No user created",
"loggingUnexpectedError": "An unexpected error occurred: {error}",
"login": "Login",
"loginDescription": "Login to your account",
"logout": "Logout",
"loggingUnexpectedError": "An unexpected error occurred: {error}",
"loggingNoUserCreated": "No user created",
"loggingMultipleUsersCreated": "Multiple users created",
"myAccount": "My Account",
"name": "Name",
"password": "Password",
"passwordChangeDescription": "Change your password here.",
"passwordChangeDescription": "Change your password here",
"passwordConfirm": "Confirm Password",
"passwordDescription": "Enter your password",
"passwordNew": "New Password",
"passwordUpdated": "Your password has been updated.",
"passwordUpdated": "Your password has been updated",
"profile": "Profile",
"profileDescription": "Edit your personal details here.",
"profileUpdated": "Your profile has been updated.",
"profileDescription": "Edit your personal details here",
"profileUpdated": "Your profile has been updated",
"save": "Save",
"sendEmail": "Send Email",
"settings": "Settings",
"setupDescription": "Lets get you started...",
"setupEmailDescription": "This will be your username",
Expand All @@ -64,5 +78,6 @@
"status": "Status",
"title": "WireGuard Web",
"toggleTheme": "Toggle Theme",
"user": "User",
"users": "Users"
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@types/better-sqlite3": "^7.6.11",
"@types/eslint": "^9.6.0",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.0.0-next.45",
"bits-ui": "^1.0.0-next.46",
"clsx": "^2.1.1",
"drizzle-kit": "^0.22.8",
"eslint": "^9.7.0",
Expand Down
33 changes: 33 additions & 0 deletions src/lib/components/ui/checkbox/checkbox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import Minus from "lucide-svelte/icons/minus";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
checked = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>

<CheckboxPrimitive.Root
bind:ref
class={cn(
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className
)}
bind:checked
{...restProps}
>
{#snippet children({ checked })}
<div class="flex size-4 items-center justify-center text-current">
{#if checked === "indeterminate"}
<Minus class="size-3.5" />
{:else}
<Check class={cn("size-3.5", !checked && "text-transparent")} />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>
6 changes: 6 additions & 0 deletions src/lib/components/ui/checkbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};
18 changes: 8 additions & 10 deletions src/lib/components/ui/label/label.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = LabelPrimitive.Props;
type $$Events = LabelPrimitive.Events;
let className: $$Props["class"] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: LabelPrimitive.RootProps = $props();
</script>

<LabelPrimitive.Root
bind:ref
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...$$restProps}
on:mousedown
>
<slot />
</LabelPrimitive.Root>
{...restProps}
/>
15 changes: 8 additions & 7 deletions src/routes/admin/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import { v4 as uuid4 } from "uuid";
import { zod } from "sveltekit-superforms/adapters";
import * as m from '$lib/paraglide/messages';
import type { Actions, PageServerLoad } from "./$types";

type Message = {
text: string | undefined;
token: string | undefined;
}
import type { Message } from "./message";

export const load: PageServerLoad = async (event) => {
// set up the form
Expand Down Expand Up @@ -122,7 +118,12 @@ export const actions: Actions = {
// return the created user and the registration token
return message(form, {
text: m.adminUserCreated({email: createdUser.email}),
token: regToken,
token: {
value: regToken,
email: createdUser.email,
firstname: createdUser.firstname,
lastname: createdUser.lastname,
}
});
},
update: async (event) => {
Expand Down Expand Up @@ -151,7 +152,7 @@ export const actions: Actions = {
}
// return the updated user
return message(form, {
text: 'User updated',
text: m.adminUserUpdated(),
token: undefined,
});
}
Expand Down
86 changes: 63 additions & 23 deletions src/routes/admin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import type { PageData } from "./$types";
import type { User } from "$lib/server/db";
import { Button } from "$lib/components/ui/button";
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
import { CircleCheckBig, CircleX, UserPlus, Ellipsis, Pencil, Trash } from "lucide-svelte";
import { Input } from '$lib/components/ui/input';
import { page } from "$app/stores";
import { Label } from "$lib/components/ui/label/index.js";
import { toast } from "svelte-sonner";
import * as Card from "$lib/components/ui/card";
import * as Dialog from "$lib/components/ui/dialog";
Expand All @@ -16,6 +17,7 @@
import * as m from "$lib/paraglide/messages";
import * as Table from "$lib/components/ui/table";
import * as Tooltip from "$lib/components/ui/tooltip";
import type { Token } from "./message";
let { data }: { data: PageData } = $props();
Expand All @@ -28,7 +30,21 @@
let dialogOpen = $state(false);
let dialogAction = $state("create" as "create" | "update" | "delete" | "reg");
let regToken = $state(undefined as string | undefined);
let disableRegCloseButton = $state(true);
let actuallyDelete = $state(false);
let disableDeleteButton = $derived(!actuallyDelete);
let regToken = $state({
value: "",
firstname: "",
lastname: "",
email: "",
} as Token | undefined);
const generateRegistrationURL = (token: Token | undefined) => {
if (!token) return "";
return `${window.location.origin}/register/${token.value}`;
}
const openUserDelete = (user: User) => {
dialogAction = "delete";
Expand All @@ -50,7 +66,7 @@
dialogOpen = true;
}
const openRegDialog = (token: string) => {
const openRegDialog = (token: Token) => {
dialogAction = "reg";
regToken = token;
dialogOpen = true;
Expand Down Expand Up @@ -101,7 +117,7 @@
if ($message.token) {
closeDialog();
openRegDialog($message.token);
$message.token = undefined;
clearRegToken();
}
}
});
Expand Down Expand Up @@ -185,6 +201,12 @@
<Card.Footer>
<Dialog.Root
bind:open={dialogOpen}
onOpenChange={(open) => {
if (!open) {
clearFormData();
clearRegToken();
}
}}
>
<Dialog.Content>
<form
Expand All @@ -194,38 +216,56 @@
class="space-y-4"
>
<input type="hidden" name="id" bind:value={$formData.id} />
{#if dialogAction === "reg"}
{#if regToken !== undefined && dialogAction === "reg"}
<Dialog.Header>
<Dialog.Title class="capitalize">New User Registration</Dialog.Title>
<Dialog.Description>
<p>User <span class="capitalize">{$formData.firstname} {$formData.lastname} created successfully.</span></p>
<p>Copy the token and send it to the user.</p>
<Dialog.Title class="mb-2">{m.adminNewUser()} - <span class="capitalize">{regToken.firstname} {regToken.lastname}</span> &lt;<span class="lowercase">{regToken.email}</span>&gt;</Dialog.Title>
<Dialog.Description class="space-y-1">
<p class="font-bold dark:text-white">{m.adminNotShownAgain()}</p>
<p>{m.adminNewUserDescription()}</p>
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button onclick={() => {
closeDialog();
clearRegToken();
}}>Close</Button>
<div class="flex flex-row w-full justify-between">
<div class="space-x-2">
<Button variant="outline" onclick={() => {
navigator.clipboard.writeText(generateRegistrationURL(regToken));
toast.success(m.copiedToClipboard());
disableRegCloseButton = false;
}}>{m.copyLink()}</Button>
<Button variant="outline"
href="mailto:{regToken.email}?subject={m.adminEmailSubject()}&body={m.adminEmailBody()}: {generateRegistrationURL(regToken)}"
onclick={() => {disableRegCloseButton = false;}}
>
{m.sendEmail()}
</Button>
</div>
<Button disabled={disableRegCloseButton} onclick={() => {
closeDialog();
clearRegToken();
}}>{m.close()}</Button>
</div>
</Dialog.Footer>
{:else if dialogAction === "delete"}
<Dialog.Header class="space-y-4" id="user-info">
<Dialog.Title class="capitalize">Delete User</Dialog.Title>
<Dialog.Description>
<p>User {$formData.email} will be deleted.</p>
<p>Are you <span class="text-red-700 font-bold underline">ABSOLUTELY</span> sure? This action cannot be undone.</p>
<Dialog.Header class="space-y-2" id="user-info">
<Dialog.Title class="capitalize mb-4">{m.adminUserDelete()}</Dialog.Title>
<Dialog.Description class="space-y-2">
<p>{m.adminUserDeleteDescription({ email: $formData.email})}</p>
<p>{m.adminUserDeleteWarning()}</p>
</Dialog.Description>
</Dialog.Header>
<input type="hidden" name="firstname" bind:value={$formData.firstname} />
<input type="hidden" name="lastname" bind:value={$formData.lastname} />
<input type="hidden" name="email" bind:value={$formData.email} />
<Dialog.Footer>
<Button type="submit" variant="destructive">Delete</Button>
<Dialog.Footer class="flex flex-row !justify-between">
<div class="flex flex-row items-center space-x-2">
<Checkbox id="actually-delete" bind:checked={actuallyDelete}/>
<Label for="actually-delete">{m.adminConfirmDelete()}</Label>
</div>
<Button disabled={disableDeleteButton} type="submit" variant="destructive">{m._delete()}</Button>
</Dialog.Footer>
{:else}
<Dialog.Header>
<Dialog.Title class="capitalize">{dialogAction} User</Dialog.Title>
<Dialog.Description>Description</Dialog.Description>
<Dialog.Title class="capitalize">{dialogAction} {m.users()}</Dialog.Title>
</Dialog.Header>
<Form.Field {form} name="firstname">
<Form.Control let:attrs>
Expand Down Expand Up @@ -254,7 +294,7 @@
<Form.FieldErrors/>
</Form.Field>
<Dialog.Footer>
<Button type="submit">Save</Button>
<Button type="submit">{m._save()}</Button>
</Dialog.Footer>
{/if}
</form>
Expand Down
11 changes: 11 additions & 0 deletions src/routes/admin/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Token = {
value: string,
email: string,
firstname: string,
lastname: string,
}

export type Message = {
text: string | undefined,
token: Token | undefined,
}

0 comments on commit dd91343

Please sign in to comment.