diff --git a/new-client/package.json b/new-client/package.json
index 3c14182..8844c1a 100644
--- a/new-client/package.json
+++ b/new-client/package.json
@@ -20,6 +20,7 @@
"@vueuse/core": "^14.3.0",
"@zag-js/avatar": "^1.40.0",
"@zag-js/collapsible": "^1.40.0",
+ "@zag-js/file-upload": "^1.41.0",
"@zag-js/file-utils": "^1.40.0",
"@zag-js/password-input": "^1.40.0",
"@zag-js/toggle": "^1.40.0",
diff --git a/new-client/src/pages/auth/login.vue b/new-client/src/pages/auth/login.vue
index 638e7a3..3eb7b15 100644
--- a/new-client/src/pages/auth/login.vue
+++ b/new-client/src/pages/auth/login.vue
@@ -6,7 +6,7 @@
-
+
Let's go
diff --git a/new-client/src/pages/auth/register.vue b/new-client/src/pages/auth/register.vue
index 8eb2d35..7536d9a 100644
--- a/new-client/src/pages/auth/register.vue
+++ b/new-client/src/pages/auth/register.vue
@@ -7,7 +7,7 @@
-
+
Create an account
diff --git a/new-client/src/shared/components/ui/Button.vue b/new-client/src/shared/components/ui/Button.vue
index 8ffc735..f648ff3 100644
--- a/new-client/src/shared/components/ui/Button.vue
+++ b/new-client/src/shared/components/ui/Button.vue
@@ -1,37 +1,53 @@
diff --git a/new-client/src/shared/components/ui/Input.vue b/new-client/src/shared/components/ui/Input.vue
index 187027d..daf85b8 100644
--- a/new-client/src/shared/components/ui/Input.vue
+++ b/new-client/src/shared/components/ui/Input.vue
@@ -9,7 +9,7 @@
{{ label }}
-
+
{{ helper }}
diff --git a/new-client/src/shared/components/ui/NotificationBadge.vue b/new-client/src/shared/components/ui/NotificationBadge.vue
new file mode 100644
index 0000000..3e7ba77
--- /dev/null
+++ b/new-client/src/shared/components/ui/NotificationBadge.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/new-client/src/shared/components/ui/ToBottom.vue b/new-client/src/shared/components/ui/ToBottom.vue
new file mode 100644
index 0000000..eb7a12b
--- /dev/null
+++ b/new-client/src/shared/components/ui/ToBottom.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
diff --git a/new-client/src/shared/composables/use-bem.ts b/new-client/src/shared/composables/use-bem.ts
new file mode 100644
index 0000000..e2951ef
--- /dev/null
+++ b/new-client/src/shared/composables/use-bem.ts
@@ -0,0 +1,68 @@
+interface Options {
+ block: string
+ blockSuffix?: string
+ element?: string
+ modifier?: string
+}
+
+function _bem(options: Options) {
+ const { block, blockSuffix, element, modifier } = options
+
+ let cls = block
+
+ if (blockSuffix)
+ cls += `-${blockSuffix}`
+
+ if (element)
+ cls += `__${element}`
+
+ if (modifier)
+ cls += `--${modifier}`
+
+ return cls
+}
+
+function isTruthy(value: unknown): boolean {
+ if (Array.isArray(value)) {
+ return value.length > 0
+ }
+ else if (typeof value === 'object' && value !== null) {
+ return Object.keys(value).length > 0
+ }
+
+ return !!value
+}
+
+export default (block: string) => {
+ const b = (blockSuffix?: string) => _bem({ block, blockSuffix })
+ const e = (element: string) => _bem({ block, element })
+ const m = (modifier: string) => _bem({ block, modifier })
+
+ const be = (blockSuffix: string, element: string) => _bem({ block, blockSuffix, element })
+ const em = (element: string, modifier: string) => _bem({ block, element, modifier })
+ const bm = (blockSuffix: string, modifier: string) => _bem({ block, blockSuffix, modifier })
+
+ const bem = (blockSuffix: string, element: string, modifier: string) => _bem({ block, blockSuffix, element, modifier })
+
+ const is = (name: string, state: unknown) => isTruthy(state) ? `is-${name}` : ''
+ const has = (name: string, state: unknown) => isTruthy(state) ? `has-${name}` : ''
+
+ const exp = (condition: boolean, trueState: string, falseState: string = '') => condition ? trueState : falseState
+
+ return {
+ b,
+ e,
+ m,
+
+ be,
+ em,
+ bm,
+
+ bem,
+
+ is,
+ has,
+
+ exp,
+ }
+}
diff --git a/new-client/src/shared/styles/tokens.scss b/new-client/src/shared/styles/tokens.scss
index 3ebcfba..3478dc8 100644
--- a/new-client/src/shared/styles/tokens.scss
+++ b/new-client/src/shared/styles/tokens.scss
@@ -10,6 +10,7 @@
--grey-2: #c9c6bd;
--grey-3: #8a8a82;
+ --shadow-sm: 3px 3px 0 0 var(--ink);
--shadow: 6px 6px 0 0 var(--ink);
--border-w: 2px;
diff --git a/new-client/src/widgets/chat/composables/use-chat-history.ts b/new-client/src/widgets/chat/composables/use-chat-history.ts
new file mode 100644
index 0000000..baf6c6c
--- /dev/null
+++ b/new-client/src/widgets/chat/composables/use-chat-history.ts
@@ -0,0 +1,42 @@
+import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
+import { useEventBus } from '@shared/composables/use-event-bus.ts'
+import { createGlobalState } from '@vueuse/core'
+import { computed, shallowRef, triggerRef } from 'vue'
+import { qChatMessages } from '../api/qChatMessages.ts'
+
+export interface MessageGroup {
+ senderUsername: string
+ messages: ChatMessage[]
+}
+
+export const useChatHistory = createGlobalState(() => {
+ const eventBus = useEventBus()
+
+ const { data: messages, hasNextPage: hasMoreMessages, fetchNextPage: loadMoreMessages, isFetching } = qChatMessages()
+
+ const feedItems = shallowRef([])
+
+ eventBus.on('chat:new-message', (message: ChatMessage) => {
+ feedItems.value.push(message)
+ triggerRef(feedItems)
+ })
+
+ const messageGroups = computed(() => {
+ const all = [...(messages.value ?? []), ...feedItems.value]
+ return all.reduce((groups, msg) => {
+ const last = groups.at(-1)
+ if (last?.senderUsername === msg.senderUsername)
+ last.messages.push(msg)
+ else
+ groups.push({ senderUsername: msg.senderUsername, messages: [msg] })
+ return groups
+ }, [] as MessageGroup[])
+ })
+
+ return {
+ messageGroups,
+ hasMoreMessages,
+ loadMoreMessages,
+ isFetching,
+ }
+})
diff --git a/new-client/src/widgets/chat/composables/use-chat-scroll.ts b/new-client/src/widgets/chat/composables/use-chat-scroll.ts
index 490f057..78b3a80 100644
--- a/new-client/src/widgets/chat/composables/use-chat-scroll.ts
+++ b/new-client/src/widgets/chat/composables/use-chat-scroll.ts
@@ -15,9 +15,7 @@ export function useChatScroll(target: TemplateRef, options?: MaybeR
end: false,
})
- watch(arrivedState, () => {
- console.log('arrived state', arrivedState)
- })
+ let lastScrollHeight = 0
watch(el, (el) => {
if (!el)
@@ -31,14 +29,22 @@ export function useChatScroll(target: TemplateRef, options?: MaybeR
if (arrivedState.start) {
scrollToStart(true)
}
+ else {
+ const heightDiff = el.value!.scrollHeight - lastScrollHeight
+
+ el.value!.scrollTop += heightDiff
+ }
getArrivedState()
+
+ lastScrollHeight = el.value!.scrollHeight
}, {
childList: true,
subtree: true,
})
useEventListener(el, 'scroll', () => {
+ console.log('scroll', el.value!.scrollTop)
getArrivedState()
}, { passive: true })
diff --git a/new-client/src/widgets/chat/composables/use-chat.ts b/new-client/src/widgets/chat/composables/use-chat.ts
index 8657dfe..9252120 100644
--- a/new-client/src/widgets/chat/composables/use-chat.ts
+++ b/new-client/src/widgets/chat/composables/use-chat.ts
@@ -1,41 +1,18 @@
-import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
-import { useSignaling } from '@shared/composables/use-signaling'
-import { createGlobalState } from '@vueuse/core'
-import { computed, shallowRef, triggerRef, watch } from 'vue'
-import { qChatMessages } from '../api/qChatMessages.ts'
+import type { NewChatMessagePayload } from '@shared/api/generated-chad-api.ts'
+import api from '@shared/api/client.ts'
-export interface MessageGroup {
- senderUsername: string
- messages: ChatMessage[]
-}
+export function useChat() {
+ async function sendMessage(newMessage: NewChatMessagePayload) {
+ newMessage.text = newMessage.text.trim()
-export const useChat = createGlobalState(() => {
- const { data: messages, hasNextPage: hasMoreMessages, fetchNextPage, isFetching } = qChatMessages()
-
- const { socket, connected } = useSignaling()
- const feedItems = shallowRef([])
-
- watch(connected, async (isConnected) => {
- if (!isConnected)
+ if (newMessage.attachments?.length === 0 && newMessage.text.length === 0) {
return
+ }
- socket.value!.on('chat:new-message', (message: ChatMessage) => {
- feedItems.value.push(message)
- triggerRef(feedItems)
- })
- })
+ await api.chad.chatSend(newMessage)
+ }
- const messageGroups = computed(() => {
- const all = [...(messages.value ?? []), ...feedItems.value]
- return all.reduce((groups, msg) => {
- const last = groups.at(-1)
- if (last?.senderUsername === msg.senderUsername)
- last.messages.push(msg)
- else
- groups.push({ senderUsername: msg.senderUsername, messages: [msg] })
- return groups
- }, [] as MessageGroup[])
- })
-
- return { messageGroups, hasMoreMessages, fetchNextPage, isFetching }
-})
+ return {
+ sendMessage,
+ }
+}
diff --git a/new-client/src/widgets/chat/ui/Chat.vue b/new-client/src/widgets/chat/ui/Chat.vue
index 71b56c9..607121d 100644
--- a/new-client/src/widgets/chat/ui/Chat.vue
+++ b/new-client/src/widgets/chat/ui/Chat.vue
@@ -6,8 +6,11 @@
diff --git a/new-client/src/widgets/chat/ui/ChatMessageGroup.vue b/new-client/src/widgets/chat/ui/ChatMessageGroup.vue
index afaa675..9f2370c 100644
--- a/new-client/src/widgets/chat/ui/ChatMessageGroup.vue
+++ b/new-client/src/widgets/chat/ui/ChatMessageGroup.vue
@@ -13,7 +13,7 @@