diff --git a/new-client/index.html b/new-client/index.html index 81a196a..d7256ce 100644 --- a/new-client/index.html +++ b/new-client/index.html @@ -8,7 +8,7 @@ - + diff --git a/new-client/package.json b/new-client/package.json index c1fb7df..e400431 100644 --- a/new-client/package.json +++ b/new-client/package.json @@ -10,13 +10,21 @@ }, "dependencies": { "@ark-ui/vue": "^5.36.2", + "@lucide/vue": "^1.14.0", + "@tanstack/vue-query": "^5.100.10", "@tauri-apps/plugin-global-shortcut": "^2.3.1", "@tauri-apps/plugin-opener": "~2", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", "@vueuse/core": "^14.3.0", + "@zag-js/avatar": "^1.40.0", + "@zag-js/collapsible": "^1.40.0", + "@zag-js/password-input": "^1.40.0", "@zag-js/vue": "^1.40.0", + "date-fns": "^4.1.0", + "mitt": "^3.0.1", "primevue": "^4.5.5", + "socket.io-client": "^4.8.3", "vue": "^3.5.32", "vue-router": "^5.0.6" }, diff --git a/new-client/src-tauri/target/.rustc_info.json b/new-client/src-tauri/target/.rustc_info.json new file mode 100644 index 0000000..2a30e1b --- /dev/null +++ b/new-client/src-tauri/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":1074701663444030447,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0 (59807616e 2026-04-14)\nbinary: rustc\ncommit-hash: 59807616e1fa2540724bfbac14d7976d7e4a3860\ncommit-date: 2026-04-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.95.0\nLLVM version: 22.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\opti\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/new-client/src-tauri/target/debug/.fingerprint/app-4e7dc2d6835c5d74/bin-app.json b/new-client/src-tauri/target/debug/.fingerprint/app-4e7dc2d6835c5d74/bin-app.json new file mode 100644 index 0000000..8db7964 --- /dev/null +++ b/new-client/src-tauri/target/debug/.fingerprint/app-4e7dc2d6835c5d74/bin-app.json @@ -0,0 +1 @@ +{"rustc":8891013984288978370,"features":"[]","declared_features":"[]","target":12687594469746301899,"profile":8731458305071235362,"path":4942398508502643691,"deps":[[1322478694103194923,"app_lib",false,8070585750093435046],[1322478694103194923,"build_script_build",false,16691984141835454783],[2700615264719386388,"tauri_plugin_opener",false,7350390124577390804],[4736393929136623228,"tauri_plugin_global_shortcut",false,15267933955632970549],[5673630358394410545,"tauri_plugin_updater",false,14019643380268120922],[8695090098768911186,"windows",false,17298248671909505349],[10630857666389190470,"log",false,5016507518680283320],[12653526805862679340,"tauri_plugin_log",false,15855325777720298125],[13548984313718623784,"serde",false,7298479236282139191],[13795362694956882968,"serde_json",false,4974751760879812175],[13905323280135927163,"tauri_plugin_single_instance",false,10523578089830730283],[14261240476176424521,"tauri",false,8738078672178348289],[18393575805749125025,"tauri_plugin_process",false,17190748950968290852]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\app-4e7dc2d6835c5d74\\dep-bin-app","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/new-client/src-tauri/target/debug/app_lib.dll.lib b/new-client/src-tauri/target/debug/app_lib.dll.lib new file mode 100644 index 0000000..0814d1d Binary files /dev/null and b/new-client/src-tauri/target/debug/app_lib.dll.lib differ diff --git a/new-client/src/app/Error.vue b/new-client/src/app/Error.vue index ef06c66..e58e0d0 100644 --- a/new-client/src/app/Error.vue +++ b/new-client/src/app/Error.vue @@ -24,12 +24,19 @@ defineProps<{ &__image { width: 120px; - margin-bottom: 32px; + margin-bottom: var(--space-6); user-select: none; + opacity: 0.9; } &__message { - color: var(--text-muted); + @include font-body-bold; + + padding: var(--space-2) var(--space-3); + background-color: var(--paper); + outline: var(--border-w) solid var(--ink); + outline-offset: calc(var(--border-w) * -1); + box-shadow: var(--shadow); } } diff --git a/new-client/src/app/Preloader.vue b/new-client/src/app/Preloader.vue index 4a7dd6d..1567ec6 100644 --- a/new-client/src/app/Preloader.vue +++ b/new-client/src/app/Preloader.vue @@ -16,10 +16,14 @@ height: 100%; > p { + @include font-body-bold; + margin: auto; - padding: 8px 16px; - border-radius: 16px; - background-color: var(--bg-light); + padding: var(--space-2) var(--space-3); + background-color: var(--paper); + outline: var(--border-w) solid var(--ink); + outline-offset: calc(var(--border-w) * -1); + box-shadow: var(--shadow); } } diff --git a/new-client/src/app/Updater.vue b/new-client/src/app/Updater.vue index 875539d..6a84174 100644 --- a/new-client/src/app/Updater.vue +++ b/new-client/src/app/Updater.vue @@ -21,10 +21,14 @@ const { checking, lastUpdate } = useUpdater() height: 100%; > p { + @include font-body-bold; + margin: auto; - padding: 8px 16px; - border-radius: 16px; - background-color: var(--bg-light); + padding: var(--space-2) var(--space-3); + background-color: var(--paper); + outline: var(--border-w) solid var(--ink); + outline-offset: calc(var(--border-w) * -1); + box-shadow: var(--shadow); } } diff --git a/new-client/src/app/bootstrap/app.ts b/new-client/src/app/bootstrap/app.ts index 80d4b7a..6b43fdd 100644 --- a/new-client/src/app/bootstrap/app.ts +++ b/new-client/src/app/bootstrap/app.ts @@ -2,6 +2,7 @@ import { useAuth } from '@shared/composables/use-auth' import { createApp } from 'vue' import App from '../App.vue' import routerPlugin, { router } from '../plugins/router' +import tanstackQueryPlugin from '../plugins/tanstack-query' const mountPoint = '#app' @@ -14,8 +15,14 @@ export default async function () { await router.isReady() if (!authorized.value && router.currentRoute.value.meta.auth === undefined) { - router.push('/auth/login') + await router.push({ name: '/auth/login', replace: true }) } + if (authorized.value && router.currentRoute.value.meta.auth === 'guest') { + await router.push({ name: '/', replace: true }) + } + + app.use(tanstackQueryPlugin) + app.mount(mountPoint) } diff --git a/new-client/src/app/entry.ts b/new-client/src/app/entry.ts index 0ab2aff..679296d 100644 --- a/new-client/src/app/entry.ts +++ b/new-client/src/app/entry.ts @@ -13,16 +13,16 @@ import checkUpdates from './bootstrap/updater' await authorize() - initializeApp() + await initializeApp() } catch (error) { console.error(error) if (error instanceof Error && error.message) { - showError(error.message) + await showError(error.message) } else { - showError('Something went wrong') + await showError('Something went wrong') } } finally { diff --git a/new-client/src/app/plugins/router.ts b/new-client/src/app/plugins/router.ts index 6a38f6f..973448f 100644 --- a/new-client/src/app/plugins/router.ts +++ b/new-client/src/app/plugins/router.ts @@ -1,4 +1,5 @@ import type { Component, FunctionPlugin } from 'vue' +import { useAuth } from '@shared/composables/use-auth' import { createRouter, createWebHistory } from 'vue-router' import { routes } from 'vue-router/auto-routes' @@ -13,6 +14,23 @@ export const router = createRouter({ history: createWebHistory(), routes, }) +router.onError((e) => { + console.log('wtf', e) +}) + +router.isReady().then(() => { + router.beforeEach((to) => { + const { authorized } = useAuth() + + if (authorized.value && to.meta.auth === 'guest') { + return { name: '/', replace: true } + } + + if (!authorized.value && to.meta.auth === undefined) { + return { name: '/auth/login', replace: true } + } + }) +}) export default { install(app) { diff --git a/new-client/src/app/plugins/tanstack-query.ts b/new-client/src/app/plugins/tanstack-query.ts new file mode 100644 index 0000000..ea934de --- /dev/null +++ b/new-client/src/app/plugins/tanstack-query.ts @@ -0,0 +1,8 @@ +import type { FunctionPlugin } from 'vue' +import { VueQueryPlugin } from '@tanstack/vue-query' + +export default { + install(app) { + app.use(VueQueryPlugin, {}) + }, +} as FunctionPlugin diff --git a/new-client/src/pages/auth.vue b/new-client/src/pages/auth.vue index 092fa96..504b2d7 100644 --- a/new-client/src/pages/auth.vue +++ b/new-client/src/pages/auth.vue @@ -10,5 +10,6 @@ definePage({ auth: 'guest', layout: AuthLayout, }, + // redirect: '/auth/login', }) diff --git a/new-client/src/pages/auth/login.vue b/new-client/src/pages/auth/login.vue index 4f23ef8..638e7a3 100644 --- a/new-client/src/pages/auth/login.vue +++ b/new-client/src/pages/auth/login.vue @@ -1,35 +1,71 @@ - - - - - - - + + + + + - + + Let's go - - + + + + Don't have an account? + + + Register + + + diff --git a/new-client/src/pages/auth/register.vue b/new-client/src/pages/auth/register.vue index b436366..8eb2d35 100644 --- a/new-client/src/pages/auth/register.vue +++ b/new-client/src/pages/auth/register.vue @@ -1,6 +1,76 @@ - Register page + + + + + + + + + + Create an account + + + + + Already have an account? + + + Sign In + + + + + diff --git a/new-client/src/pages/index.vue b/new-client/src/pages/index.vue index e84d75b..e7b3d57 100644 --- a/new-client/src/pages/index.vue +++ b/new-client/src/pages/index.vue @@ -1,5 +1,7 @@ - Index page + - + diff --git a/new-client/src/shared/api/generated-chad-api.ts b/new-client/src/shared/api/generated-chad-api.ts index 7f4256c..5c4e4ac 100644 --- a/new-client/src/shared/api/generated-chad-api.ts +++ b/new-client/src/shared/api/generated-chad-api.ts @@ -232,7 +232,7 @@ export class HttpClient { fetch(...fetchParams); private baseApiParams: RequestParams = { - credentials: "same-origin", + credentials: "include", headers: {}, redirect: "follow", referrerPolicy: "no-referrer", diff --git a/new-client/src/shared/components/AppLogo.vue b/new-client/src/shared/components/AppLogo.vue index 4615f70..4aaf8b3 100644 --- a/new-client/src/shared/components/AppLogo.vue +++ b/new-client/src/shared/components/AppLogo.vue @@ -17,15 +17,21 @@ const { version } = useApp() diff --git a/new-client/src/shared/components/ui/Avatar.vue b/new-client/src/shared/components/ui/Avatar.vue new file mode 100644 index 0000000..224ee9f --- /dev/null +++ b/new-client/src/shared/components/ui/Avatar.vue @@ -0,0 +1,50 @@ + + + {{ fallback }} + + + + + + + diff --git a/new-client/src/shared/components/ui/Button.vue b/new-client/src/shared/components/ui/Button.vue index 32c100d..83deb62 100644 --- a/new-client/src/shared/components/ui/Button.vue +++ b/new-client/src/shared/components/ui/Button.vue @@ -2,6 +2,7 @@ .chad-button { + @include font-display-14; + border: none; - color: var(--bg-dark); - border-radius: 12px; - padding-inline: 12px; + color: var(--ink); + padding-inline: var(--space-3); height: 44px; - background-color: var(--primary); + background-color: var(--yellow); font-weight: 700; text-align: center; cursor: pointer; - transition-property: color, background-color; - transition-duration: 150ms; - transition-timing-function: ease-out; - outline: none; + outline: var(--border-w) solid var(--ink); + outline-offset: calc(var(--border-w) * -1); &[data-full] { width: 100%; } &:hover, - &:focus, + &:focus { + background-color: var(--yellow-deep); + } + &:active { - background-color: var(--secondary); + background-color: var(--ink); + color: var(--yellow); + } + + &[data-disabled], + &[data-loading], + &:disabled { + cursor: not-allowed; + background-color: var(--grey-1); + color: var(--grey-3); + } + + &[data-loading] { + cursor: wait; } } diff --git a/new-client/src/shared/components/ui/Card.vue b/new-client/src/shared/components/ui/Card.vue new file mode 100644 index 0000000..c3a4a25 --- /dev/null +++ b/new-client/src/shared/components/ui/Card.vue @@ -0,0 +1,68 @@ + + + + {{ title }} + + + + + + + + + + + + + + + + + + + diff --git a/new-client/src/shared/components/ui/Input.vue b/new-client/src/shared/components/ui/Input.vue index 9c2b8a2..187027d 100644 --- a/new-client/src/shared/components/ui/Input.vue +++ b/new-client/src/shared/components/ui/Input.vue @@ -1,40 +1,47 @@ - - + + {{ label }} - + - - - + - + {{ helper }} - - + + {{ error }} - - + + diff --git a/new-client/src/shared/components/ui/PasswordInput.vue b/new-client/src/shared/components/ui/PasswordInput.vue index a672572..e88e004 100644 --- a/new-client/src/shared/components/ui/PasswordInput.vue +++ b/new-client/src/shared/components/ui/PasswordInput.vue @@ -1,126 +1,131 @@ - - + + {{ label }} - + - - - - - - Z - - X - - - - - + + - - {{ helper }} - - - {{ error }} - - + + + + + diff --git a/new-client/src/shared/composables/use-auth.ts b/new-client/src/shared/composables/use-auth.ts index d7b0182..c8e6421 100644 --- a/new-client/src/shared/composables/use-auth.ts +++ b/new-client/src/shared/composables/use-auth.ts @@ -1,4 +1,5 @@ import type { User } from '../api/generated-chad-api' +import { router } from '@app/plugins/router' import { createGlobalState } from '@vueuse/core' import { computed, readonly, shallowRef } from 'vue' import api from '../api/client' @@ -20,6 +21,8 @@ export const useAuth = createGlobalState(() => { }) setMe(response.data) + + await router.push({ name: '/', replace: true }) } catch { setMe(undefined) @@ -34,6 +37,8 @@ export const useAuth = createGlobalState(() => { }) setMe(response.data) + + await router.push({ name: '/', replace: true }) } catch {} } @@ -43,6 +48,8 @@ export const useAuth = createGlobalState(() => { await api.chad.authLogout() setMe(undefined) + + await router.push({ name: '/auth/login' }) } catch {} } diff --git a/new-client/src/shared/composables/use-event-bus.ts b/new-client/src/shared/composables/use-event-bus.ts new file mode 100644 index 0000000..6ecf5ec --- /dev/null +++ b/new-client/src/shared/composables/use-event-bus.ts @@ -0,0 +1,43 @@ +import type { EventType } from 'mitt' +import mitt from 'mitt' + +export interface AppEvents extends Record { + // 'socket:connected': void + // 'socket:disconnected': void + // 'socket:authenticated': { socketId: string } + // + // 'client:added': ChadClient + // 'client:removed': ChadClient + // 'client:updated': { socketId: string, oldClient: ChadClient, updatedClient: Partial } + // + // 'consumer:added': Consumer + // 'consumer:removed': Consumer + // 'consumer:paused': Consumer + // 'consumer:resumed': Consumer + // + // 'producer:added': Producer + // 'producer:removed': Producer + // 'producer:paused': Producer + // 'producer:resumed': Producer + + 'audio:muted': void + 'audio:unmuted': void + 'output:muted': void + 'output:unmuted': void + 'video:enabled': void + 'video:disabled': void + 'share:enabled': void + 'share:disabled': void + + 'chat:new-message': void +} + +const emitter = mitt() + +export function useEventBus() { + return { + emit: emitter.emit, + on: emitter.on, + off: emitter.off, + } +} diff --git a/new-client/src/shared/composables/use-signaling.ts b/new-client/src/shared/composables/use-signaling.ts new file mode 100644 index 0000000..af06f62 --- /dev/null +++ b/new-client/src/shared/composables/use-signaling.ts @@ -0,0 +1,87 @@ +import type { Socket } from 'socket.io-client' +import { createSharedComposable } from '@vueuse/core' +import { io } from 'socket.io-client' +import { parseURL } from 'ufo' +import { onScopeDispose, ref, shallowRef, watch } from 'vue' +import { useAuth } from './use-auth' +import { useEventBus } from './use-event-bus' + +export const useSignaling = createSharedComposable(() => { + const { emit } = useEventBus() + const { me } = useAuth() + + const socket = shallowRef() + + const connected = ref(false) + + watch(socket, (socket, prevSocket) => { + if (prevSocket) { + prevSocket.close() + } + + if (!socket) { + return + } + + if (import.meta.dev) { + socket.onAny((event, ...args) => { + console.info('[onAny]', event, args) + }) + + socket.onAnyOutgoing((event, ...args) => { + console.info('[onAnyOutgoing]', event, args) + }) + } + + socket.on('connect', async () => { + connected.value = true + }) + + socket.on('disconnect', async () => { + connected.value = false + }) + }, { immediate: true, flush: 'sync' }) + + watch(connected, (connected) => { + if (connected) + emit('socket:connected') + else + emit('socket:disconnected') + }, { immediate: true }) + + watch(me, (me) => { + if (!me) { + socket.value?.close() + socket.value = undefined + } + }) + + onScopeDispose(() => { + socket.value?.close() + socket.value = undefined + }) + + function connect() { + if (socket.value || !me.value) + return + + const { protocol, host, pathname } = parseURL(__API_BASE_URL__) + + const uri = host ? `${protocol}//${host}` : `` + + socket.value = io(uri, { + path: `${pathname}/ws`, + transports: ['websocket'], + withCredentials: true, + auth: { + userId: me.value.id, + }, + }) + } + + return { + socket, + connected, + connect, + } +}) diff --git a/new-client/src/shared/layouts/Auth.vue b/new-client/src/shared/layouts/Auth.vue index eaedcbe..227a9b2 100644 --- a/new-client/src/shared/layouts/Auth.vue +++ b/new-client/src/shared/layouts/Auth.vue @@ -27,18 +27,12 @@ defineOptions({ &__container { margin: auto; width: 100%; - max-width: 380px; + max-width: 440px; } &__logo { - text-align: center; - margin-bottom: 24px; - } - - &__content { - padding: 24px; - background-color: var(--bg); - border-radius: 36px; + justify-self: center; + margin-bottom: var(--space-6); } } diff --git a/new-client/src/shared/layouts/Default.vue b/new-client/src/shared/layouts/Default.vue index eae6993..ee812ff 100644 --- a/new-client/src/shared/layouts/Default.vue +++ b/new-client/src/shared/layouts/Default.vue @@ -1,30 +1,42 @@ - - - + + + + Chad + + + + {{ version }} + - - Header + + + + Messages + -
+ {{ title }} +
{{ helper }} -
{{ error }} -
+ Chad +
+ {{ version }} +
+ Messages +