chat.sad.ovh/src/lib/components/app-sidebar.svelte

416 lines
16 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 Button, { buttonVariants } from './ui/button/button.svelte';
import User from './extra/User.svelte';
import type { SessionValidationResult } from '$lib/server/auth';
import { Status, statuses, type OverviewData, type OverviewUser } from '$lib';
import Label from './ui/label/label.svelte';
let {
currentPage = $bindable<string | null>(),
subPage = $bindable<string | null>(),
data,
user,
...restProps
}: {
currentPage: string | null;
subPage: string | null;
data: OverviewData;
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">
<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">
<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">
<input type="hidden" name="userId" value={request.fromUser} />
<Button type="submit" size="sm">Accept</Button>
</form>
<!-- decline friend -->
<form method="POST" action="?/cancelFriendRequest">
<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 data.friends.length === 0}
<p class="text-sm text-muted-foreground">You have no friends added.</p>
{:else}
{#each 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">
<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">
<Dialog.Header>
<Dialog.Title>Create a group</Dialog.Title>
<Dialog.Description>Add friends into your group!</Dialog.Description>
</Dialog.Header>
{#each 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">
<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">
<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 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 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 data.servers as server (server.id)}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton
onclick={(e) => {
e.preventDefault();
currentPage = server.id;
subPage = null;
}}
>
<img
src={'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + server.name}
alt={server.name}
class="size-6 rounded-full"
/>
{server.name}
</Sidebar.MenuSubButton>
</Sidebar.MenuSubItem>
{#each server.channels as channel (channel.id)}
<a
onclick={(e) => {
e.preventDefault();
currentPage = server.id;
subPage = channel.id;
}}
href="##"
class="flex items-center gap-2"
>
{channel.name}
</a>
{/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">
<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>