fullstack: added profile pictures/avatars to accounts and improved settings page layout
This commit is contained in:
@@ -12,9 +12,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluent/bundle": "^0.19.1",
|
"@fluent/bundle": "^0.19.1",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-http": "~2",
|
"@tauri-apps/plugin-http": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-store": "~2",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
|
"@tauri-apps/plugin-upload": "~2",
|
||||||
"@tauri-apps/plugin-websocket": "~2",
|
"@tauri-apps/plugin-websocket": "~2",
|
||||||
"fluent-vue": "^3.8.1",
|
"fluent-vue": "^3.8.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
|||||||
106
src-tauri/Cargo.lock
generated
106
src-tauri/Cargo.lock
generated
@@ -443,9 +443,11 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-http",
|
"tauri-plugin-http",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
|
"tauri-plugin-upload",
|
||||||
"tauri-plugin-websocket",
|
"tauri-plugin-websocket",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -759,6 +761,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
|
"block2",
|
||||||
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1038,6 +1042,21 @@ dependencies = [
|
|||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -1045,6 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1112,6 +1132,7 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
@@ -1527,6 +1548,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
@@ -3024,6 +3051,17 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "read-progress-stream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -3139,6 +3177,30 @@ dependencies = [
|
|||||||
"webpki-roots 1.0.4",
|
"webpki-roots 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfd"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
||||||
|
dependencies = [
|
||||||
|
"block2",
|
||||||
|
"dispatch2",
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"gtk-sys",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation",
|
||||||
|
"raw-window-handle",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -3827,6 +3889,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"http-range",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -3942,10 +4005,28 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.4.4"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9"
|
checksum = "05416b57601eca8666b5ec4186f5b1dc826ed35263b4797ad6641e58da6bc6c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"raw-window-handle",
|
||||||
|
"rfd",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tauri-plugin-fs",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-fs"
|
||||||
|
version = "2.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -4025,6 +4106,25 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-upload"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e2844560c33b360506cea7289626267f43be253a01018b53bd556e86163b9fd"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"read-progress-stream",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-websocket"
|
name = "tauri-plugin-websocket"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = ["devtools"] }
|
tauri = { version = "2", features = ["protocol-asset", "devtools"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tauri-plugin-store = "2"
|
tauri-plugin-store = "2"
|
||||||
tauri-plugin-http = "2"
|
tauri-plugin-http = "2"
|
||||||
tauri-plugin-websocket = "2"
|
tauri-plugin-websocket = "2"
|
||||||
|
tauri-plugin-upload = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"websocket:default"
|
"websocket:default",
|
||||||
|
"upload:default",
|
||||||
|
"dialog:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ fn greet(name: &str) -> String {
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_upload::init())
|
||||||
.plugin(tauri_plugin_websocket::init())
|
.plugin(tauri_plugin_websocket::init())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
.plugin(tauri_plugin_store::Builder::new().build())
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
|
|||||||
@@ -19,11 +19,18 @@
|
|||||||
"resizable": true,
|
"resizable": true,
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"center": true,
|
"center": true,
|
||||||
"theme": "Dark"
|
"theme": "Dark",
|
||||||
|
"dragDropEnabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null,
|
||||||
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": [
|
||||||
|
"**"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
@@ -1,10 +1,37 @@
|
|||||||
import { UpdateUserResponse } from '../types';
|
import { UpdateUserResponse } from '../types';
|
||||||
import { apiFetch } from './client'
|
import { apiFetch } from './client'
|
||||||
// import type { User } from '../types'
|
import { upload } from '@tauri-apps/plugin-upload';
|
||||||
|
import { getAuthData } from '../authStore';
|
||||||
|
import { API } from '../main.ts';
|
||||||
|
|
||||||
export function updateSettings(username: string, email: string, password: string) {
|
export function updateSettings(username: string, email: string, password: string) {
|
||||||
return apiFetch<UpdateUserResponse>('/settings', {
|
return apiFetch<UpdateUserResponse>('/account/settings', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({ username, email, password }),
|
body: JSON.stringify({ username, email, password }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function uploadAvatar(
|
||||||
|
filePath: string,
|
||||||
|
onProgress: (progress: number, total: number) => void
|
||||||
|
) {
|
||||||
|
const auth = await getAuthData();
|
||||||
|
const url = `${API}/account/upload-avatar`;
|
||||||
|
const headers = new Map<string, string>([
|
||||||
|
['Authorization', `Bearer ${auth.token}`],
|
||||||
|
['Content-Type', 'application/octet-stream']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return upload(
|
||||||
|
url,
|
||||||
|
filePath,
|
||||||
|
({ progress, total }) => {
|
||||||
|
onProgress(progress, total);
|
||||||
|
},
|
||||||
|
headers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAvatar(uuid: string): string {
|
||||||
|
return `${API}/account/get-avatar/${uuid}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ export async function apiFetch<T>(
|
|||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const auth = await getAuthData()
|
const auth = await getAuthData()
|
||||||
|
|
||||||
|
const isFormData = options.body instanceof FormData;
|
||||||
|
|
||||||
const res = await fetch(`${API}${path}`, {
|
const res = await fetch(`${API}${path}`, {
|
||||||
...options,
|
...options,
|
||||||
method: options.method || 'GET',
|
method: options.method || 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
// Only add json header if it's not formdata
|
||||||
|
...(!isFormData ? { 'Content-Type': 'application/json' } : {}),
|
||||||
...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),
|
...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),
|
||||||
...options.headers,
|
...options.headers,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { load, Store } from '@tauri-apps/plugin-store'
|
import { load, Store } from '@tauri-apps/plugin-store'
|
||||||
import { UpdateUserResponse, User } from './types'
|
import { UpdateUserResponse, User } from './types'
|
||||||
|
import { getAvatar } from './api/account'
|
||||||
|
|
||||||
let store: Store | null = null
|
let store: Store | null = null
|
||||||
|
|
||||||
@@ -32,7 +33,8 @@ export async function updateLocalUser(newData: UpdateUserResponse, uuid?: string
|
|||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
username: newData.username,
|
username: newData.username,
|
||||||
email: newData.email,
|
email: newData.email,
|
||||||
uuid
|
uuid,
|
||||||
|
avatar_url: `${getAvatar(uuid || '')}?t=${Date.now()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = await getStore()
|
const s = await getStore()
|
||||||
@@ -40,6 +42,18 @@ export async function updateLocalUser(newData: UpdateUserResponse, uuid?: string
|
|||||||
await s.save()
|
await s.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshLocalUser() {
|
||||||
|
const s = await getStore()
|
||||||
|
const user = await s.get<User>('user')
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
user.avatar_url = `${getAvatar(user.uuid)}?t=${Date.now()}`
|
||||||
|
|
||||||
|
await s.set('user', user)
|
||||||
|
await s.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function setLastRoom(uuid: string) {
|
export async function setLastRoom(uuid: string) {
|
||||||
if (!uuid || uuid === 'none') return
|
if (!uuid || uuid === 'none') return
|
||||||
const s = await getStore()
|
const s = await getStore()
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="submit" :disabled="isSubmitting">
|
<button type="submit" :disabled="isSubmitting">
|
||||||
{{ isSubmitting ? $t('settings-updating') : $t('settings-update-save') }}
|
{{ isSubmitting ? $t('shared-updating') : $t('shared-save') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -95,8 +95,7 @@ async function submit() {
|
|||||||
.backdrop {
|
.backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
270
src/components/UploadAvatarModal.vue
Normal file
270
src/components/UploadAvatarModal.vue
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<template>
|
||||||
|
<div class="backdrop" @click.self="emit('close')">
|
||||||
|
<div class="modal">
|
||||||
|
<h2>{{ $t('settings-upload-avatar-title') || 'Upload Avatar' }}</h2>
|
||||||
|
|
||||||
|
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
|
||||||
|
|
||||||
|
<div class="drop-zone" :class="{ 'is-dragging': isDragging }" @click="pickFile">
|
||||||
|
<div v-if="!previewUrl" class="drop-content">
|
||||||
|
<i class="fa-solid fa-cloud-arrow-up"></i>
|
||||||
|
<p>{{ $t('settings-upload-prompt') || 'Drag an image here or click to browse' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="preview-container">
|
||||||
|
<img :src="previewUrl" alt="Avatar preview" />
|
||||||
|
<div class="overlay">
|
||||||
|
<span>{{ $t('settings-change-photo') || 'Change Photo' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isSubmitting" class="progress-container">
|
||||||
|
<div class="progress-bar" :style="{ width: uploadProgress + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button type="button" @click="emit('close')" class="secondary" :disabled="isSubmitting">
|
||||||
|
{{ $t('shared-cancel') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="handleUpload" :disabled="isSubmitting || !selectedPath">
|
||||||
|
{{ isSubmitting ? $t('shared-uploading') || 'Uploading...' : $t('shared-save') || 'Save Avatar' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { uploadAvatar } from '../api/account';
|
||||||
|
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||||
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
|
import { refreshLocalUser } from '../authStore';
|
||||||
|
|
||||||
|
const emit = defineEmits(['close', 'updated']);
|
||||||
|
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
const errorMessage = ref('');
|
||||||
|
const uploadProgress = ref(0);
|
||||||
|
|
||||||
|
const selectedPath = ref<string | null>(null);
|
||||||
|
const previewUrl = ref<string | null>(null);
|
||||||
|
|
||||||
|
let unlistenDrag: UnlistenFn;
|
||||||
|
let unlistenHover: UnlistenFn;
|
||||||
|
let unlistenLeave: UnlistenFn;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// When files are hovered over the window
|
||||||
|
unlistenHover = await listen('tauri://drag-enter', () => {
|
||||||
|
isDragging.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
unlistenLeave = await listen('tauri://drag-leave', () => {
|
||||||
|
isDragging.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// When files are dropped
|
||||||
|
unlistenDrag = await listen<{ paths: string[] }>('tauri://drag-drop', (event) => {
|
||||||
|
isDragging.value = false;
|
||||||
|
const path = event.payload.paths[0];
|
||||||
|
if (path && isImage(path)) {
|
||||||
|
setFile(path);
|
||||||
|
} else {
|
||||||
|
errorMessage.value = "Please drop a valid image file.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (unlistenDrag) unlistenDrag();
|
||||||
|
if (unlistenHover) unlistenHover();
|
||||||
|
if (unlistenLeave) unlistenLeave();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function pickFile() {
|
||||||
|
try {
|
||||||
|
const selected = await open({
|
||||||
|
multiple: false,
|
||||||
|
filters: [{ name: 'Image', extensions: ['png', 'jpeg', 'jpg', 'webp'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected && typeof selected === 'string') {
|
||||||
|
setFile(selected);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to set path and preview
|
||||||
|
function setFile(path: string) {
|
||||||
|
selectedPath.value = path;
|
||||||
|
previewUrl.value = convertFileSrc(path);
|
||||||
|
errorMessage.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImage(path: string) {
|
||||||
|
return /\.(jpg|jpeg|png|webp|gif)$/i.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpload() {
|
||||||
|
if (!selectedPath.value) return;
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
uploadProgress.value = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await uploadAvatar(selectedPath.value, (progress, total) => {
|
||||||
|
uploadProgress.value = Math.round((progress / total) * 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
await refreshLocalUser();
|
||||||
|
|
||||||
|
emit('updated');
|
||||||
|
emit('close');
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Upload failed:", err);
|
||||||
|
errorMessage.value = 'Failed to upload avatar. Please try again.';
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone {
|
||||||
|
border: 2px dashed var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
min-height: 180px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.is-dragging {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(var(--accent-rgb), 0.1);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-content i {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
position: relative;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.is-dragging {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(var(--accent-rgb), 0.1);
|
||||||
|
transform: scale(1.02);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container:hover .overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay span {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -62,6 +62,7 @@ notifications-error-room-accept = An error occurred while accepting the invite.
|
|||||||
notifications-error-room-decline = An error occurred while declining the invite.
|
notifications-error-room-decline = An error occurred while declining the invite.
|
||||||
|
|
||||||
## Settings page
|
## Settings page
|
||||||
|
settings-loading = Loading settings...
|
||||||
settings-title = Settings
|
settings-title = Settings
|
||||||
settings-account = Account
|
settings-account = Account
|
||||||
settings-language = Language
|
settings-language = Language
|
||||||
@@ -74,6 +75,9 @@ settings-account-update-modal-title = Update your Account
|
|||||||
settings-account-update-subtitle = Only fill in the fields you wish to change.
|
settings-account-update-subtitle = Only fill in the fields you wish to change.
|
||||||
settings-new-password = New Password
|
settings-new-password = New Password
|
||||||
settings-new-password-confirm = Confirm new password
|
settings-new-password-confirm = Confirm new password
|
||||||
|
settings-upload-prompt = Upload an image
|
||||||
|
settings-upload-avatar-btn = Upload an avatar
|
||||||
|
settings-upload-avatar-title = Upload an avatar
|
||||||
settings-update-save = Save Changes
|
settings-update-save = Save Changes
|
||||||
settings-updating = Updating...
|
settings-updating = Updating...
|
||||||
settings-error-required = Username and Email are required.
|
settings-error-required = Username and Email are required.
|
||||||
@@ -82,3 +86,5 @@ settings-error-failed = Update failed
|
|||||||
## Shared
|
## Shared
|
||||||
shared-cancel = Cancel
|
shared-cancel = Cancel
|
||||||
shared-error = An error occurred
|
shared-error = An error occurred
|
||||||
|
shared-save = Save
|
||||||
|
shared-updating = Updating
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ notifications-error-room-accept = Erreur lors de l'acceptation de l'invitation.
|
|||||||
notifications-error-room-decline = Erreur lors du refus de l'invitation.
|
notifications-error-room-decline = Erreur lors du refus de l'invitation.
|
||||||
|
|
||||||
## Settings page
|
## Settings page
|
||||||
|
settings-loading = Chargement des paramètres...
|
||||||
settings-title = Paramètres
|
settings-title = Paramètres
|
||||||
settings-account = Compte
|
settings-account = Compte
|
||||||
settings-language = Langue
|
settings-language = Langue
|
||||||
@@ -74,11 +75,14 @@ settings-account-update-modal-title = Modifier votre compte
|
|||||||
settings-account-update-subtitle = Remplissez uniquement ce que vous souhaitez changer.
|
settings-account-update-subtitle = Remplissez uniquement ce que vous souhaitez changer.
|
||||||
settings-new-password = Nouveau mot de passe
|
settings-new-password = Nouveau mot de passe
|
||||||
settings-new-password-confirm = Confirmer le mot de passe
|
settings-new-password-confirm = Confirmer le mot de passe
|
||||||
settings-update-save = Enregistrer
|
settings-upload-prompt = Importer une image
|
||||||
settings-updating = Mise à jour...
|
settings-upload-avatar-btn = Importer un avatar
|
||||||
|
settings-upload-avatar-title = Importer un avatar
|
||||||
settings-error-required = Le nom d'utilisateur et l'email sont requis.
|
settings-error-required = Le nom d'utilisateur et l'email sont requis.
|
||||||
settings-error-failed = Échec de la mise à jour
|
settings-error-failed = Échec de la mise à jour
|
||||||
|
|
||||||
## Shared
|
## Shared
|
||||||
shared-cancel = Annuler
|
shared-cancel = Annuler
|
||||||
shared-error = Une erreur est survenue
|
shared-error = Une erreur est survenue
|
||||||
|
shared-save = Enregistrer
|
||||||
|
shared-updating = Mise à jour...
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-page">
|
<div class="settings-page">
|
||||||
<h1>{{ $t('settings-title') }}</h1>
|
<h1>{{ $t('settings-title') }}</h1>
|
||||||
|
|
||||||
<UpdateAccountModal v-if="showUpdateModal" :user="user" @close="showUpdateModal = false"
|
<UpdateAccountModal v-if="showUpdateModal" :user="user" @close="showUpdateModal = false" @updated="fetchUserData" />
|
||||||
@updated="fetchUserData" />
|
<UploadAvatarModal v-if="showAvatarModal" @close="showAvatarModal = false" @updated="fetchUserData" />
|
||||||
|
|
||||||
<h2>{{ $t('settings-account') }}</h2>
|
<h2>{{ $t('settings-account') }}</h2>
|
||||||
<div v-if="user" class="info-card">
|
<div v-if="user" class="info-card">
|
||||||
<button class="update-btn" @click="showUpdateModal = true">{{ $t('settings-update-btn') }}</button>
|
<div class="avatar-display">
|
||||||
<p><strong>{{ $t('settings-label-username') }}</strong> {{ user.username }}</p>
|
<img :src="user.avatar_url || '/tauri.svg'" class="avatar-img" />
|
||||||
<p><strong>{{ $t('settings-label-email') }}</strong> {{ user.email }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-else class="loading-state">
|
|
||||||
<p>{{ $t('settings-loading') }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>{{ $t('settings-language') }}</h2>
|
<button class="update-btn" @click="showAvatarModal = true">
|
||||||
<div class="input-group">
|
{{ $t('settings-upload-avatar-btn') || 'Change Avatar' }}
|
||||||
<div class="lang-grid">
|
|
||||||
<button v-for="lang in languages" :key="lang.code" class="lang-btn"
|
|
||||||
:class="{ active: currentLang === lang.code }" @click="changeLanguage(lang.code)">
|
|
||||||
{{ lang.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="logout-btn" @click="logout">
|
|
||||||
<i class="fa-solid fa-right-from-bracket"></i>
|
|
||||||
<span>{{ $t('settings-logout-btn') }}</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-display">
|
||||||
|
<div class="left">
|
||||||
|
<p><strong>{{ $t('settings-label-username') }}</strong> {{ user.username }}</p>
|
||||||
|
<p><strong>{{ $t('settings-label-email') }}</strong> {{ user.email }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="update-btn" @click="showUpdateModal = true">{{ $t('settings-update-btn') }}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="loading-state">
|
||||||
|
<p>{{ $t('settings-loading') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{ $t('settings-language') }}</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="lang-grid">
|
||||||
|
<button v-for="lang in languages" :key="lang.code" class="lang-btn"
|
||||||
|
:class="{ active: currentLang === lang.code }" @click="changeLanguage(lang.code)">
|
||||||
|
{{ lang.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="logout-btn" @click="logout">
|
||||||
|
<i class="fa-solid fa-right-from-bracket"></i>
|
||||||
|
<span>{{ $t('settings-logout-btn') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -42,6 +55,8 @@ import UpdateAccountModal from '../components/UpdateAccountModal.vue'
|
|||||||
import { useFluent } from 'fluent-vue'
|
import { useFluent } from 'fluent-vue'
|
||||||
import { saveLocalePreference, getLocalePreference } from "../authStore.ts"
|
import { saveLocalePreference, getLocalePreference } from "../authStore.ts"
|
||||||
import { getSupportedLanguagesMetadata, setLanguage } from '../i18n'
|
import { getSupportedLanguagesMetadata, setLanguage } from '../i18n'
|
||||||
|
import UploadAvatarModal from '../components/UploadAvatarModal.vue'
|
||||||
|
const showAvatarModal = ref(false)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = ref<User | null>(null)
|
const user = ref<User | null>(null)
|
||||||
@@ -52,112 +67,146 @@ const currentLang = ref('')
|
|||||||
const languages = computed(() => getSupportedLanguagesMetadata())
|
const languages = computed(() => getSupportedLanguagesMetadata())
|
||||||
|
|
||||||
async function fetchUserData() {
|
async function fetchUserData() {
|
||||||
try {
|
try {
|
||||||
const auth = await getAuthData()
|
const auth = await getAuthData()
|
||||||
user.value = auth.user
|
user.value = auth.user
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load user data:", err)
|
console.error("Failed to load user data:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const pref = await getLocalePreference()
|
const pref = await getLocalePreference()
|
||||||
// Synchronize the UI state with the actual active language
|
// Synchronize the UI state with the actual active language
|
||||||
currentLang.value = pref || (navigator.language.split('-')[0])
|
currentLang.value = pref || (navigator.language.split('-')[0])
|
||||||
|
|
||||||
fetchUserData()
|
fetchUserData()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function changeLanguage(code: string) {
|
async function changeLanguage(code: string) {
|
||||||
const actual = setLanguage(code)
|
const actual = setLanguage(code)
|
||||||
currentLang.value = actual
|
currentLang.value = actual
|
||||||
await saveLocalePreference(actual)
|
await saveLocalePreference(actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
authLogout()
|
authLogout()
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.settings-page {
|
.settings-page {
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1.5rem;
|
padding: 2rem 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card {
|
.info-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--panel);
|
display: flex;
|
||||||
border: 1px solid var(--border);
|
flex-direction: column;
|
||||||
border-radius: var(--radius);
|
align-items: flex-start;
|
||||||
padding: 1rem;
|
justify-content: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-btn {
|
.avatar-display,
|
||||||
position: absolute;
|
.info-display {
|
||||||
right: 10px;
|
display: flex;
|
||||||
top: 10px;
|
/* border: 1px solid var(--border); */
|
||||||
|
/* border-radius: var(--radius); */
|
||||||
|
/* background-color: rgba(255, 255, 255, 0.02); */
|
||||||
|
/* padding: 1rem; */
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .update-btn { */
|
||||||
|
/* position: absolute; */
|
||||||
|
/* right: 10px; */
|
||||||
|
/* top: 10px; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.lang-grid {
|
.lang-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn {
|
.lang-btn {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn.active {
|
.lang-btn.active {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #000;
|
color: #000;
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid var(--error);
|
border: 1px solid var(--error);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover {
|
.logout-btn:hover {
|
||||||
color: rgba(255, 80, 80, 0.8);
|
color: rgba(255, 80, 80, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover i {
|
.logout-btn:hover i {
|
||||||
color: rgba(255, 80, 80, 0.8);
|
color: rgba(255, 80, 80, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn i {
|
.logout-btn i {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-state {
|
.loading-state {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 720px) {
|
||||||
|
|
||||||
|
.avatar-display,
|
||||||
|
.info-display {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ref, computed } from 'vue'
|
|||||||
import { fetchFriendRequests } from './api/friends'
|
import { fetchFriendRequests } from './api/friends'
|
||||||
import { fetchRoomInvites } from './api/rooms'
|
import { fetchRoomInvites } from './api/rooms'
|
||||||
import type { FriendRequest, RoomInvite } from './types'
|
import type { FriendRequest, RoomInvite } from './types'
|
||||||
|
import { getAvatar } from './api/account'
|
||||||
|
|
||||||
export const initAuth = authStore.getAuthData
|
export const initAuth = authStore.getAuthData
|
||||||
export const getLastRoom = authStore.getLastRoom
|
export const getLastRoom = authStore.getLastRoom
|
||||||
@@ -19,7 +20,8 @@ export async function login(email: string, username: string, password: string) {
|
|||||||
let user: User = {
|
let user: User = {
|
||||||
uuid: res.uuid,
|
uuid: res.uuid,
|
||||||
username: res.username,
|
username: res.username,
|
||||||
email: res.email
|
email: res.email,
|
||||||
|
avatar_url: await getAvatar(res.uuid)
|
||||||
};
|
};
|
||||||
await authStore.saveAuthData(res.token, user)
|
await authStore.saveAuthData(res.token, user)
|
||||||
return { token: res.token, uuid: res.uuid, isAuthenticated: true }
|
return { token: res.token, uuid: res.uuid, isAuthenticated: true }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface User {
|
|||||||
uuid: string
|
uuid: string
|
||||||
username: string
|
username: string
|
||||||
email: string
|
email: string
|
||||||
|
avatar_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -402,6 +402,13 @@
|
|||||||
"@tauri-apps/cli-win32-ia32-msvc" "2.9.6"
|
"@tauri-apps/cli-win32-ia32-msvc" "2.9.6"
|
||||||
"@tauri-apps/cli-win32-x64-msvc" "2.9.6"
|
"@tauri-apps/cli-win32-x64-msvc" "2.9.6"
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-dialog@~2":
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.5.0.tgz#52057077b52cc51643ac9829d48c2c590e5e1a54"
|
||||||
|
integrity sha512-I0R0ygwRd9AN8Wj5GnzCogOlqu2+OWAtBd0zEC4+kQCI32fRowIyuhPCBoUv4h/lQt2bM39kHlxPHD5vDcFjiA==
|
||||||
|
dependencies:
|
||||||
|
"@tauri-apps/api" "^2.8.0"
|
||||||
|
|
||||||
"@tauri-apps/plugin-http@~2":
|
"@tauri-apps/plugin-http@~2":
|
||||||
version "2.5.4"
|
version "2.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-http/-/plugin-http-2.5.4.tgz#998a9cd02efa006fcbeddd92e8e51434ff3804dd"
|
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-http/-/plugin-http-2.5.4.tgz#998a9cd02efa006fcbeddd92e8e51434ff3804dd"
|
||||||
@@ -423,6 +430,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@tauri-apps/api" "^2.8.0"
|
"@tauri-apps/api" "^2.8.0"
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-upload@~2":
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-upload/-/plugin-upload-2.4.0.tgz#a08f7174471936429f5601c1477f40462915b2e8"
|
||||||
|
integrity sha512-ebhsqXmiELnpKu2p46EZG14UKxvbVP28BpJBiHzR+quWVrMxm40518PXTDlXXcJUW5CkbmP/6RL5ERSVXBL8sQ==
|
||||||
|
dependencies:
|
||||||
|
"@tauri-apps/api" "^2.8.0"
|
||||||
|
|
||||||
"@tauri-apps/plugin-websocket@~2":
|
"@tauri-apps/plugin-websocket@~2":
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-websocket/-/plugin-websocket-2.4.1.tgz#90774d5be337f92fc2a770cc0d98a0fbaea107fc"
|
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-websocket/-/plugin-websocket-2.4.1.tgz#90774d5be337f92fc2a770cc0d98a0fbaea107fc"
|
||||||
|
|||||||
Reference in New Issue
Block a user