feat: Add functionality to channel creation dialog

This commit is contained in:
April Hall 2025-02-07 11:13:55 -05:00
parent 5cef539040
commit e8f634f759
No known key found for this signature in database
GPG Key ID: A49AC35CB186266C
19 changed files with 209 additions and 125 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -30,12 +30,14 @@
"prettier-plugin-tailwindcss": "^0.6.10",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"sveltekit-superforms": "^2.23.1",
"tailwind-merge": "^3.0.1",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^6.0.0"
"vite": "^6.0.0",
"zod": "^3.24.1"
},
"dependencies": {
"@sveltejs/adapter-node": "^5.2.12",

View File

@ -2,6 +2,13 @@
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import type { SuperValidated, Infer } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms';
import type { NewChannelSchema } from '$lib/types/schema';
import { Label } from '$lib/components/ui/label/index';
let { data }: { data: SuperValidated<Infer<NewChannelSchema>> } = $props();
const { form, errors, constraints, enhance } = superForm(data);
</script>
<Dialog.Root>
@ -10,11 +17,19 @@
<Dialog.Header>
<Dialog.Title>Create Channel</Dialog.Title>
</Dialog.Header>
<form class="grid gap-4 py-4">
<Input id="channelName" name="channelName" placeholder="Channel Name" type="text" />
</form>
<form class="grid gap-4 py-4" use:enhance method="POST" action="/">
<Input
id="channelName"
name="channelName"
placeholder="Channel Name"
type="text"
bind:value={$form.channelName}
aria-invalid={$errors.channelName ? 'true' : undefined}
{...$constraints.channelName} />
{#if $errors.channelName}<Label for="channelName" class="text-red-500 m-0 p-0">{$errors.channelName}</Label>{/if}
<Dialog.Footer>
<Button type="submit">Create</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@ -1,17 +1,26 @@
<script lang="ts">
import MessagesSquare from 'lucide-svelte/icons/messages-square';
import type { SuperValidated } from 'sveltekit-superforms';
import ChannelDialog from './channelDialog.svelte';
import { Button } from '$lib/components/ui/button/index';
import ModeSwitcher from './modeSwitcher.svelte';
import Channel from './channel.svelte';
import type { Snippet } from 'svelte';
interface Props {
data: SuperValidated<
{
channelName: string;
},
any,
{
channelName: string;
}
>;
channels: string[];
children: Snippet;
}
const { channels, children }: Props = $props();
const { data, channels, children }: Props = $props();
</script>
<div class="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
@ -32,7 +41,7 @@
</nav>
</div>
<div class="mt-auto p-4">
<ChannelDialog />
<ChannelDialog {data} />
</div>
</div>
</div>

View File

@ -1,14 +1,14 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import Cross2 from "svelte-radix/Cross2.svelte";
import * as Dialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import Cross2 from 'svelte-radix/Cross2.svelte';
import * as Dialog from './index.js';
import { cn, flyAndScale } from '$lib/utils.js';
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = flyAndScale;
export let transitionConfig: $$Props['transitionConfig'] = {
duration: 200,
};
export { className as class };
@ -20,15 +20,13 @@
{transition}
{transitionConfig}
class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
className
'bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full',
className,
)}
{...$$restProps}
>
{...$$restProps}>
<slot />
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
>
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
<Cross2 class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>

View File

@ -1,16 +1,13 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-muted-foreground text-sm", className)}
{...$$restProps}
>
<DialogPrimitive.Description class={cn('text-muted-foreground text-sm', className)} {...$$restProps}>
<slot />
</DialogPrimitive.Description>

View File

@ -1,16 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<div class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...$$restProps}>
<slot />
</div>

View File

@ -1,13 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
<div class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...$$restProps}>
<slot />
</div>

View File

@ -1,13 +1,13 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import { fade } from 'svelte/transition';
import { cn } from '$lib/utils.js';
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = fade;
export let transitionConfig: $$Props['transitionConfig'] = {
duration: 150,
};
export { className as class };
@ -16,6 +16,5 @@
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ", className)}
{...$$restProps}
/>
class={cn('bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ', className)}
{...$$restProps} />

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { Dialog as DialogPrimitive } from 'bits-ui';
type $$Props = DialogPrimitive.PortalProps;
</script>

View File

@ -1,16 +1,13 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<DialogPrimitive.Title class={cn('text-lg font-semibold leading-none tracking-tight', className)} {...$$restProps}>
<slot />
</DialogPrimitive.Title>

View File

@ -1,12 +1,12 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import { Dialog as DialogPrimitive } from 'bits-ui';
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
import Title from './dialog-title.svelte';
import Portal from './dialog-portal.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;

View File

@ -0,0 +1,7 @@
import Root from './label.svelte';
export {
Root,
//
Root as Label,
};

View File

@ -0,0 +1,15 @@
<script lang="ts">
import { Label as LabelPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
type $$Props = LabelPrimitive.Props;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<LabelPrimitive.Root
class={cn('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', className)}
{...$$restProps}>
<slot />
</LabelPrimitive.Root>

View File

@ -62,6 +62,19 @@ class Db {
}
}
async checkChannel(channel: string): Promise<boolean> {
try {
const res = await this.client.execute(`SELECT table_name FROM system_schema.tables WHERE keyspace_name = 'channels' AND table_name = ?`, [
channel.toLowerCase(),
]);
return res.rowLength !== 0;
} catch (e) {
console.log(`Error checking channel existance: ${e as Error}`);
return false;
}
}
// Get Channels method
async getChannels(): Promise<cassandra.types.Row[] | undefined> {
try {

7
src/lib/types/schema.ts Normal file
View File

@ -0,0 +1,7 @@
import { z } from 'zod';
export const newChannelSchema = z.object({
channelName: z.string().min(1, 'Channel name is required'),
});
export type NewChannelSchema = typeof newChannelSchema;

View File

@ -1,6 +1,10 @@
import { db } from '$lib/server/db';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms';
import { newChannelSchema } from '$lib/types/schema';
export async function load() {
const form = await superValidate(zod(newChannelSchema));
const rows = await db.getChannels();
const channels: string[] = rows
? rows.map((value) => {
@ -10,5 +14,6 @@ export async function load() {
return {
channels,
form,
};
}

View File

@ -4,10 +4,9 @@
import MainLayout from '$lib/components/mainLayout.svelte';
import { ModeWatcher } from 'mode-watcher';
let { data, children }: LayoutProps = $props();
const channels = data.channels;
</script>
<ModeWatcher />
<MainLayout {channels}>
<MainLayout data={data.form} channels={data.channels}>
{@render children()}
</MainLayout>

View File

@ -1,5 +1,29 @@
import { redirect } from '@sveltejs/kit';
import { redirect, fail } from '@sveltejs/kit';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate, message } from 'sveltekit-superforms';
import { newChannelSchema } from '$lib/types/schema';
import type { Actions } from './$types';
import { db } from '$lib/server/db';
export function load(): void {
redirect(308, '/channel/general');
}
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(newChannelSchema));
const channel = form.data.channelName;
if (!form.valid) {
return fail(400, { form });
}
if (await db.checkChannel(channel)) {
return setError(form, 'channelName', 'Channel already exists.');
}
db.createChannel(channel);
return message(form, 'Channel created!');
},
} satisfies Actions;