From 8dedbf208c4441b79e2f66a5de726a0ff776b288 Mon Sep 17 00:00:00 2001 From: lionarius Date: Sun, 19 May 2024 18:59:16 +0300 Subject: [PATCH] a --- .prettierrc | 24 +++-- bun.lockb | Bin 126147 -> 150028 bytes package.json | 11 +- src/app.d.ts | 16 +-- src/app.html | 36 +++++-- src/hooks.server.ts | 6 ++ src/lib/api/channel.ts | 30 ++++++ src/lib/api/message.ts | 10 ++ src/lib/api/user.ts | 22 ++++ src/lib/api/utils.ts | 31 ++++++ .../ui/avatar/avatar-fallback.svelte | 16 +++ .../components/ui/avatar/avatar-image.svelte | 18 ++++ src/lib/components/ui/avatar/avatar.svelte | 18 ++++ src/lib/components/ui/avatar/index.ts | 13 +++ src/lib/components/ui/button/button.svelte | 25 +++++ src/lib/components/ui/button/index.ts | 49 +++++++++ .../dropdown-menu-checkbox-item.svelte | 35 +++++++ .../dropdown-menu-content.svelte | 27 +++++ .../dropdown-menu/dropdown-menu-item.svelte | 31 ++++++ .../dropdown-menu/dropdown-menu-label.svelte | 19 ++++ .../dropdown-menu-radio-group.svelte | 11 ++ .../dropdown-menu-radio-item.svelte | 35 +++++++ .../dropdown-menu-separator.svelte | 14 +++ .../dropdown-menu-shortcut.svelte | 13 +++ .../dropdown-menu-sub-content.svelte | 30 ++++++ .../dropdown-menu-sub-trigger.svelte | 32 ++++++ src/lib/components/ui/dropdown-menu/index.ts | 48 +++++++++ src/lib/components/ui/form/form-button.svelte | 10 ++ .../ui/form/form-description.svelte | 17 ++++ .../ui/form/form-element-field.svelte | 25 +++++ .../ui/form/form-field-errors.svelte | 26 +++++ src/lib/components/ui/form/form-field.svelte | 25 +++++ .../components/ui/form/form-fieldset.svelte | 30 ++++++ src/lib/components/ui/form/form-label.svelte | 17 ++++ src/lib/components/ui/form/form-legend.svelte | 17 ++++ src/lib/components/ui/form/index.ts | 33 ++++++ src/lib/components/ui/input/index.ts | 29 ++++++ src/lib/components/ui/input/input.svelte | 42 ++++++++ src/lib/components/ui/label/index.ts | 7 ++ src/lib/components/ui/label/label.svelte | 21 ++++ src/lib/components/ui/popover/index.ts | 17 ++++ .../ui/popover/popover-content.svelte | 22 ++++ src/lib/components/ui/scroll-area/index.ts | 10 ++ .../scroll-area/scroll-area-scrollbar.svelte | 27 +++++ .../ui/scroll-area/scroll-area.svelte | 66 ++++++++++++ src/lib/components/ui/separator/index.ts | 7 ++ .../components/ui/separator/separator.svelte | 22 ++++ src/lib/components/ui/skeleton/index.ts | 7 ++ .../components/ui/skeleton/skeleton.svelte | 11 ++ src/lib/components/ui/sonner/index.ts | 1 + src/lib/components/ui/sonner/sonner.svelte | 20 ++++ src/lib/components/ui/switch/index.ts | 7 ++ src/lib/components/ui/switch/switch.svelte | 28 ++++++ src/lib/components/ui/textarea/index.ts | 28 ++++++ .../components/ui/textarea/textarea.svelte | 38 +++++++ src/lib/constants.ts | 1 + src/lib/stores/cache/messages.ts | 42 ++++++++ src/lib/stores/cache/users.ts | 47 +++++++++ src/lib/stores/user.ts | 22 ++++ src/lib/types.ts | 36 +++++++ src/routes/(auth)/+layout.server.ts | 19 ++++ src/routes/(auth)/+layout.svelte | 12 +++ src/routes/(auth)/+layout.ts | 1 + .../chats/(components)/(chat)/chat.svelte | 44 ++++++++ .../(components)/(chat)/message-area.svelte | 27 +++++ .../chats/(components)/(chat)/message.svelte | 46 +++++++++ .../(components)/(chat)/text-field.svelte | 56 +++++++++++ .../chats/(components)/chat-list-item.svelte | 25 +++++ .../chats/(components)/chat-list.svelte | 32 ++++++ .../chats/(components)/sidebar-header.svelte | 43 ++++++++ .../(auth)/chats/(components)/sidebar.svelte | 18 ++++ src/routes/(auth)/chats/+layout.svelte | 67 ++++++++++++ src/routes/(auth)/chats/+layout.ts | 11 ++ src/routes/(auth)/chats/+page.svelte | 5 + .../(auth)/chats/[username]/+page.svelte | 13 +++ src/routes/(auth)/chats/[username]/+page.ts | 18 ++++ src/routes/+layout.svelte | 8 +- src/routes/+page.server.ts | 6 ++ src/routes/+page.svelte | 2 - src/routes/login/+page.server.ts | 35 +++++++ src/routes/login/+page.svelte | 15 +++ src/routes/login/login-form.svelte | 79 +++++++++++++++ src/routes/logout/+page.server.ts | 7 ++ src/routes/logout/+page.svelte | 11 ++ src/routes/register/+page.server.ts | 29 ++++++ src/routes/register/+page.svelte | 14 +++ src/routes/register/register-form.svelte | 95 ++++++++++++++++++ static/default-avatar.png | Bin 0 -> 15131 bytes tailwind.config.ts | 2 +- 89 files changed, 2085 insertions(+), 33 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/api/channel.ts create mode 100644 src/lib/api/message.ts create mode 100644 src/lib/api/user.ts create mode 100644 src/lib/api/utils.ts create mode 100644 src/lib/components/ui/avatar/avatar-fallback.svelte create mode 100644 src/lib/components/ui/avatar/avatar-image.svelte create mode 100644 src/lib/components/ui/avatar/avatar.svelte create mode 100644 src/lib/components/ui/avatar/index.ts create mode 100644 src/lib/components/ui/button/button.svelte create mode 100644 src/lib/components/ui/button/index.ts create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte create mode 100644 src/lib/components/ui/dropdown-menu/index.ts create mode 100644 src/lib/components/ui/form/form-button.svelte create mode 100644 src/lib/components/ui/form/form-description.svelte create mode 100644 src/lib/components/ui/form/form-element-field.svelte create mode 100644 src/lib/components/ui/form/form-field-errors.svelte create mode 100644 src/lib/components/ui/form/form-field.svelte create mode 100644 src/lib/components/ui/form/form-fieldset.svelte create mode 100644 src/lib/components/ui/form/form-label.svelte create mode 100644 src/lib/components/ui/form/form-legend.svelte create mode 100644 src/lib/components/ui/form/index.ts create mode 100644 src/lib/components/ui/input/index.ts create mode 100644 src/lib/components/ui/input/input.svelte create mode 100644 src/lib/components/ui/label/index.ts create mode 100644 src/lib/components/ui/label/label.svelte create mode 100644 src/lib/components/ui/popover/index.ts create mode 100644 src/lib/components/ui/popover/popover-content.svelte create mode 100644 src/lib/components/ui/scroll-area/index.ts create mode 100644 src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte create mode 100644 src/lib/components/ui/scroll-area/scroll-area.svelte create mode 100644 src/lib/components/ui/separator/index.ts create mode 100644 src/lib/components/ui/separator/separator.svelte create mode 100644 src/lib/components/ui/skeleton/index.ts create mode 100644 src/lib/components/ui/skeleton/skeleton.svelte create mode 100644 src/lib/components/ui/sonner/index.ts create mode 100644 src/lib/components/ui/sonner/sonner.svelte create mode 100644 src/lib/components/ui/switch/index.ts create mode 100644 src/lib/components/ui/switch/switch.svelte create mode 100644 src/lib/components/ui/textarea/index.ts create mode 100644 src/lib/components/ui/textarea/textarea.svelte create mode 100644 src/lib/constants.ts create mode 100644 src/lib/stores/cache/messages.ts create mode 100644 src/lib/stores/cache/users.ts create mode 100644 src/lib/stores/user.ts create mode 100644 src/lib/types.ts create mode 100644 src/routes/(auth)/+layout.server.ts create mode 100644 src/routes/(auth)/+layout.svelte create mode 100644 src/routes/(auth)/+layout.ts create mode 100644 src/routes/(auth)/chats/(components)/(chat)/chat.svelte create mode 100644 src/routes/(auth)/chats/(components)/(chat)/message-area.svelte create mode 100644 src/routes/(auth)/chats/(components)/(chat)/message.svelte create mode 100644 src/routes/(auth)/chats/(components)/(chat)/text-field.svelte create mode 100644 src/routes/(auth)/chats/(components)/chat-list-item.svelte create mode 100644 src/routes/(auth)/chats/(components)/chat-list.svelte create mode 100644 src/routes/(auth)/chats/(components)/sidebar-header.svelte create mode 100644 src/routes/(auth)/chats/(components)/sidebar.svelte create mode 100644 src/routes/(auth)/chats/+layout.svelte create mode 100644 src/routes/(auth)/chats/+layout.ts create mode 100644 src/routes/(auth)/chats/+page.svelte create mode 100644 src/routes/(auth)/chats/[username]/+page.svelte create mode 100644 src/routes/(auth)/chats/[username]/+page.ts create mode 100644 src/routes/+page.server.ts delete mode 100644 src/routes/+page.svelte create mode 100644 src/routes/login/+page.server.ts create mode 100644 src/routes/login/+page.svelte create mode 100644 src/routes/login/login-form.svelte create mode 100644 src/routes/logout/+page.server.ts create mode 100644 src/routes/logout/+page.svelte create mode 100644 src/routes/register/+page.server.ts create mode 100644 src/routes/register/+page.svelte create mode 100644 src/routes/register/register-form.svelte create mode 100644 static/default-avatar.png diff --git a/.prettierrc b/.prettierrc index 8bc6e86..d173948 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,18 @@ { - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": [ + "prettier-plugin-svelte", + "prettier-plugin-tailwindcss" + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 58960aaaec8dc77a70d8319e06066c755c54018d..9931be6a8bb56d9f4fc2c9ec180acfa1eb5fa35d 100644 GIT binary patch delta 36928 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 delta 22897 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 + + - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - + + + + + + + %sveltekit.head% + + + + + +
%sveltekit.body%
+ + + \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..f994583 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,6 @@ +import type { Handle } from "@sveltejs/kit"; + +export const handle: Handle = async ({ event, resolve }) => { + const response = await resolve(event); + return response; +}; \ No newline at end of file diff --git a/src/lib/api/channel.ts b/src/lib/api/channel.ts new file mode 100644 index 0000000..44f7b5d --- /dev/null +++ b/src/lib/api/channel.ts @@ -0,0 +1,30 @@ +import type { Channel, Message } from '../types'; +import { apiRequest } from './utils'; + +export async function getAllChannels() { + return await apiRequest('/channel', 'get'); +} + +export async function getChannelById(channelId: number) { + return await apiRequest(`/channel/${channelId}`, 'get'); +} + +export async function createChannel(name: string) { + return await apiRequest('/channel', 'post', { data: { name } }); +} + +export async function deleteChannel(channelId: number) { + return await apiRequest(`/channel/${channelId}`, 'delete'); +} + +export async function addUserToChannel(channelId: number, userId: number) { + return await apiRequest(`/channel/${channelId}/user/${userId}`, 'post'); +} + +export async function removeUserFromChannel(channelId: number, userId: number) { + return await apiRequest(`/channel/${channelId}/user/${userId}`, 'delete'); +} + +export async function getMessagesByChannelId(channelId: number, before_id: number, limit?: number) { + return await apiRequest(`/channel/${channelId}/message`, 'get', { data: { before_id, limit } }); +} \ No newline at end of file diff --git a/src/lib/api/message.ts b/src/lib/api/message.ts new file mode 100644 index 0000000..111f0f4 --- /dev/null +++ b/src/lib/api/message.ts @@ -0,0 +1,10 @@ +import type { Message } from '../types'; +import { apiRequest } from './utils'; + +export async function getMessageById(messageId: number) { + return await apiRequest(`/message/${messageId}`, 'get'); +} + +export async function createMessage(channelId: number, content: string) { + return await apiRequest('/message', 'post', { data: { channelId, content } }); +} \ No newline at end of file diff --git a/src/lib/api/user.ts b/src/lib/api/user.ts new file mode 100644 index 0000000..2763692 --- /dev/null +++ b/src/lib/api/user.ts @@ -0,0 +1,22 @@ +import type { Token, User } from '../types'; +import { apiRequest } from './utils'; + +export async function getByToken(token: string | undefined | null) { + return await apiRequest('/user/me', 'get', { token: token as string | undefined }); +} + +export async function getUserById(userId: number) { + return await apiRequest(`/user/${userId}`, 'get'); +} + +export async function getUserByUsername(username: string) { + return await apiRequest(`/user/username/${username}`, 'get'); +} + +export async function loginUser(username: string, password: string) { + return await apiRequest('/user/login', 'post', { data: { username, password } }); +} + +export async function registerUser(username: string, password: string) { + return await apiRequest('/user/register', 'post', { data: { username, password } }); +} \ No newline at end of file diff --git a/src/lib/api/utils.ts b/src/lib/api/utils.ts new file mode 100644 index 0000000..1e40da8 --- /dev/null +++ b/src/lib/api/utils.ts @@ -0,0 +1,31 @@ +import { API_URL } from "$lib/constants"; +import { getUserToken } from "$lib/stores/user"; +import type { ErrorResponse } from "$lib/types"; +import type { AxiosRequestConfig } from "axios"; +import axios from "axios"; + + + +export async function apiRequest(path: string, method: 'get' | 'post' | 'put' | 'delete', options?: AxiosRequestConfig & { token?: string }): Promise { + const url = API_URL + path; + + const token = options?.token || getUserToken(); + + options = { + url, + method, + headers: { + ...options?.headers, + 'Authorization': `Bearer ${token}` + }, + validateStatus: () => true, + ...options, + } + + const response = await axios.request(options); + + if (response.status === 200) + return response.data as T; + else + return response.data as ErrorResponse; +} \ No newline at end of file diff --git a/src/lib/components/ui/avatar/avatar-fallback.svelte b/src/lib/components/ui/avatar/avatar-fallback.svelte new file mode 100644 index 0000000..865fc40 --- /dev/null +++ b/src/lib/components/ui/avatar/avatar-fallback.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/src/lib/components/ui/avatar/avatar-image.svelte b/src/lib/components/ui/avatar/avatar-image.svelte new file mode 100644 index 0000000..6558dc4 --- /dev/null +++ b/src/lib/components/ui/avatar/avatar-image.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/ui/avatar/avatar.svelte b/src/lib/components/ui/avatar/avatar.svelte new file mode 100644 index 0000000..ba1379b --- /dev/null +++ b/src/lib/components/ui/avatar/avatar.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/avatar/index.ts b/src/lib/components/ui/avatar/index.ts new file mode 100644 index 0000000..d06457b --- /dev/null +++ b/src/lib/components/ui/avatar/index.ts @@ -0,0 +1,13 @@ +import Root from "./avatar.svelte"; +import Image from "./avatar-image.svelte"; +import Fallback from "./avatar-fallback.svelte"; + +export { + Root, + Image, + Fallback, + // + Root as Avatar, + Image as AvatarImage, + Fallback as AvatarFallback, +}; diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..86827f3 --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..a927293 --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,49 @@ +import { type VariantProps, tv } from "tailwind-variants"; +import type { Button as ButtonPrimitive } from "bits-ui"; +import Root from "./button.svelte"; + +const buttonVariants = tv({ + base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +type Variant = VariantProps["variant"]; +type Size = VariantProps["size"]; + +type Props = ButtonPrimitive.Props & { + variant?: Variant; + size?: Size; +}; + +type Events = ButtonPrimitive.Events; + +export { + Root, + type Props, + type Events, + // + Root as Button, + type Props as ButtonProps, + type Events as ButtonEvents, + buttonVariants, +}; diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..cbca3c5 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..a94b527 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..9a05d4b --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..43f1527 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..1c74ae1 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..79a48ee --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..e14d078 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,14 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..d8c7378 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,13 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..ff20507 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,30 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..9ba3916 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,32 @@ + + + + + + diff --git a/src/lib/components/ui/dropdown-menu/index.ts b/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..c1749e9 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,48 @@ +import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; + +const Sub = DropdownMenuPrimitive.Sub; +const Root = DropdownMenuPrimitive.Root; +const Trigger = DropdownMenuPrimitive.Trigger; +const Group = DropdownMenuPrimitive.Group; + +export { + Sub, + Root, + Item, + Label, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as DropdownMenu, + Sub as DropdownMenuSub, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + Group as DropdownMenuGroup, + Content as DropdownMenuContent, + Trigger as DropdownMenuTrigger, + Shortcut as DropdownMenuShortcut, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + RadioGroup as DropdownMenuRadioGroup, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + CheckboxItem as DropdownMenuCheckboxItem, +}; diff --git a/src/lib/components/ui/form/form-button.svelte b/src/lib/components/ui/form/form-button.svelte new file mode 100644 index 0000000..087c839 --- /dev/null +++ b/src/lib/components/ui/form/form-button.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/src/lib/components/ui/form/form-description.svelte b/src/lib/components/ui/form/form-description.svelte new file mode 100644 index 0000000..7d36254 --- /dev/null +++ b/src/lib/components/ui/form/form-description.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/src/lib/components/ui/form/form-element-field.svelte b/src/lib/components/ui/form/form-element-field.svelte new file mode 100644 index 0000000..2de747e --- /dev/null +++ b/src/lib/components/ui/form/form-element-field.svelte @@ -0,0 +1,25 @@ + + + + + +
+ +
+
diff --git a/src/lib/components/ui/form/form-field-errors.svelte b/src/lib/components/ui/form/form-field-errors.svelte new file mode 100644 index 0000000..9395326 --- /dev/null +++ b/src/lib/components/ui/form/form-field-errors.svelte @@ -0,0 +1,26 @@ + + + + + {#each errors as error} +
{error}
+ {/each} +
+
diff --git a/src/lib/components/ui/form/form-field.svelte b/src/lib/components/ui/form/form-field.svelte new file mode 100644 index 0000000..6e958a3 --- /dev/null +++ b/src/lib/components/ui/form/form-field.svelte @@ -0,0 +1,25 @@ + + + + + +
+ +
+
diff --git a/src/lib/components/ui/form/form-fieldset.svelte b/src/lib/components/ui/form/form-fieldset.svelte new file mode 100644 index 0000000..81e8f1b --- /dev/null +++ b/src/lib/components/ui/form/form-fieldset.svelte @@ -0,0 +1,30 @@ + + + + + + + diff --git a/src/lib/components/ui/form/form-label.svelte b/src/lib/components/ui/form/form-label.svelte new file mode 100644 index 0000000..fcd1028 --- /dev/null +++ b/src/lib/components/ui/form/form-label.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/form/form-legend.svelte b/src/lib/components/ui/form/form-legend.svelte new file mode 100644 index 0000000..3b1387c --- /dev/null +++ b/src/lib/components/ui/form/form-legend.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/src/lib/components/ui/form/index.ts b/src/lib/components/ui/form/index.ts new file mode 100644 index 0000000..0713927 --- /dev/null +++ b/src/lib/components/ui/form/index.ts @@ -0,0 +1,33 @@ +import * as FormPrimitive from "formsnap"; +import Description from "./form-description.svelte"; +import Label from "./form-label.svelte"; +import FieldErrors from "./form-field-errors.svelte"; +import Field from "./form-field.svelte"; +import Fieldset from "./form-fieldset.svelte"; +import Legend from "./form-legend.svelte"; +import ElementField from "./form-element-field.svelte"; +import Button from "./form-button.svelte"; + +const Control = FormPrimitive.Control; + +export { + Field, + Control, + Label, + Button, + FieldErrors, + Description, + Fieldset, + Legend, + ElementField, + // + Field as FormField, + Control as FormControl, + Description as FormDescription, + Label as FormLabel, + FieldErrors as FormFieldErrors, + Fieldset as FormFieldset, + Legend as FormLegend, + ElementField as FormElementField, + Button as FormButton, +}; diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts new file mode 100644 index 0000000..75e3bc2 --- /dev/null +++ b/src/lib/components/ui/input/index.ts @@ -0,0 +1,29 @@ +import Root from "./input.svelte"; + +export type FormInputEvent = T & { + currentTarget: EventTarget & HTMLInputElement; +}; +export type InputEvents = { + blur: FormInputEvent; + change: FormInputEvent; + click: FormInputEvent; + focus: FormInputEvent; + focusin: FormInputEvent; + focusout: FormInputEvent; + keydown: FormInputEvent; + keypress: FormInputEvent; + keyup: FormInputEvent; + mouseover: FormInputEvent; + mouseenter: FormInputEvent; + mouseleave: FormInputEvent; + mousemove: FormInputEvent; + paste: FormInputEvent; + input: FormInputEvent; + wheel: FormInputEvent; +}; + +export { + Root, + // + Root as Input, +}; diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte new file mode 100644 index 0000000..5824137 --- /dev/null +++ b/src/lib/components/ui/input/input.svelte @@ -0,0 +1,42 @@ + + + diff --git a/src/lib/components/ui/label/index.ts b/src/lib/components/ui/label/index.ts new file mode 100644 index 0000000..8bfca0b --- /dev/null +++ b/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from "./label.svelte"; + +export { + Root, + // + Root as Label, +}; diff --git a/src/lib/components/ui/label/label.svelte b/src/lib/components/ui/label/label.svelte new file mode 100644 index 0000000..2a7d479 --- /dev/null +++ b/src/lib/components/ui/label/label.svelte @@ -0,0 +1,21 @@ + + + + + diff --git a/src/lib/components/ui/popover/index.ts b/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..63aecf9 --- /dev/null +++ b/src/lib/components/ui/popover/index.ts @@ -0,0 +1,17 @@ +import { Popover as PopoverPrimitive } from "bits-ui"; +import Content from "./popover-content.svelte"; +const Root = PopoverPrimitive.Root; +const Trigger = PopoverPrimitive.Trigger; +const Close = PopoverPrimitive.Close; + +export { + Root, + Content, + Trigger, + Close, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose, +}; diff --git a/src/lib/components/ui/popover/popover-content.svelte b/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..794436c --- /dev/null +++ b/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/src/lib/components/ui/scroll-area/index.ts b/src/lib/components/ui/scroll-area/index.ts new file mode 100644 index 0000000..e86a25b --- /dev/null +++ b/src/lib/components/ui/scroll-area/index.ts @@ -0,0 +1,10 @@ +import Scrollbar from "./scroll-area-scrollbar.svelte"; +import Root from "./scroll-area.svelte"; + +export { + Root, + Scrollbar, + //, + Root as ScrollArea, + Scrollbar as ScrollAreaScrollbar, +}; diff --git a/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte new file mode 100644 index 0000000..a97f3bb --- /dev/null +++ b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte @@ -0,0 +1,27 @@ + + + + + + diff --git a/src/lib/components/ui/scroll-area/scroll-area.svelte b/src/lib/components/ui/scroll-area/scroll-area.svelte new file mode 100644 index 0000000..d9f23c9 --- /dev/null +++ b/src/lib/components/ui/scroll-area/scroll-area.svelte @@ -0,0 +1,66 @@ + + + + + + + + + {#if orientation === 'vertical' || orientation === 'both'} + + {/if} + {#if orientation === 'horizontal' || orientation === 'both'} + + {/if} + + diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts new file mode 100644 index 0000000..82442d2 --- /dev/null +++ b/src/lib/components/ui/separator/index.ts @@ -0,0 +1,7 @@ +import Root from "./separator.svelte"; + +export { + Root, + // + Root as Separator, +}; diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte new file mode 100644 index 0000000..be3843a --- /dev/null +++ b/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/skeleton/index.ts b/src/lib/components/ui/skeleton/index.ts new file mode 100644 index 0000000..186db21 --- /dev/null +++ b/src/lib/components/ui/skeleton/index.ts @@ -0,0 +1,7 @@ +import Root from "./skeleton.svelte"; + +export { + Root, + // + Root as Skeleton, +}; diff --git a/src/lib/components/ui/skeleton/skeleton.svelte b/src/lib/components/ui/skeleton/skeleton.svelte new file mode 100644 index 0000000..640f112 --- /dev/null +++ b/src/lib/components/ui/skeleton/skeleton.svelte @@ -0,0 +1,11 @@ + + +
diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..7d5b2f1 --- /dev/null +++ b/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/switch/index.ts b/src/lib/components/ui/switch/index.ts new file mode 100644 index 0000000..f5533db --- /dev/null +++ b/src/lib/components/ui/switch/index.ts @@ -0,0 +1,7 @@ +import Root from "./switch.svelte"; + +export { + Root, + // + Root as Switch, +}; diff --git a/src/lib/components/ui/switch/switch.svelte b/src/lib/components/ui/switch/switch.svelte new file mode 100644 index 0000000..c6bcfd2 --- /dev/null +++ b/src/lib/components/ui/switch/switch.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/src/lib/components/ui/textarea/index.ts b/src/lib/components/ui/textarea/index.ts new file mode 100644 index 0000000..6eb6ba3 --- /dev/null +++ b/src/lib/components/ui/textarea/index.ts @@ -0,0 +1,28 @@ +import Root from "./textarea.svelte"; + +type FormTextareaEvent = T & { + currentTarget: EventTarget & HTMLTextAreaElement; +}; + +type TextareaEvents = { + blur: FormTextareaEvent; + change: FormTextareaEvent; + click: FormTextareaEvent; + focus: FormTextareaEvent; + keydown: FormTextareaEvent; + keypress: FormTextareaEvent; + keyup: FormTextareaEvent; + mouseover: FormTextareaEvent; + mouseenter: FormTextareaEvent; + mouseleave: FormTextareaEvent; + paste: FormTextareaEvent; + input: FormTextareaEvent; +}; + +export { + Root, + // + Root as Textarea, + type TextareaEvents, + type FormTextareaEvent, +}; diff --git a/src/lib/components/ui/textarea/textarea.svelte b/src/lib/components/ui/textarea/textarea.svelte new file mode 100644 index 0000000..d786257 --- /dev/null +++ b/src/lib/components/ui/textarea/textarea.svelte @@ -0,0 +1,38 @@ + + + diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..1c334e9 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1 @@ +export const API_URL = 'http://localhost:1234'; \ No newline at end of file diff --git a/src/lib/stores/cache/messages.ts b/src/lib/stores/cache/messages.ts new file mode 100644 index 0000000..cbb0136 --- /dev/null +++ b/src/lib/stores/cache/messages.ts @@ -0,0 +1,42 @@ +import { getMessageById } from "$lib/api/message"; +import { isErrorResponse, type Message } from "$lib/types"; +import { get, writable, type Writable } from "svelte/store"; + +const messagesCache: Writable> = writable(new Map()); + +const runningCaches = new Set(); + +export function addMessageToCache(message: Message) { + messagesCache.update((messages) => messages.set(message.id, message)); +} + +export async function getCachedMessage(messageId: number): Promise { + const cached = get(messagesCache).get(messageId); + + if (cached) + return cached; + + if (runningCaches.has(messageId)) + return await new Promise((resolve) => { + messagesCache.subscribe((users) => { + + const user = users.get(messageId); + if (user) { + runningCaches.delete(messageId); + resolve(user); + } + }); + }); + + runningCaches.add(messageId); + + const response = await getMessageById(messageId); + if (isErrorResponse(response)) + return null; + + const message = response as Message; + + addMessageToCache(message); + + return message; +} \ No newline at end of file diff --git a/src/lib/stores/cache/users.ts b/src/lib/stores/cache/users.ts new file mode 100644 index 0000000..f9a469f --- /dev/null +++ b/src/lib/stores/cache/users.ts @@ -0,0 +1,47 @@ +import { getUserById } from "$lib/api/user"; +import { isErrorResponse, type User } from "$lib/types"; +import { get, writable, type Writable } from "svelte/store"; + +const usersCache: Writable> = writable(new Map()); + +const runningCaches = new Set(); + +usersCache.subscribe((users) => { + console.log(`Cached users: ${JSON.stringify([...users.values()])}`); +}) + +export function addUserToCache(user: User) { + usersCache.update((users) => users.set(user.id, user)); +} + +export async function getCachedUser(userId: number): Promise { + const cachedUser = get(usersCache).get(userId); + + if (cachedUser) + return cachedUser; + + if (runningCaches.has(userId)) { + return await new Promise((resolve) => { + usersCache.subscribe((users) => { + console.log(`subsribed called`); + const user = users.get(userId); + if (user) { + runningCaches.delete(userId); + resolve(user); + } + }); + }); + } + + runningCaches.add(userId); + + const response = await getUserById(userId); + if (isErrorResponse(response)) + return null; + + const user = response as User; + + addUserToCache(user); + + return user; +} \ No newline at end of file diff --git a/src/lib/stores/user.ts b/src/lib/stores/user.ts new file mode 100644 index 0000000..7b71f61 --- /dev/null +++ b/src/lib/stores/user.ts @@ -0,0 +1,22 @@ +import { derived, get, writable } from "svelte/store"; +import { isErrorResponse, type User } from "../types"; +import { getByToken } from "$lib/api/user"; + +export const token = writable(null); + +token.subscribe((token) => { + console.log(`updated token: ${JSON.stringify(token)}`); +}) + +export const user = derived(token, ($token, set) => { + getByToken($token).then((response) => { + if (isErrorResponse(response)) + set(null); + else + set(response); + }) +}); + +export function getUserToken(): string | null { + return get(token); +} \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..c71b2ff --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,36 @@ +export type ErrorResponse = { + error: string; +} + +export function isErrorResponse(data: unknown): data is ErrorResponse { + return ((data as ErrorResponse).error !== undefined); +} + +export type Token = { + token: string; + userId: number; + createdAt: Date; + expiresAt: Date; +} + +export type User = { + id: number; + username: string; + avatar: string | null; + createdAt: Date; +}; + +export type Message = { + id: number; + channelId: number; + authorId: number; + content: string; + createdAt: Date; +}; + +export type Channel = { + id: number; + name: string; + lastMessageId: number | null; + createdAt: Date; +}; \ No newline at end of file diff --git a/src/routes/(auth)/+layout.server.ts b/src/routes/(auth)/+layout.server.ts new file mode 100644 index 0000000..9c9f4fc --- /dev/null +++ b/src/routes/(auth)/+layout.server.ts @@ -0,0 +1,19 @@ +import { redirect } from '@sveltejs/kit'; +import type { LayoutServerLoad } from './$types'; +import { getByToken } from '$lib/api/user'; +import { isErrorResponse } from '$lib/types'; + +export const load = (async ({ cookies }) => { + const token = cookies.get('token'); + + if (!token) + return redirect(302, '/logout'); + + const user = await getByToken(token) + if (isErrorResponse(user)) + return redirect(302, '/logout'); + + console.log(`User: ${JSON.stringify(user)}`) + + return { token, user }; +}) satisfies LayoutServerLoad; \ No newline at end of file diff --git a/src/routes/(auth)/+layout.svelte b/src/routes/(auth)/+layout.svelte new file mode 100644 index 0000000..7fba21f --- /dev/null +++ b/src/routes/(auth)/+layout.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/routes/(auth)/+layout.ts b/src/routes/(auth)/+layout.ts new file mode 100644 index 0000000..e2d9be0 --- /dev/null +++ b/src/routes/(auth)/+layout.ts @@ -0,0 +1 @@ +export const ssr = false \ No newline at end of file diff --git a/src/routes/(auth)/chats/(components)/(chat)/chat.svelte b/src/routes/(auth)/chats/(components)/(chat)/chat.svelte new file mode 100644 index 0000000..f6839ca --- /dev/null +++ b/src/routes/(auth)/chats/(components)/(chat)/chat.svelte @@ -0,0 +1,44 @@ + + +
+
+
+ + + + {username[0].toUpperCase()} + + {username} +
+
+ +
+ +
+
+ { + messages = [ + ...messages, + { content: message, createdAt: new Date(), authorId: 1, id: 1 } + ]; + + setTimeout(() => { + messageArea.scroll('bottom'); + }, 100); + }} + /> +
+
diff --git a/src/routes/(auth)/chats/(components)/(chat)/message-area.svelte b/src/routes/(auth)/chats/(components)/(chat)/message-area.svelte new file mode 100644 index 0000000..0972917 --- /dev/null +++ b/src/routes/(auth)/chats/(components)/(chat)/message-area.svelte @@ -0,0 +1,27 @@ + + +
+ +
+ {#each messages as message} + + {/each} +
+
+
diff --git a/src/routes/(auth)/chats/(components)/(chat)/message.svelte b/src/routes/(auth)/chats/(components)/(chat)/message.svelte new file mode 100644 index 0000000..e4eb1ec --- /dev/null +++ b/src/routes/(auth)/chats/(components)/(chat)/message.svelte @@ -0,0 +1,46 @@ + + +
+
+ {#if !isSelf} + + {username[0].toUpperCase()} + + {/if} + +
+ + {message.content} + + {message.createdAt.toLocaleString()} + +
+ {#if isSelf} + + {username[0].toUpperCase()} + + {/if} +
+
diff --git a/src/routes/(auth)/chats/(components)/(chat)/text-field.svelte b/src/routes/(auth)/chats/(components)/(chat)/text-field.svelte new file mode 100644 index 0000000..25efcf0 --- /dev/null +++ b/src/routes/(auth)/chats/(components)/(chat)/text-field.svelte @@ -0,0 +1,56 @@ + + +
+ +
+
+ + +
diff --git a/src/routes/(auth)/chats/(components)/chat-list-item.svelte b/src/routes/(auth)/chats/(components)/chat-list-item.svelte new file mode 100644 index 0000000..3a0845d --- /dev/null +++ b/src/routes/(auth)/chats/(components)/chat-list-item.svelte @@ -0,0 +1,25 @@ + + + diff --git a/src/routes/(auth)/chats/(components)/chat-list.svelte b/src/routes/(auth)/chats/(components)/chat-list.svelte new file mode 100644 index 0000000..acdd58f --- /dev/null +++ b/src/routes/(auth)/chats/(components)/chat-list.svelte @@ -0,0 +1,32 @@ + + +
+ {#each chats as chat} + select(chat.username)} + /> + {/each} +
diff --git a/src/routes/(auth)/chats/(components)/sidebar-header.svelte b/src/routes/(auth)/chats/(components)/sidebar-header.svelte new file mode 100644 index 0000000..f8bbb81 --- /dev/null +++ b/src/routes/(auth)/chats/(components)/sidebar-header.svelte @@ -0,0 +1,43 @@ + + + + +
+ + + + + + My Account + + {#each menuItems as item} +
+ + {item.name} +
+ {/each} +
+
+
diff --git a/src/routes/(auth)/chats/(components)/sidebar.svelte b/src/routes/(auth)/chats/(components)/sidebar.svelte new file mode 100644 index 0000000..a37d560 --- /dev/null +++ b/src/routes/(auth)/chats/(components)/sidebar.svelte @@ -0,0 +1,18 @@ + + +
+
+ +
+
+ +
+ +
+
+
+
diff --git a/src/routes/(auth)/chats/+layout.svelte b/src/routes/(auth)/chats/+layout.svelte new file mode 100644 index 0000000..04f5c63 --- /dev/null +++ b/src/routes/(auth)/chats/+layout.svelte @@ -0,0 +1,67 @@ + + + + +
+ + +
+ +
+
diff --git a/src/routes/(auth)/chats/+layout.ts b/src/routes/(auth)/chats/+layout.ts new file mode 100644 index 0000000..c656668 --- /dev/null +++ b/src/routes/(auth)/chats/+layout.ts @@ -0,0 +1,11 @@ +import type { LayoutLoad } from './$types'; + +const chats = [ + { username: 'lionarius', lastMessage: 'Привет', avatarUrl: '/default-avatar.png' }, + { username: 'tenebris', lastMessage: 'Как дела', avatarUrl: null }, + { username: 'staheys', lastMessage: 'Hey', avatarUrl: '/default-avatar.png' }, +] + +export const load = (async () => { + return { chats }; +}) satisfies LayoutLoad; \ No newline at end of file diff --git a/src/routes/(auth)/chats/+page.svelte b/src/routes/(auth)/chats/+page.svelte new file mode 100644 index 0000000..246e86b --- /dev/null +++ b/src/routes/(auth)/chats/+page.svelte @@ -0,0 +1,5 @@ +
+
+

No chat selected

+
+
diff --git a/src/routes/(auth)/chats/[username]/+page.svelte b/src/routes/(auth)/chats/[username]/+page.svelte new file mode 100644 index 0000000..13a459a --- /dev/null +++ b/src/routes/(auth)/chats/[username]/+page.svelte @@ -0,0 +1,13 @@ + + +
+ +
diff --git a/src/routes/(auth)/chats/[username]/+page.ts b/src/routes/(auth)/chats/[username]/+page.ts new file mode 100644 index 0000000..1a0525d --- /dev/null +++ b/src/routes/(auth)/chats/[username]/+page.ts @@ -0,0 +1,18 @@ +import type { PageLoad } from './$types'; + +const messages = [ + { content: 'Приветdsfsdfsdfsdf', senderId: 1, createdAt: new Date(Date.now() - 1000 * 60 * 25) }, + { content: 'Приветffffffffffdsfsdfsdfffffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsffffffffdsfsdfsdffвеffffffffffdsfsdfsdffвеffветdsfsdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff дела?', senderId: 2, createdAt: new Date(Date.now() - 1000 * 60 * 20) }, + { content: 'Хорошо', senderId: 1, createdAt: new Date(Date.now() - 1000 * 60 * 15) }, + { content: 'Привет', senderId: 2, createdAt: new Date(Date.now() - 1000 * 60 * 10) }, + { content: 'Приветdsfsdfsdfsdf', senderId: 1, createdAt: new Date(Date.now() - 1000 * 60 * 25) }, + { content: 'fdsfdsfsdfКак дела?', senderId: 2, createdAt: new Date(Date.now() - 1000 * 60 * 20) }, + { content: 'Хорошо', senderId: 1, createdAt: new Date(Date.now() - 1000 * 60 * 15) }, + { content: 'Приветffffffffffdsfsdfsdfffffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsffffffffdsfsdfsdffвеffffffffffdsfsdfsdffвеffветdsfsdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderId: 2, createdAt: new Date(Date.now() - 1000 * 60 * 10) }, + { content: 'Приветffffffffffdsfsdfsdfffffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsfffffffdsfsdfsdffffffffffdsffffffffdsfsdfsdffвеffffffffffdsfsdfsdffвеffветdsfsdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', senderId: 1, createdAt: new Date(Date.now() - 1000 * 60 * 25) }, + +] + +export const load = (async () => { + return { messages }; +}) satisfies PageLoad; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 07b516b..f5e8f79 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,7 @@ - - - +
+ +
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..98b36e6 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,6 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load = (async () => { + redirect(302, '/login') +}) satisfies PageServerLoad; \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte deleted file mode 100644 index 5982b0a..0000000 --- a/src/routes/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts new file mode 100644 index 0000000..0754c8a --- /dev/null +++ b/src/routes/login/+page.server.ts @@ -0,0 +1,35 @@ +import type { PageServerLoad } from './$types'; +import { zod } from "sveltekit-superforms/adapters"; +import { loginFormSchema } from './login-form.svelte'; +import { superValidate } from "sveltekit-superforms"; +import { type Actions, fail, redirect } from "@sveltejs/kit"; +import { loginUser } from '$lib/api/user'; +import { isErrorResponse } from '$lib/types'; + + +export const load = (async ({ cookies }) => { + if (cookies.get('token')) + throw redirect(302, '/chats'); + + return { form: await superValidate(zod(loginFormSchema)) }; +}) satisfies PageServerLoad; + +export const actions: Actions = { + default: async (event) => { + const form = await superValidate(event, zod(loginFormSchema)); + + if (!form.valid) + return fail(400, { form }); + + const response = await loginUser(form.data.username, form.data.password); + + if (isErrorResponse(response)) { + form.errors.username = [response.error]; + return fail(400, { form }); + } + + event.cookies.set('token', response.token, { path: '/' }); + + return { form, token: response }; + }, +}; diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..2baad88 --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,15 @@ + + +
+ +
+

Login

+ +
+
diff --git a/src/routes/login/login-form.svelte b/src/routes/login/login-form.svelte new file mode 100644 index 0000000..4e3da05 --- /dev/null +++ b/src/routes/login/login-form.svelte @@ -0,0 +1,79 @@ + + + + +
+
+ + + Username + + + + + + + Password + + + + +
+ +
+ Login + +
+
diff --git a/src/routes/logout/+page.server.ts b/src/routes/logout/+page.server.ts new file mode 100644 index 0000000..9730af7 --- /dev/null +++ b/src/routes/logout/+page.server.ts @@ -0,0 +1,7 @@ +import type { PageServerLoad } from './$types'; + +export const load = (async ({ cookies }) => { + cookies.delete('token', { path: '/' }); + + return {}; +}) satisfies PageServerLoad; \ No newline at end of file diff --git a/src/routes/logout/+page.svelte b/src/routes/logout/+page.svelte new file mode 100644 index 0000000..18ec6d5 --- /dev/null +++ b/src/routes/logout/+page.svelte @@ -0,0 +1,11 @@ + diff --git a/src/routes/register/+page.server.ts b/src/routes/register/+page.server.ts new file mode 100644 index 0000000..dab1fd3 --- /dev/null +++ b/src/routes/register/+page.server.ts @@ -0,0 +1,29 @@ +import type { PageServerLoad } from './$types'; +import { superValidate } from "sveltekit-superforms"; +import { type Actions, fail } from "@sveltejs/kit"; +import { zod } from "sveltekit-superforms/adapters"; +import { registerFormSchema } from './register-form.svelte'; +import { registerUser } from '$lib/api/user'; +import { isErrorResponse } from '$lib/types'; + +export const load = (async () => { + return { form: await superValidate(zod(registerFormSchema)) }; +}) satisfies PageServerLoad; + +export const actions: Actions = { + default: async (event) => { + const form = await superValidate(event, zod(registerFormSchema)); + + if (!form.valid) + return fail(400, { form }); + + const response = await registerUser(form.data.username, form.data.password); + + if (isErrorResponse(response)) { + form.errors.username = [response.error]; + return fail(400, { form }); + } + + return { form, success: true }; + } +} \ No newline at end of file diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte new file mode 100644 index 0000000..e1c66ed --- /dev/null +++ b/src/routes/register/+page.svelte @@ -0,0 +1,14 @@ + + +
+
+

Register

+ +
+
diff --git a/src/routes/register/register-form.svelte b/src/routes/register/register-form.svelte new file mode 100644 index 0000000..e2b368e --- /dev/null +++ b/src/routes/register/register-form.svelte @@ -0,0 +1,95 @@ + + + + +
+
+ + + Username + + + + + + + Password + + + + + + + Confirm Password + + + + + +
+ Register + +
+
+
diff --git a/static/default-avatar.png b/static/default-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..e397a40223f667931d4776c49ac5c9ac9b029fe6 GIT binary patch literal 15131 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 URCnk(WX1zEB~8ULdCRc>18shij{pDw literal 0 HcmV?d00001 diff --git a/tailwind.config.ts b/tailwind.config.ts index c4bf235..1da57b6 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -37,7 +37,7 @@ const config: Config = { foreground: "hsl(var(--muted-foreground) / )" }, accent: { - DEFAULT: "hsl(var(--accent) / )", + DEFAULT: "#AD5CD6", foreground: "hsl(var(--accent-foreground) / )" }, popover: {