718 lines
29 KiB
Svelte
718 lines
29 KiB
Svelte
<script lang="ts">
|
|
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
|
|
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
|
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
|
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
|
import * as Card from '$lib/components/ui/card/index.js';
|
|
import MessagesSquare from '@lucide/svelte/icons/messages-square';
|
|
import MinusIcon from '@lucide/svelte/icons/minus';
|
|
import PlusIcon from '@lucide/svelte/icons/plus';
|
|
import UserRoundPlus from '@lucide/svelte/icons/user-round-plus';
|
|
import UsersRound from '@lucide/svelte/icons/users-round';
|
|
import CirclePlus from '@lucide/svelte/icons/circle-plus';
|
|
import Input from './ui/input/input.svelte';
|
|
import * as ContextMenu from '$lib/components/ui/context-menu/index.js';
|
|
import Button, { buttonVariants } from './ui/button/button.svelte';
|
|
import User from './extra/User.svelte';
|
|
import type { SessionValidationResult } from '$lib/server/auth';
|
|
import { Status, type AppPSD, type OverviewUser } from '$lib';
|
|
import Label from './ui/label/label.svelte';
|
|
import { toast } from 'svelte-sonner';
|
|
import { enhance } from '$app/forms';
|
|
import { invalidateAll } from '$app/navigation';
|
|
import { fill_overview_data } from '$lib/state.svelte';
|
|
|
|
import { overview_data } from '$lib/state.svelte';
|
|
|
|
let {
|
|
currentPage = $bindable<string | null>(),
|
|
subPage = $bindable<string | null>(),
|
|
psd,
|
|
user,
|
|
...restProps
|
|
}: {
|
|
currentPage: string | null;
|
|
subPage: string | null;
|
|
psd: AppPSD;
|
|
user: SessionValidationResult['user'];
|
|
} = $props();
|
|
</script>
|
|
|
|
<Sidebar.Root {...restProps}>
|
|
<Sidebar.Header>
|
|
<Sidebar.Menu>
|
|
<Sidebar.MenuItem>
|
|
<Sidebar.MenuButton size="lg">
|
|
<div
|
|
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
|
|
>
|
|
<MessagesSquare class="size-4" />
|
|
</div>
|
|
<div class="flex flex-col gap-0.5 leading-none">
|
|
<span class="font-medium">chat.sad.ovh</span>
|
|
</div>
|
|
</Sidebar.MenuButton>
|
|
<div class="flex w-full justify-center gap-2">
|
|
<Dialog.Root>
|
|
<Dialog.Trigger>
|
|
<Button
|
|
variant={user!.friendRequests.length > 0 ? 'destructive' : 'outline'}
|
|
size="icon"
|
|
>
|
|
<UserRoundPlus />
|
|
</Button>
|
|
</Dialog.Trigger>
|
|
|
|
<Dialog.Content class="sm:max-w-106.25">
|
|
<Dialog.Header>
|
|
<Dialog.Title>Add a friend</Dialog.Title>
|
|
<Dialog.Description>
|
|
Add a friend using their username or manage pending requests.
|
|
</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<!-- input to add a new friend -->
|
|
<form
|
|
method="POST"
|
|
action="?/addFriend"
|
|
class="mb-4"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Friend request sent successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not send friend request: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Input name="username" placeholder="username" required class="mb-2" />
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
|
<Button type="submit">Send request</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
|
|
<!-- Tabs for Friend Requests -->
|
|
<Tabs.Root value="outgoing">
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="outgoing">Outgoing</Tabs.Trigger>
|
|
<Tabs.Trigger value="incoming">Incoming</Tabs.Trigger>
|
|
<Tabs.Trigger value="manage">Manage Friends</Tabs.Trigger>
|
|
</Tabs.List>
|
|
|
|
<!-- Outgoing Requests -->
|
|
<Tabs.Content value="outgoing">
|
|
{#if user!.friendRequests.filter((r) => r.fromUser === user!.id).length === 0}
|
|
<p class="text-sm text-muted-foreground">No outgoing requests</p>
|
|
{:else}
|
|
{#each user!.friendRequests.filter((r) => r.fromUser === user!.id) as request (request.id)}
|
|
<Card.Root class="mb-2">
|
|
<Card.Header>
|
|
<Card.Title>{request.toUsername}</Card.Title>
|
|
<Card.Description>Request sent</Card.Description>
|
|
</Card.Header>
|
|
<Card.Footer>
|
|
<form
|
|
method="POST"
|
|
action="?/cancelFriendRequest"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Cancelled friend request successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not cancel friend request: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<input type="hidden" name="requestId" value={request.id} />
|
|
<Button type="submit" variant="outline" size="sm">Cancel</Button>
|
|
</form>
|
|
</Card.Footer>
|
|
</Card.Root>
|
|
{/each}
|
|
{/if}
|
|
</Tabs.Content>
|
|
|
|
<!-- Incoming Requests -->
|
|
<Tabs.Content value="incoming">
|
|
{#if user!.friendRequests.filter((r) => r.toUser === user!.id).length === 0}
|
|
<p class="text-sm text-muted-foreground">No incoming requests</p>
|
|
{:else}
|
|
{#each user!.friendRequests.filter((r) => r.toUser === user!.id) as request (request.id)}
|
|
<Card.Root class="mb-2">
|
|
<Card.Header>
|
|
<Card.Title>{request.fromUsername}</Card.Title>
|
|
<Card.Description>Sent you a friend request</Card.Description>
|
|
</Card.Header>
|
|
<Card.Footer class="flex gap-2">
|
|
<!-- accept friend -->
|
|
<form
|
|
method="POST"
|
|
action="?/addFriend"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Accepted friend request successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not accept friend request: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<input type="hidden" name="userId" value={request.fromUser} />
|
|
<Button type="submit" size="sm">Accept</Button>
|
|
</form>
|
|
<!-- decline friend -->
|
|
<form
|
|
method="POST"
|
|
action="?/cancelFriendRequest"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Cancelled friend request successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not cancel friend request: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<input type="hidden" name="requestId" value={request.id} />
|
|
<Button type="submit" variant="outline" size="sm">Decline</Button>
|
|
</form>
|
|
</Card.Footer>
|
|
</Card.Root>
|
|
{/each}
|
|
{/if}
|
|
</Tabs.Content>
|
|
<Tabs.Content value="manage">
|
|
{#if overview_data.friends.length === 0}
|
|
<p class="text-sm text-muted-foreground">You have no friends added.</p>
|
|
{:else}
|
|
{#each overview_data.friends as friend (friend.id)}
|
|
<Card.Root class="mb-2">
|
|
<Card.Header>
|
|
<Card.Title>{friend.username}</Card.Title>
|
|
<Card.Description>Currently your friend</Card.Description>
|
|
</Card.Header>
|
|
<Card.Footer>
|
|
<form
|
|
method="POST"
|
|
action="?/removeFriend"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Removed friend successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not remove friend: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<input type="hidden" name="userId" value={friend.id} />
|
|
<Button type="submit" variant="destructive" size="sm">
|
|
Remove Friend
|
|
</Button>
|
|
</form>
|
|
</Card.Footer>
|
|
</Card.Root>
|
|
{/each}
|
|
{/if}
|
|
</Tabs.Content>
|
|
</Tabs.Root>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
|
|
<Dialog.Root>
|
|
<Dialog.Trigger>
|
|
<Button variant="outline" size="icon">
|
|
<UsersRound />
|
|
</Button>
|
|
</Dialog.Trigger>
|
|
|
|
<Dialog.Content class="sm:max-w-106.25">
|
|
<form
|
|
method="POST"
|
|
action="?/createGroup"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Created group successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not create group: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Create a group</Dialog.Title>
|
|
<Dialog.Description>Add friends into your group!</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
{#each overview_data.friends as friend (friend.id)}
|
|
<label class="flex items-center gap-2">
|
|
<input type="checkbox" name="member" value={friend.id} />
|
|
<User crown={false} user={friend} />
|
|
</label>
|
|
{/each}
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
|
<Button type="submit">Create group</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
|
|
<Dialog.Root>
|
|
<Dialog.Trigger>
|
|
<Button variant="outline" size="icon">
|
|
<CirclePlus />
|
|
</Button>
|
|
</Dialog.Trigger>
|
|
|
|
<Dialog.Content class="sm:max-w-106.25">
|
|
<form
|
|
method="POST"
|
|
action="?/joinServer"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
console.log(result);
|
|
if (result.type == 'success') {
|
|
toast.success('Joined server successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not join server: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Join a server</Dialog.Title>
|
|
<Dialog.Description>Enter an invite link.</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<Input name="invite" placeholder="invite link" required />
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
|
<Button type="submit">Join</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
|
|
<Dialog.Root>
|
|
<Dialog.Trigger>
|
|
<Button variant="outline" size="icon">
|
|
<PlusIcon />
|
|
</Button>
|
|
</Dialog.Trigger>
|
|
|
|
<Dialog.Content class="sm:max-w-106.25">
|
|
<form
|
|
method="POST"
|
|
action="?/createServer"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Created server successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not create server: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Create a server</Dialog.Title>
|
|
<Dialog.Description>Name your new server.</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<Input name="name" placeholder="Server name" required />
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
|
<Button type="submit">Create</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
</div>
|
|
</Sidebar.MenuItem>
|
|
</Sidebar.Menu>
|
|
</Sidebar.Header>
|
|
<Sidebar.Content>
|
|
<Sidebar.Group>
|
|
<Sidebar.Menu>
|
|
<Collapsible.Root open={true} class="group/collapsible">
|
|
<Sidebar.MenuItem>
|
|
<Collapsible.Trigger>
|
|
<Sidebar.MenuButton>
|
|
Friends
|
|
<PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
|
|
<MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
|
|
</Sidebar.MenuButton>
|
|
</Collapsible.Trigger>
|
|
<Collapsible.Content>
|
|
<Sidebar.MenuSub>
|
|
{#each overview_data.friends as friend (friend.id)}
|
|
<Sidebar.MenuSubItem>
|
|
<Sidebar.MenuSubButton>
|
|
<User
|
|
onclick={(e) => {
|
|
e.preventDefault();
|
|
currentPage = friend.id;
|
|
subPage = null;
|
|
}}
|
|
user={friend}
|
|
crown={false}
|
|
></User>
|
|
</Sidebar.MenuSubButton>
|
|
</Sidebar.MenuSubItem>
|
|
{/each}
|
|
</Sidebar.MenuSub>
|
|
</Collapsible.Content>
|
|
</Sidebar.MenuItem>
|
|
</Collapsible.Root>
|
|
|
|
<Collapsible.Root open={true} class="group/collapsible">
|
|
<Sidebar.MenuItem>
|
|
<Collapsible.Trigger>
|
|
<Sidebar.MenuButton>
|
|
Groups
|
|
<PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
|
|
<MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
|
|
</Sidebar.MenuButton>
|
|
</Collapsible.Trigger>
|
|
<Collapsible.Content>
|
|
<Sidebar.MenuSub>
|
|
{#each overview_data.groups as group (group.id)}
|
|
<Sidebar.MenuSubItem>
|
|
<Sidebar.MenuSubButton>
|
|
<a
|
|
onclick={(e) => {
|
|
e.preventDefault();
|
|
currentPage = group.id;
|
|
subPage = null;
|
|
}}
|
|
href="##"
|
|
>
|
|
{group.name} ({group.members} members)
|
|
</a>
|
|
</Sidebar.MenuSubButton>
|
|
</Sidebar.MenuSubItem>
|
|
{/each}
|
|
</Sidebar.MenuSub>
|
|
</Collapsible.Content>
|
|
</Sidebar.MenuItem>
|
|
</Collapsible.Root>
|
|
|
|
<Collapsible.Root open={true} class="group/collapsible">
|
|
<Sidebar.MenuItem>
|
|
<Collapsible.Trigger>
|
|
<Sidebar.MenuButton>
|
|
Servers
|
|
<PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
|
|
<MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
|
|
</Sidebar.MenuButton>
|
|
</Collapsible.Trigger>
|
|
<Collapsible.Content>
|
|
<Sidebar.MenuSub>
|
|
{#each overview_data.servers as server (server.id)}
|
|
<Dialog.Root>
|
|
<ContextMenu.Root>
|
|
<ContextMenu.Trigger
|
|
><Sidebar.MenuSubItem>
|
|
<Sidebar.MenuSubButton
|
|
onclick={(e) => {
|
|
e.preventDefault();
|
|
currentPage = server.id;
|
|
subPage = null;
|
|
}}
|
|
>
|
|
<img
|
|
src={'https://api.dicebear.com/9.x/glass/svg?seed=' + server.name}
|
|
alt={server.name}
|
|
class="size-6 rounded-full"
|
|
/>
|
|
{server.name}
|
|
</Sidebar.MenuSubButton>
|
|
</Sidebar.MenuSubItem>
|
|
</ContextMenu.Trigger>
|
|
<ContextMenu.Content>
|
|
<ContextMenu.Item>
|
|
<Dialog.Trigger>Create channel</Dialog.Trigger>
|
|
</ContextMenu.Item>
|
|
</ContextMenu.Content>
|
|
</ContextMenu.Root>
|
|
|
|
<Dialog.Content>
|
|
<form
|
|
method="POST"
|
|
action="?/createChannel"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Created channel successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not create channel: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Create a channel</Dialog.Title>
|
|
<Dialog.Description>Add a new channel to this server.</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<div class="space-y-1">
|
|
<Label for="channelName">Channel name</Label>
|
|
<Input
|
|
id="channelName"
|
|
name="channelName"
|
|
placeholder="Channel name"
|
|
required
|
|
minlength={1}
|
|
maxlength={32}
|
|
/>
|
|
<input type="hidden" name="serverId" value={server.id} />
|
|
</div>
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}
|
|
>Cancel</Dialog.Close
|
|
>
|
|
<Button type="submit">Create</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
{#each server.channels as channel (channel.id)}
|
|
<Dialog.Root>
|
|
<ContextMenu.Root>
|
|
<ContextMenu.Trigger>
|
|
<a
|
|
onclick={(e) => {
|
|
e.preventDefault();
|
|
currentPage = server.id;
|
|
subPage = channel.id;
|
|
}}
|
|
href="##"
|
|
class="flex items-center gap-2"
|
|
>
|
|
{channel.name}
|
|
</a>
|
|
</ContextMenu.Trigger>
|
|
<ContextMenu.Content>
|
|
<ContextMenu.Item class="text-red-500 underline">
|
|
<Dialog.Trigger>Delete channel</Dialog.Trigger>
|
|
</ContextMenu.Item>
|
|
</ContextMenu.Content>
|
|
</ContextMenu.Root>
|
|
|
|
<Dialog.Content>
|
|
<form
|
|
method="POST"
|
|
action="?/deleteChannel"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Deleted channel successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not delete channel: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<input type="hidden" name="serverId" value={server.id} />
|
|
<input type="hidden" name="channelId" value={channel.id} />
|
|
|
|
<Dialog.Header>
|
|
<Dialog.Title>Delete this channel</Dialog.Title>
|
|
<Dialog.Description
|
|
>This action is IRREVERSIBLE and will delete all messages in the
|
|
channel. Are you sure you want to proceed?</Dialog.Description
|
|
>
|
|
</Dialog.Header>
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}
|
|
>No</Dialog.Close
|
|
>
|
|
<Button variant="destructive" type="submit">Yes</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
{/each}
|
|
{/each}
|
|
</Sidebar.MenuSub>
|
|
</Collapsible.Content>
|
|
</Sidebar.MenuItem>
|
|
</Collapsible.Root>
|
|
</Sidebar.Menu>
|
|
</Sidebar.Group>
|
|
</Sidebar.Content>
|
|
<Sidebar.Footer class="border-t-2 p-2">
|
|
<Dialog.Root>
|
|
<Dialog.Trigger>
|
|
<User user={user as unknown as OverviewUser} crown={false} />
|
|
</Dialog.Trigger>
|
|
|
|
<Dialog.Content>
|
|
<form
|
|
method="POST"
|
|
action="?/updateProfile"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type == 'success') {
|
|
toast.success('Edited profile successfully');
|
|
}
|
|
|
|
if (result.type == 'error' || result.type == 'failure') {
|
|
toast.error(
|
|
'Could not edit profile: ' +
|
|
(result.type === 'error' ? result.error : result.data?.error)
|
|
);
|
|
}
|
|
|
|
await invalidateAll();
|
|
await fill_overview_data(psd);
|
|
};
|
|
}}
|
|
>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Edit profile</Dialog.Title>
|
|
<Dialog.Description>Update how others see you.</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<div class="">
|
|
<Label for="userName">Username</Label>
|
|
<Input
|
|
id="userName"
|
|
name="userName"
|
|
placeholder="Your name"
|
|
value={user?.username}
|
|
required
|
|
minlength={2}
|
|
maxlength={32}
|
|
/>
|
|
</div>
|
|
|
|
<!-- Presence -->
|
|
<div class="space-y-1">
|
|
<Label for="status">Status</Label>
|
|
<select id="status" name="status" class="input" required>
|
|
<option value={Status.ONLINE} selected={user?.statusOverwrite === Status.ONLINE}
|
|
>Online</option
|
|
>
|
|
<option value={Status.DND} selected={user?.statusOverwrite === Status.DND}
|
|
>Do Not Disturb</option
|
|
>
|
|
<option value={Status.OFFLINE} selected={user?.statusOverwrite === Status.OFFLINE}
|
|
>Offline</option
|
|
>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<Label for="statusMessage">Status message</Label>
|
|
<Input
|
|
id="statusMessage"
|
|
name="statusMessage"
|
|
placeholder="What's going on?"
|
|
value={user?.statusMessage ?? ''}
|
|
maxlength={64}
|
|
/>
|
|
</div>
|
|
|
|
<Dialog.Footer>
|
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
|
<Button type="submit">Save</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
</Sidebar.Footer>
|
|
<Sidebar.Rail />
|
|
</Sidebar.Root>
|