From 5613a7c92b13a47693734343c23574bfa64435d3 Mon Sep 17 00:00:00 2001 From: matthieu Date: Sun, 15 Mar 2026 21:55:39 +0100 Subject: [PATCH] feat(avatar) : add avatar service, DTO update, and cropper dependency Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/package-lock.json | 37 ++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/services/avatar.ts | 24 +++++++++++++++++++ frontend/services/dto/user-data.ts | 1 + frontend/stores/auth.ts | 8 +++++++ 5 files changed, 71 insertions(+) create mode 100644 frontend/services/avatar.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4ba6645..23b6e5c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "nuxt-toast": "^1.4.0", "pinia": "^3.0.4", "vue": "^3.5.29", + "vue-advanced-cropper": "^2.8.9", "vue-chartjs": "^5.3.3", "vue-router": "^4.6.4" } @@ -6789,6 +6790,12 @@ "consola": "^3.2.3" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clipboardy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", @@ -7301,6 +7308,12 @@ } } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -7578,6 +7591,12 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/easy-bem": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz", + "integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13869,6 +13888,24 @@ } } }, + "node_modules/vue-advanced-cropper": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/vue-advanced-cropper/-/vue-advanced-cropper-2.8.9.tgz", + "integrity": "sha512-1jc5gO674kVGpJKekoaol6ZlwaF5VYDLSBwBOUpViW0IOrrRsyLw6XNszjEqgbavvqinlKNS6Kqlom3B5M72Tw==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.6", + "debounce": "^1.2.0", + "easy-bem": "^1.0.2" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-bundle-renderer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-2.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index bccdaab..5097f04 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "nuxt-toast": "^1.4.0", "pinia": "^3.0.4", "vue": "^3.5.29", + "vue-advanced-cropper": "^2.8.9", "vue-chartjs": "^5.3.3", "vue-router": "^4.6.4" } diff --git a/frontend/services/avatar.ts b/frontend/services/avatar.ts new file mode 100644 index 0000000..010aad2 --- /dev/null +++ b/frontend/services/avatar.ts @@ -0,0 +1,24 @@ +export function useAvatarService() { + const api = useApi() + + async function upload(userId: number, file: Blob): Promise<{ avatarUrl: string }> { + const formData = new FormData() + formData.append('file', file, 'avatar.png') + + return $fetch(`/api/users/${userId}/avatar`, { + method: 'POST', + body: formData, + credentials: 'include', + }) + } + + async function remove(userId: number): Promise { + await api.delete(`/users/${userId}/avatar`) + } + + function getUrl(userId: number): string { + return `/api/users/${userId}/avatar` + } + + return { upload, remove, getUrl } +} diff --git a/frontend/services/dto/user-data.ts b/frontend/services/dto/user-data.ts index c7d1163..b609a59 100644 --- a/frontend/services/dto/user-data.ts +++ b/frontend/services/dto/user-data.ts @@ -7,6 +7,7 @@ export type UserData = { roles: string[] client?: { id: number; name: string } | null allowedProjects?: Project[] + avatarUrl?: string | null } export type UserWrite = { diff --git a/frontend/stores/auth.ts b/frontend/stores/auth.ts index 11dfc38..ad2f727 100644 --- a/frontend/stores/auth.ts +++ b/frontend/stores/auth.ts @@ -58,6 +58,14 @@ export const useAuthStore = defineStore('auth', { this.checked = true this.isLoading = false } + }, + async refreshUser() { + try { + const me = await getCurrentUser() + this.user = me + } catch { + // Silently fail — user session might have expired + } } } })