From ac211f86b3f142890d91eaaa47c146d0f57acf5f Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Fri, 6 Feb 2026 20:41:07 -0500 Subject: [PATCH] feat(api): refactored to api client to be per feature --- src/frontend/.idea/.gitignore | 10 + src/frontend/.idea/encodings.xml | 4 + src/frontend/.idea/frontend.iml | 8 + src/frontend/.idea/modules.xml | 8 + src/frontend/.idea/vcs.xml | 6 + src/frontend/src/api/analytics.js | 14 + src/frontend/src/api/apiKeys.js | 15 + src/frontend/src/api/assets.js | 15 + src/frontend/src/api/auth.js | 47 +++ src/frontend/src/api/base.js | 91 ++++++ src/frontend/src/api/billing.js | 20 ++ src/frontend/src/api/client.js | 309 ------------------ src/frontend/src/api/domains.js | 19 ++ src/frontend/src/api/index.js | 12 + src/frontend/src/api/links.js | 44 +++ src/frontend/src/api/projects.js | 23 ++ src/frontend/src/api/qrcodes.js | 35 ++ src/frontend/src/api/usage.js | 8 + src/frontend/src/api/workspaces.js | 23 ++ src/frontend/src/stores/analytics.js | 36 ++ src/frontend/src/stores/assets.js | 59 ++++ src/frontend/src/stores/auth.js | 19 +- src/frontend/src/stores/domains.js | 73 +++++ src/frontend/src/stores/index.js | 8 + src/frontend/src/stores/links.js | 119 +++++++ src/frontend/src/stores/projects.js | 76 +++++ src/frontend/src/stores/qrcodes.js | 107 ++++++ src/frontend/src/stores/workspace.js | 269 +-------------- .../src/views/analytics/Analytics.vue | 14 +- .../src/views/auth/ForgotPassword.vue | 4 +- src/frontend/src/views/auth/ResetPassword.vue | 4 +- src/frontend/src/views/auth/VerifyEmail.vue | 4 +- src/frontend/src/views/billing/Billing.vue | 13 +- .../src/views/dashboard/Dashboard.vue | 20 +- src/frontend/src/views/domains/Domains.vue | 14 +- src/frontend/src/views/links/LinkDetail.vue | 8 +- src/frontend/src/views/links/Links.vue | 34 +- src/frontend/src/views/projects/Projects.vue | 14 +- .../src/views/qrcodes/QRCodeDesigner.vue | 32 +- .../src/views/qrcodes/QRCodeDetail.vue | 10 +- src/frontend/src/views/qrcodes/QRCodes.vue | 30 +- src/frontend/src/views/settings/Settings.vue | 23 +- 42 files changed, 1016 insertions(+), 685 deletions(-) create mode 100644 src/frontend/.idea/.gitignore create mode 100644 src/frontend/.idea/encodings.xml create mode 100644 src/frontend/.idea/frontend.iml create mode 100644 src/frontend/.idea/modules.xml create mode 100644 src/frontend/.idea/vcs.xml create mode 100644 src/frontend/src/api/analytics.js create mode 100644 src/frontend/src/api/apiKeys.js create mode 100644 src/frontend/src/api/assets.js create mode 100644 src/frontend/src/api/auth.js create mode 100644 src/frontend/src/api/base.js create mode 100644 src/frontend/src/api/billing.js delete mode 100644 src/frontend/src/api/client.js create mode 100644 src/frontend/src/api/domains.js create mode 100644 src/frontend/src/api/index.js create mode 100644 src/frontend/src/api/links.js create mode 100644 src/frontend/src/api/projects.js create mode 100644 src/frontend/src/api/qrcodes.js create mode 100644 src/frontend/src/api/usage.js create mode 100644 src/frontend/src/api/workspaces.js create mode 100644 src/frontend/src/stores/analytics.js create mode 100644 src/frontend/src/stores/assets.js create mode 100644 src/frontend/src/stores/domains.js create mode 100644 src/frontend/src/stores/index.js create mode 100644 src/frontend/src/stores/links.js create mode 100644 src/frontend/src/stores/projects.js create mode 100644 src/frontend/src/stores/qrcodes.js diff --git a/src/frontend/.idea/.gitignore b/src/frontend/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/src/frontend/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/src/frontend/.idea/encodings.xml b/src/frontend/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/src/frontend/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/frontend/.idea/frontend.iml b/src/frontend/.idea/frontend.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/src/frontend/.idea/frontend.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/frontend/.idea/modules.xml b/src/frontend/.idea/modules.xml new file mode 100644 index 0000000..f3d93d7 --- /dev/null +++ b/src/frontend/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/frontend/.idea/vcs.xml b/src/frontend/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/src/frontend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/src/api/analytics.js b/src/frontend/src/api/analytics.js new file mode 100644 index 0000000..6031713 --- /dev/null +++ b/src/frontend/src/api/analytics.js @@ -0,0 +1,14 @@ +import { baseClient } from './base.js'; + +export const analyticsApi = { + getWorkspaceAnalytics(workspaceId, period = '7d', startDate = null, endDate = null) { + const params = new URLSearchParams(); + if (startDate && endDate) { + params.set('startDate', startDate); + params.set('endDate', endDate); + } else { + params.set('period', period); + } + return baseClient.get(`/workspaces/${workspaceId}/analytics?${params.toString()}`); + }, +}; diff --git a/src/frontend/src/api/apiKeys.js b/src/frontend/src/api/apiKeys.js new file mode 100644 index 0000000..792aee2 --- /dev/null +++ b/src/frontend/src/api/apiKeys.js @@ -0,0 +1,15 @@ +import { baseClient } from './base.js'; + +export const apiKeysApi = { + list(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/api-keys`); + }, + + create(workspaceId, name, expiresAt = null, scopes = null) { + return baseClient.post(`/workspaces/${workspaceId}/api-keys`, { name, expiresAt, scopes }); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/api-keys/${id}`); + }, +}; diff --git a/src/frontend/src/api/assets.js b/src/frontend/src/api/assets.js new file mode 100644 index 0000000..8fd1238 --- /dev/null +++ b/src/frontend/src/api/assets.js @@ -0,0 +1,15 @@ +import { baseClient } from './base.js'; + +export const assetsApi = { + list(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/assets`); + }, + + upload(workspaceId, file) { + return baseClient.upload(`/workspaces/${workspaceId}/assets`, file); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/assets/${id}`); + }, +}; diff --git a/src/frontend/src/api/auth.js b/src/frontend/src/api/auth.js new file mode 100644 index 0000000..e7e64b0 --- /dev/null +++ b/src/frontend/src/api/auth.js @@ -0,0 +1,47 @@ +import { baseClient } from './base.js'; + +export const authApi = { + register(email, password) { + return baseClient.post('/auth/register', { email, password }); + }, + + login(email, password) { + return baseClient.post('/auth/login', { email, password }); + }, + + forgotPassword(email) { + return baseClient.post('/auth/forgot', { email }); + }, + + resetPassword(token, newPassword) { + return baseClient.post('/auth/reset', { token, newPassword }); + }, + + getProfile() { + return baseClient.get('/auth/profile'); + }, + + updateProfile(data) { + return baseClient.put('/auth/profile', data); + }, + + changePassword(currentPassword, newPassword) { + return baseClient.post('/auth/change-password', { currentPassword, newPassword }); + }, + + resendVerification() { + return baseClient.post('/auth/resend-verification'); + }, + + verifyEmail(token) { + return baseClient.post('/auth/verify-email', { token }); + }, + + deleteAccount(password) { + return baseClient.delete('/auth/account', { password }); + }, + + setToken(token) { + baseClient.setToken(token); + }, +}; diff --git a/src/frontend/src/api/base.js b/src/frontend/src/api/base.js new file mode 100644 index 0000000..5e4b1cf --- /dev/null +++ b/src/frontend/src/api/base.js @@ -0,0 +1,91 @@ +const API_BASE = 'https://localhost:42001'; + +class BaseClient { + constructor() { + this.token = localStorage.getItem('token'); + } + + setToken(token) { + this.token = token; + if (token) { + localStorage.setItem('token', token); + } else { + localStorage.removeItem('token'); + } + } + + async #request(method, path, body = null) { + const headers = {}; + + if (this.token) { + headers['Authorization'] = `Bearer ${this.token}`; + } + + const options = { method, headers }; + if (body) { + headers['Content-Type'] = 'application/json'; + options.body = JSON.stringify(body); + } + + const response = await fetch(`${API_BASE}${path}`, options); + + if (response.status === 401) { + this.setToken(null); + window.location.href = '/login'; + throw new Error('Unauthorized'); + } + + const text = await response.text(); + const data = text ? JSON.parse(text) : null; + + if (!response.ok) { + throw new Error(data?.message || `HTTP ${response.status}`); + } + + return data; + } + + get(path) { + return this.#request('GET', path); + } + + post(path, body = null) { + return this.#request('POST', path, body); + } + + put(path, body) { + return this.#request('PUT', path, body); + } + + delete(path, body = null) { + return this.#request('DELETE', path, body); + } + + async upload(path, file) { + const headers = {}; + if (this.token) { + headers['Authorization'] = `Bearer ${this.token}`; + } + + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch(`${API_BASE}${path}`, { + method: 'POST', + headers, + body: formData, + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data?.message || `HTTP ${response.status}`); + } + return data; + } + + getBaseUrl() { + return API_BASE; + } +} + +export const baseClient = new BaseClient(); diff --git a/src/frontend/src/api/billing.js b/src/frontend/src/api/billing.js new file mode 100644 index 0000000..706e7d5 --- /dev/null +++ b/src/frontend/src/api/billing.js @@ -0,0 +1,20 @@ +import { baseClient } from './base.js'; + +export const billingApi = { + createCheckoutSession(workspaceId, plan, successUrl, cancelUrl) { + return baseClient.post('/billing/checkout', { + workspaceId, + plan, + successUrl, + cancelUrl, + }); + }, + + createPortalSession(returnUrl) { + return baseClient.post('/billing/portal', { returnUrl }); + }, + + getSubscription(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/subscription`); + }, +}; diff --git a/src/frontend/src/api/client.js b/src/frontend/src/api/client.js deleted file mode 100644 index 2467b10..0000000 --- a/src/frontend/src/api/client.js +++ /dev/null @@ -1,309 +0,0 @@ -const API_BASE = 'https://localhost:42001'; - -class ApiClient { - constructor() { - this.token = localStorage.getItem('token'); - } - - setToken(token) { - this.token = token; - if (token) { - localStorage.setItem('token', token); - } else { - localStorage.removeItem('token'); - } - } - - async request(method, path, body = null) { - const headers = { - 'Content-Type': 'application/json', - }; - - if (this.token) { - headers['Authorization'] = `Bearer ${this.token}`; - } - - const options = { method, headers }; - if (body) { - options.body = JSON.stringify(body); - } - - const response = await fetch(`${API_BASE}${path}`, options); - - if (response.status === 401) { - this.setToken(null); - window.location.href = '/login'; - throw new Error('Unauthorized'); - } - - const text = await response.text(); - const data = text ? JSON.parse(text) : null; - - if (!response.ok) { - throw new Error(data?.message || `HTTP ${response.status}`); - } - - return data; - } - - async upload(path, file) { - const headers = {}; - if (this.token) { - headers['Authorization'] = `Bearer ${this.token}`; - } - - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch(`${API_BASE}${path}`, { - method: 'POST', - headers, - body: formData, - }); - - const data = await response.json(); - if (!response.ok) { - throw new Error(data?.message || `HTTP ${response.status}`); - } - return data; - } - - // Auth - register(email, password) { - return this.request('POST', '/auth/register', { email, password }); - } - - login(email, password) { - return this.request('POST', '/auth/login', { email, password }); - } - - forgotPassword(email) { - return this.request('POST', '/auth/forgot', { email }); - } - - resetPassword(token, newPassword) { - return this.request('POST', '/auth/reset', { token, newPassword }); - } - - getProfile() { - return this.request('GET', '/auth/profile'); - } - - updateProfile(data) { - return this.request('PUT', '/auth/profile', data); - } - - changePassword(currentPassword, newPassword) { - return this.request('POST', '/auth/change-password', { currentPassword, newPassword }); - } - - resendVerification() { - return this.request('POST', '/auth/resend-verification'); - } - - verifyEmail(token) { - return this.request('POST', '/auth/verify-email', { token }); - } - - deleteAccount(password) { - return this.request('DELETE', '/auth/account', { password }); - } - - // Workspaces - listWorkspaces() { - return this.request('GET', '/workspaces'); - } - - createWorkspace(name) { - return this.request('POST', '/workspaces', { name }); - } - - getWorkspace(id) { - return this.request('GET', `/workspaces/${id}`); - } - - updateWorkspace(id, name) { - return this.request('PUT', `/workspaces/${id}`, { name }); - } - - deleteWorkspace(id) { - return this.request('DELETE', `/workspaces/${id}`); - } - - // Projects - listProjects(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/projects`); - } - - createProject(workspaceId, name, description = '') { - return this.request('POST', `/workspaces/${workspaceId}/projects`, { name, description }); - } - - getProject(workspaceId, id) { - return this.request('GET', `/workspaces/${workspaceId}/projects/${id}`); - } - - updateProject(workspaceId, id, data) { - return this.request('PUT', `/workspaces/${workspaceId}/projects/${id}`, data); - } - - deleteProject(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/projects/${id}`); - } - - // Links - listLinks(workspaceId, params = {}) { - const query = new URLSearchParams(params).toString(); - const path = `/workspaces/${workspaceId}/links${query ? `?${query}` : ''}`; - return this.request('GET', path); - } - - restoreLink(workspaceId, id) { - return this.request('POST', `/workspaces/${workspaceId}/links/${id}/restore`); - } - - createLink(workspaceId, data) { - return this.request('POST', `/workspaces/${workspaceId}/links`, data); - } - - getLink(workspaceId, id) { - return this.request('GET', `/workspaces/${workspaceId}/links/${id}`); - } - - updateLink(workspaceId, id, data) { - return this.request('PUT', `/workspaces/${workspaceId}/links/${id}`, data); - } - - deleteLink(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/links/${id}`); - } - - bulkCreateLinks(workspaceId, links) { - return this.request('POST', `/workspaces/${workspaceId}/links/bulk`, { links }); - } - - getLinkAnalytics(workspaceId, linkId, period = '7d', startDate = null, endDate = null) { - const params = new URLSearchParams(); - if (startDate && endDate) { - params.set('startDate', startDate); - params.set('endDate', endDate); - } else { - params.set('period', period); - } - return this.request('GET', `/workspaces/${workspaceId}/links/${linkId}/analytics?${params.toString()}`); - } - - // QR Codes - listQRCodes(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/qrcodes`); - } - - createQRCode(workspaceId, data) { - return this.request('POST', `/workspaces/${workspaceId}/qrcodes`, data); - } - - getQRCode(workspaceId, id) { - return this.request('GET', `/workspaces/${workspaceId}/qrcodes/${id}`); - } - - updateQRCode(workspaceId, id, data) { - return this.request('PUT', `/workspaces/${workspaceId}/qrcodes/${id}`, data); - } - - deleteQRCode(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/qrcodes/${id}`); - } - - getQRCodePreview(workspaceId, id) { - return this.request('GET', `/workspaces/${workspaceId}/qrcodes/${id}/preview`); - } - - getQRCodeExportUrl(workspaceId, id, format = 'png', size = 512) { - return `${API_BASE}/workspaces/${workspaceId}/qrcodes/${id}/export?format=${format}&size=${size}`; - } - - getQRCodeAnalytics(workspaceId, qrCodeId, period = '7d') { - return this.request('GET', `/workspaces/${workspaceId}/qrcodes/${qrCodeId}/analytics?period=${period}`); - } - - // Analytics - getWorkspaceAnalytics(workspaceId, period = '7d', startDate = null, endDate = null) { - const params = new URLSearchParams(); - if (startDate && endDate) { - params.set('startDate', startDate); - params.set('endDate', endDate); - } else { - params.set('period', period); - } - return this.request('GET', `/workspaces/${workspaceId}/analytics?${params.toString()}`); - } - - // Domains - listDomains(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/domains`); - } - - addDomain(workspaceId, hostname) { - return this.request('POST', `/workspaces/${workspaceId}/domains`, { hostname }); - } - - deleteDomain(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/domains/${id}`); - } - - verifyDomain(workspaceId, id) { - return this.request('POST', `/workspaces/${workspaceId}/domains/${id}/verify`, {}); - } - - // Assets - listAssets(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/assets`); - } - - uploadAsset(workspaceId, file) { - return this.upload(`/workspaces/${workspaceId}/assets`, file); - } - - deleteAsset(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/assets/${id}`); - } - - // Billing - createCheckoutSession(workspaceId, plan, successUrl, cancelUrl) { - return this.request('POST', '/billing/checkout', { - workspaceId, - plan, - successUrl, - cancelUrl, - }); - } - - createPortalSession(returnUrl) { - return this.request('POST', '/billing/portal', { returnUrl }); - } - - getSubscription(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/subscription`); - } - - // Usage - getUsage(workspaceId = null) { - const path = workspaceId ? `/usage?workspaceId=${workspaceId}` : '/usage'; - return this.request('GET', path); - } - - // API Keys - listApiKeys(workspaceId) { - return this.request('GET', `/workspaces/${workspaceId}/api-keys`); - } - - createApiKey(workspaceId, name, expiresAt = null, scopes = null) { - return this.request('POST', `/workspaces/${workspaceId}/api-keys`, { name, expiresAt, scopes }); - } - - deleteApiKey(workspaceId, id) { - return this.request('DELETE', `/workspaces/${workspaceId}/api-keys/${id}`); - } -} - -export const api = new ApiClient(); diff --git a/src/frontend/src/api/domains.js b/src/frontend/src/api/domains.js new file mode 100644 index 0000000..b4ee277 --- /dev/null +++ b/src/frontend/src/api/domains.js @@ -0,0 +1,19 @@ +import { baseClient } from './base.js'; + +export const domainsApi = { + list(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/domains`); + }, + + add(workspaceId, hostname) { + return baseClient.post(`/workspaces/${workspaceId}/domains`, { hostname }); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/domains/${id}`); + }, + + verify(workspaceId, id) { + return baseClient.post(`/workspaces/${workspaceId}/domains/${id}/verify`); + }, +}; diff --git a/src/frontend/src/api/index.js b/src/frontend/src/api/index.js new file mode 100644 index 0000000..d94be06 --- /dev/null +++ b/src/frontend/src/api/index.js @@ -0,0 +1,12 @@ +export { baseClient } from './base.js'; +export { authApi } from './auth.js'; +export { workspacesApi } from './workspaces.js'; +export { projectsApi } from './projects.js'; +export { linksApi } from './links.js'; +export { qrcodesApi } from './qrcodes.js'; +export { domainsApi } from './domains.js'; +export { assetsApi } from './assets.js'; +export { billingApi } from './billing.js'; +export { analyticsApi } from './analytics.js'; +export { apiKeysApi } from './apiKeys.js'; +export { usageApi } from './usage.js'; diff --git a/src/frontend/src/api/links.js b/src/frontend/src/api/links.js new file mode 100644 index 0000000..0ac1227 --- /dev/null +++ b/src/frontend/src/api/links.js @@ -0,0 +1,44 @@ +import { baseClient } from './base.js'; + +export const linksApi = { + list(workspaceId, params = {}) { + const query = new URLSearchParams(params).toString(); + const path = `/workspaces/${workspaceId}/links${query ? `?${query}` : ''}`; + return baseClient.get(path); + }, + + create(workspaceId, data) { + return baseClient.post(`/workspaces/${workspaceId}/links`, data); + }, + + get(workspaceId, id) { + return baseClient.get(`/workspaces/${workspaceId}/links/${id}`); + }, + + update(workspaceId, id, data) { + return baseClient.put(`/workspaces/${workspaceId}/links/${id}`, data); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/links/${id}`); + }, + + restore(workspaceId, id) { + return baseClient.post(`/workspaces/${workspaceId}/links/${id}/restore`); + }, + + bulkCreate(workspaceId, links) { + return baseClient.post(`/workspaces/${workspaceId}/links/bulk`, { links }); + }, + + getAnalytics(workspaceId, linkId, period = '7d', startDate = null, endDate = null) { + const params = new URLSearchParams(); + if (startDate && endDate) { + params.set('startDate', startDate); + params.set('endDate', endDate); + } else { + params.set('period', period); + } + return baseClient.get(`/workspaces/${workspaceId}/links/${linkId}/analytics?${params.toString()}`); + }, +}; diff --git a/src/frontend/src/api/projects.js b/src/frontend/src/api/projects.js new file mode 100644 index 0000000..f6bcf39 --- /dev/null +++ b/src/frontend/src/api/projects.js @@ -0,0 +1,23 @@ +import { baseClient } from './base.js'; + +export const projectsApi = { + list(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/projects`); + }, + + create(workspaceId, name, description = '') { + return baseClient.post(`/workspaces/${workspaceId}/projects`, { name, description }); + }, + + get(workspaceId, id) { + return baseClient.get(`/workspaces/${workspaceId}/projects/${id}`); + }, + + update(workspaceId, id, data) { + return baseClient.put(`/workspaces/${workspaceId}/projects/${id}`, data); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/projects/${id}`); + }, +}; diff --git a/src/frontend/src/api/qrcodes.js b/src/frontend/src/api/qrcodes.js new file mode 100644 index 0000000..280b152 --- /dev/null +++ b/src/frontend/src/api/qrcodes.js @@ -0,0 +1,35 @@ +import { baseClient } from './base.js'; + +export const qrcodesApi = { + list(workspaceId) { + return baseClient.get(`/workspaces/${workspaceId}/qrcodes`); + }, + + create(workspaceId, data) { + return baseClient.post(`/workspaces/${workspaceId}/qrcodes`, data); + }, + + get(workspaceId, id) { + return baseClient.get(`/workspaces/${workspaceId}/qrcodes/${id}`); + }, + + update(workspaceId, id, data) { + return baseClient.put(`/workspaces/${workspaceId}/qrcodes/${id}`, data); + }, + + delete(workspaceId, id) { + return baseClient.delete(`/workspaces/${workspaceId}/qrcodes/${id}`); + }, + + getPreview(workspaceId, id) { + return baseClient.get(`/workspaces/${workspaceId}/qrcodes/${id}/preview`); + }, + + getExportUrl(workspaceId, id, format = 'png', size = 512) { + return `${baseClient.getBaseUrl()}/workspaces/${workspaceId}/qrcodes/${id}/export?format=${format}&size=${size}`; + }, + + getAnalytics(workspaceId, qrCodeId, period = '7d') { + return baseClient.get(`/workspaces/${workspaceId}/qrcodes/${qrCodeId}/analytics?period=${period}`); + }, +}; diff --git a/src/frontend/src/api/usage.js b/src/frontend/src/api/usage.js new file mode 100644 index 0000000..c836a3d --- /dev/null +++ b/src/frontend/src/api/usage.js @@ -0,0 +1,8 @@ +import { baseClient } from './base.js'; + +export const usageApi = { + get(workspaceId = null) { + const path = workspaceId ? `/usage?workspaceId=${workspaceId}` : '/usage'; + return baseClient.get(path); + }, +}; diff --git a/src/frontend/src/api/workspaces.js b/src/frontend/src/api/workspaces.js new file mode 100644 index 0000000..37b7c2f --- /dev/null +++ b/src/frontend/src/api/workspaces.js @@ -0,0 +1,23 @@ +import { baseClient } from './base.js'; + +export const workspacesApi = { + list() { + return baseClient.get('/workspaces'); + }, + + create(name) { + return baseClient.post('/workspaces', { name }); + }, + + get(id) { + return baseClient.get(`/workspaces/${id}`); + }, + + update(id, name) { + return baseClient.put(`/workspaces/${id}`, { name }); + }, + + delete(id) { + return baseClient.delete(`/workspaces/${id}`); + }, +}; diff --git a/src/frontend/src/stores/analytics.js b/src/frontend/src/stores/analytics.js new file mode 100644 index 0000000..945f7cb --- /dev/null +++ b/src/frontend/src/stores/analytics.js @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia'; +import { analyticsApi } from '../api/analytics.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useAnalyticsStore = defineStore('analytics', { + state: () => ({ + analytics: null, + loading: false, + error: null, + }), + + actions: { + async fetchAnalytics(period = '7d', startDate = null, endDate = null) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + this.analytics = await analyticsApi.getWorkspaceAnalytics( + workspaceStore.currentWorkspaceId, + period, + startDate, + endDate + ); + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + clearAll() { + this.analytics = null; + }, + }, +}); diff --git a/src/frontend/src/stores/assets.js b/src/frontend/src/stores/assets.js new file mode 100644 index 0000000..d9be4bf --- /dev/null +++ b/src/frontend/src/stores/assets.js @@ -0,0 +1,59 @@ +import { defineStore } from 'pinia'; +import { assetsApi } from '../api/assets.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useAssetsStore = defineStore('assets', { + state: () => ({ + assets: [], + loading: false, + error: null, + }), + + actions: { + async fetchAssets() { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + const response = await assetsApi.list(workspaceStore.currentWorkspaceId); + this.assets = response.assets || []; + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + async uploadAsset(file) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const asset = await assetsApi.upload(workspaceStore.currentWorkspaceId, file); + this.assets.unshift(asset); + return asset; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async deleteAsset(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + await assetsApi.delete(workspaceStore.currentWorkspaceId, id); + this.assets = this.assets.filter(a => a.id !== id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + clearAll() { + this.assets = []; + }, + }, +}); diff --git a/src/frontend/src/stores/auth.js b/src/frontend/src/stores/auth.js index ee19e46..a781fbd 100644 --- a/src/frontend/src/stores/auth.js +++ b/src/frontend/src/stores/auth.js @@ -1,5 +1,5 @@ import { defineStore } from 'pinia'; -import { api } from '../api/client'; +import { authApi } from '../api/auth.js'; export const useAuthStore = defineStore('auth', { state: () => ({ @@ -19,12 +19,11 @@ export const useAuthStore = defineStore('auth', { if (this.initialized) return; if (this.token) { - api.setToken(this.token); + authApi.setToken(this.token); try { - const profile = await api.getProfile(); + const profile = await authApi.getProfile(); this.user = profile; } catch (err) { - // Token is invalid, clear it this.logout(); } } @@ -35,11 +34,11 @@ export const useAuthStore = defineStore('auth', { this.loading = true; this.error = null; try { - const response = await api.register(email, password); + const response = await authApi.register(email, password); this.token = response.token; this.user = { email: response.email, isVerified: false }; localStorage.setItem('token', response.token); - api.setToken(response.token); + authApi.setToken(response.token); return true; } catch (err) { this.error = err.message; @@ -53,11 +52,11 @@ export const useAuthStore = defineStore('auth', { this.loading = true; this.error = null; try { - const response = await api.login(email, password); + const response = await authApi.login(email, password); this.token = response.token; this.user = { email: response.email }; localStorage.setItem('token', response.token); - api.setToken(response.token); + authApi.setToken(response.token); return true; } catch (err) { this.error = err.message; @@ -72,12 +71,12 @@ export const useAuthStore = defineStore('auth', { this.user = null; localStorage.removeItem('token'); localStorage.removeItem('currentWorkspaceId'); - api.setToken(null); + authApi.setToken(null); }, async fetchProfile() { try { - this.user = await api.getProfile(); + this.user = await authApi.getProfile(); } catch (err) { this.error = err.message; } diff --git a/src/frontend/src/stores/domains.js b/src/frontend/src/stores/domains.js new file mode 100644 index 0000000..ca22065 --- /dev/null +++ b/src/frontend/src/stores/domains.js @@ -0,0 +1,73 @@ +import { defineStore } from 'pinia'; +import { domainsApi } from '../api/domains.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useDomainsStore = defineStore('domains', { + state: () => ({ + domains: [], + loading: false, + error: null, + }), + + actions: { + async fetchDomains() { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + const response = await domainsApi.list(workspaceStore.currentWorkspaceId); + this.domains = response.domains || []; + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + async addDomain(hostname) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const domain = await domainsApi.add(workspaceStore.currentWorkspaceId, hostname); + this.domains.unshift(domain); + return domain; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async verifyDomain(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const result = await domainsApi.verify(workspaceStore.currentWorkspaceId, id); + await this.fetchDomains(); + return result; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async deleteDomain(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + await domainsApi.delete(workspaceStore.currentWorkspaceId, id); + this.domains = this.domains.filter(d => d.id !== id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + clearAll() { + this.domains = []; + }, + }, +}); diff --git a/src/frontend/src/stores/index.js b/src/frontend/src/stores/index.js new file mode 100644 index 0000000..4e60aba --- /dev/null +++ b/src/frontend/src/stores/index.js @@ -0,0 +1,8 @@ +export { useAuthStore } from './auth.js'; +export { useWorkspaceStore } from './workspace.js'; +export { useProjectsStore } from './projects.js'; +export { useLinksStore } from './links.js'; +export { useQRCodesStore } from './qrcodes.js'; +export { useDomainsStore } from './domains.js'; +export { useAssetsStore } from './assets.js'; +export { useAnalyticsStore } from './analytics.js'; diff --git a/src/frontend/src/stores/links.js b/src/frontend/src/stores/links.js new file mode 100644 index 0000000..066d00e --- /dev/null +++ b/src/frontend/src/stores/links.js @@ -0,0 +1,119 @@ +import { defineStore } from 'pinia'; +import { linksApi } from '../api/links.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useLinksStore = defineStore('links', { + state: () => ({ + links: [], + loading: false, + error: null, + }), + + actions: { + async fetchLinks(params = {}) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + const response = await linksApi.list(workspaceStore.currentWorkspaceId, params); + this.links = response.links || []; + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + async createLink(data) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const link = await linksApi.create(workspaceStore.currentWorkspaceId, data); + this.links.unshift(link); + return link; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async updateLink(id, data) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const link = await linksApi.update(workspaceStore.currentWorkspaceId, id, data); + const index = this.links.findIndex(l => l.id === id); + if (index !== -1) { + this.links[index] = link; + } + return link; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async deleteLink(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + await linksApi.delete(workspaceStore.currentWorkspaceId, id); + this.links = this.links.filter(l => l.id !== id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async restoreLink(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const link = await linksApi.restore(workspaceStore.currentWorkspaceId, id); + const index = this.links.findIndex(l => l.id === id); + if (index !== -1) { + this.links[index] = link; + } + return link; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async bulkCreateLinks(links) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const result = await linksApi.bulkCreate(workspaceStore.currentWorkspaceId, links); + await this.fetchLinks(); + return result; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async getLinkAnalytics(linkId, period = '7d', startDate = null, endDate = null) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + return await linksApi.getAnalytics(workspaceStore.currentWorkspaceId, linkId, period, startDate, endDate); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + clearAll() { + this.links = []; + }, + }, +}); diff --git a/src/frontend/src/stores/projects.js b/src/frontend/src/stores/projects.js new file mode 100644 index 0000000..e1fd4a0 --- /dev/null +++ b/src/frontend/src/stores/projects.js @@ -0,0 +1,76 @@ +import { defineStore } from 'pinia'; +import { projectsApi } from '../api/projects.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useProjectsStore = defineStore('projects', { + state: () => ({ + projects: [], + loading: false, + error: null, + }), + + actions: { + async fetchProjects() { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + const response = await projectsApi.list(workspaceStore.currentWorkspaceId); + this.projects = response.projects || []; + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + async createProject(name, description = '') { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const project = await projectsApi.create(workspaceStore.currentWorkspaceId, name, description); + this.projects.unshift(project); + return project; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async updateProject(id, data) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const project = await projectsApi.update(workspaceStore.currentWorkspaceId, id, data); + const index = this.projects.findIndex(p => p.id === id); + if (index !== -1) { + this.projects[index] = project; + } + return project; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async deleteProject(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + await projectsApi.delete(workspaceStore.currentWorkspaceId, id); + this.projects = this.projects.filter(p => p.id !== id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + clearAll() { + this.projects = []; + }, + }, +}); diff --git a/src/frontend/src/stores/qrcodes.js b/src/frontend/src/stores/qrcodes.js new file mode 100644 index 0000000..2b6ef1a --- /dev/null +++ b/src/frontend/src/stores/qrcodes.js @@ -0,0 +1,107 @@ +import { defineStore } from 'pinia'; +import { qrcodesApi } from '../api/qrcodes.js'; +import { useWorkspaceStore } from './workspace.js'; + +export const useQRCodesStore = defineStore('qrcodes', { + state: () => ({ + qrcodes: [], + loading: false, + error: null, + }), + + actions: { + async fetchQRCodes() { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + this.loading = true; + try { + const response = await qrcodesApi.list(workspaceStore.currentWorkspaceId); + this.qrcodes = response.qrCodes || []; + } catch (err) { + this.error = err.message; + } finally { + this.loading = false; + } + }, + + async createQRCode(data) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const qrcode = await qrcodesApi.create(workspaceStore.currentWorkspaceId, data); + this.qrcodes.unshift(qrcode); + return qrcode; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async updateQRCode(id, data) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + const qrcode = await qrcodesApi.update(workspaceStore.currentWorkspaceId, id, data); + const index = this.qrcodes.findIndex(q => q.id === id); + if (index !== -1) { + this.qrcodes[index] = qrcode; + } + return qrcode; + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async deleteQRCode(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + await qrcodesApi.delete(workspaceStore.currentWorkspaceId, id); + this.qrcodes = this.qrcodes.filter(q => q.id !== id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + async getQRCodePreview(id) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + return await qrcodesApi.getPreview(workspaceStore.currentWorkspaceId, id); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + getQRCodeExportUrl(id, format = 'png', size = 512) { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return null; + + return qrcodesApi.getExportUrl(workspaceStore.currentWorkspaceId, id, format, size); + }, + + async getQRCodeAnalytics(qrCodeId, period = '7d') { + const workspaceStore = useWorkspaceStore(); + if (!workspaceStore.currentWorkspaceId) return; + + try { + return await qrcodesApi.getAnalytics(workspaceStore.currentWorkspaceId, qrCodeId, period); + } catch (err) { + this.error = err.message; + throw err; + } + }, + + clearAll() { + this.qrcodes = []; + }, + }, +}); diff --git a/src/frontend/src/stores/workspace.js b/src/frontend/src/stores/workspace.js index f5b86b3..8b15d83 100644 --- a/src/frontend/src/stores/workspace.js +++ b/src/frontend/src/stores/workspace.js @@ -1,16 +1,10 @@ import { defineStore } from 'pinia'; -import { api } from '../api/client'; +import { workspacesApi } from '../api/workspaces.js'; export const useWorkspaceStore = defineStore('workspace', { state: () => ({ workspaces: [], currentWorkspace: null, - projects: [], - links: [], - qrcodes: [], - domains: [], - assets: [], - analytics: null, loading: false, error: null, initialized: false, @@ -35,10 +29,9 @@ export const useWorkspaceStore = defineStore('workspace', { async fetchWorkspaces() { this.loading = true; try { - const response = await api.listWorkspaces(); + const response = await workspacesApi.list(); this.workspaces = response.workspaces || []; - // Restore saved workspace or use first one const savedId = localStorage.getItem('currentWorkspaceId'); const saved = savedId ? this.workspaces.find(w => w.id === savedId) : null; @@ -61,18 +54,11 @@ export const useWorkspaceStore = defineStore('workspace', { } else { localStorage.removeItem('currentWorkspaceId'); } - // Clear workspace-specific data - this.projects = []; - this.links = []; - this.qrcodes = []; - this.domains = []; - this.assets = []; - this.analytics = null; }, async createWorkspace(name) { try { - const workspace = await api.createWorkspace(name); + const workspace = await workspacesApi.create(name); this.workspaces.push(workspace); return workspace; } catch (err) { @@ -83,7 +69,7 @@ export const useWorkspaceStore = defineStore('workspace', { async updateWorkspace(id, name) { try { - const updated = await api.updateWorkspace(id, name); + const updated = await workspacesApi.update(id, name); const index = this.workspaces.findIndex(w => w.id === id); if (index !== -1) { this.workspaces[index] = updated; @@ -100,7 +86,7 @@ export const useWorkspaceStore = defineStore('workspace', { async deleteWorkspace(id) { try { - await api.deleteWorkspace(id); + await workspacesApi.delete(id); this.workspaces = this.workspaces.filter(w => w.id !== id); if (this.currentWorkspace?.id === id) { this.setCurrentWorkspace(this.workspaces[0] || null); @@ -111,254 +97,9 @@ export const useWorkspaceStore = defineStore('workspace', { } }, - // Projects - async fetchProjects() { - if (!this.currentWorkspaceId) return; - try { - const response = await api.listProjects(this.currentWorkspaceId); - this.projects = response.projects || []; - } catch (err) { - this.error = err.message; - } - }, - - async createProject(name, description = '') { - if (!this.currentWorkspaceId) return; - try { - const project = await api.createProject(this.currentWorkspaceId, name, description); - this.projects.unshift(project); - return project; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async updateProject(id, data) { - if (!this.currentWorkspaceId) return; - try { - const project = await api.updateProject(this.currentWorkspaceId, id, data); - const index = this.projects.findIndex(p => p.id === id); - if (index !== -1) { - this.projects[index] = project; - } - return project; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async deleteProject(id) { - if (!this.currentWorkspaceId) return; - try { - await api.deleteProject(this.currentWorkspaceId, id); - this.projects = this.projects.filter(p => p.id !== id); - } catch (err) { - this.error = err.message; - throw err; - } - }, - - // Links - async fetchLinks(params = {}) { - if (!this.currentWorkspaceId) return; - try { - const response = await api.listLinks(this.currentWorkspaceId, params); - this.links = response.links || []; - } catch (err) { - this.error = err.message; - } - }, - - async createLink(data) { - if (!this.currentWorkspaceId) return; - try { - const link = await api.createLink(this.currentWorkspaceId, data); - this.links.unshift(link); - return link; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async updateLink(id, data) { - if (!this.currentWorkspaceId) return; - try { - const link = await api.updateLink(this.currentWorkspaceId, id, data); - const index = this.links.findIndex(l => l.id === id); - if (index !== -1) { - this.links[index] = link; - } - return link; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async deleteLink(id) { - if (!this.currentWorkspaceId) return; - try { - await api.deleteLink(this.currentWorkspaceId, id); - this.links = this.links.filter(l => l.id !== id); - } catch (err) { - this.error = err.message; - throw err; - } - }, - - // QR Codes - async fetchQRCodes() { - if (!this.currentWorkspaceId) return; - try { - const response = await api.listQRCodes(this.currentWorkspaceId); - this.qrcodes = response.qrCodes || []; - } catch (err) { - this.error = err.message; - } - }, - - async createQRCode(data) { - if (!this.currentWorkspaceId) return; - try { - const qrcode = await api.createQRCode(this.currentWorkspaceId, data); - this.qrcodes.unshift(qrcode); - return qrcode; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async updateQRCode(id, data) { - if (!this.currentWorkspaceId) return; - try { - const qrcode = await api.updateQRCode(this.currentWorkspaceId, id, data); - const index = this.qrcodes.findIndex(q => q.id === id); - if (index !== -1) { - this.qrcodes[index] = qrcode; - } - return qrcode; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async deleteQRCode(id) { - if (!this.currentWorkspaceId) return; - try { - await api.deleteQRCode(this.currentWorkspaceId, id); - this.qrcodes = this.qrcodes.filter(q => q.id !== id); - } catch (err) { - this.error = err.message; - throw err; - } - }, - - // Domains - async fetchDomains() { - if (!this.currentWorkspaceId) return; - try { - const response = await api.listDomains(this.currentWorkspaceId); - this.domains = response.domains || []; - } catch (err) { - this.error = err.message; - } - }, - - async addDomain(hostname) { - if (!this.currentWorkspaceId) return; - try { - const domain = await api.addDomain(this.currentWorkspaceId, hostname); - this.domains.unshift(domain); - return domain; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async verifyDomain(id) { - if (!this.currentWorkspaceId) return; - try { - const result = await api.verifyDomain(this.currentWorkspaceId, id); - // Refresh domains to get updated status - await this.fetchDomains(); - return result; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async deleteDomain(id) { - if (!this.currentWorkspaceId) return; - try { - await api.deleteDomain(this.currentWorkspaceId, id); - this.domains = this.domains.filter(d => d.id !== id); - } catch (err) { - this.error = err.message; - throw err; - } - }, - - // Assets - async fetchAssets() { - if (!this.currentWorkspaceId) return; - try { - const response = await api.listAssets(this.currentWorkspaceId); - this.assets = response.assets || []; - } catch (err) { - this.error = err.message; - } - }, - - async uploadAsset(file) { - if (!this.currentWorkspaceId) return; - try { - const asset = await api.uploadAsset(this.currentWorkspaceId, file); - this.assets.unshift(asset); - return asset; - } catch (err) { - this.error = err.message; - throw err; - } - }, - - async deleteAsset(id) { - if (!this.currentWorkspaceId) return; - try { - await api.deleteAsset(this.currentWorkspaceId, id); - this.assets = this.assets.filter(a => a.id !== id); - } catch (err) { - this.error = err.message; - throw err; - } - }, - - // Analytics - async fetchAnalytics(period = '7d', startDate = null, endDate = null) { - if (!this.currentWorkspaceId) return; - try { - this.analytics = await api.getWorkspaceAnalytics(this.currentWorkspaceId, period, startDate, endDate); - } catch (err) { - this.error = err.message; - } - }, - - // Clear all data (for logout) clearAll() { this.workspaces = []; this.currentWorkspace = null; - this.projects = []; - this.links = []; - this.qrcodes = []; - this.domains = []; - this.assets = []; - this.analytics = null; this.initialized = false; localStorage.removeItem('currentWorkspaceId'); }, diff --git a/src/frontend/src/views/analytics/Analytics.vue b/src/frontend/src/views/analytics/Analytics.vue index 8a3750f..c6c0c47 100644 --- a/src/frontend/src/views/analytics/Analytics.vue +++ b/src/frontend/src/views/analytics/Analytics.vue @@ -200,9 +200,11 @@ diff --git a/src/frontend/src/views/auth/ForgotPassword.vue b/src/frontend/src/views/auth/ForgotPassword.vue index 7ff341c..874854f 100644 --- a/src/frontend/src/views/auth/ForgotPassword.vue +++ b/src/frontend/src/views/auth/ForgotPassword.vue @@ -53,7 +53,7 @@ diff --git a/src/frontend/src/views/domains/Domains.vue b/src/frontend/src/views/domains/Domains.vue index 7e2b4d6..b0954ad 100644 --- a/src/frontend/src/views/domains/Domains.vue +++ b/src/frontend/src/views/domains/Domains.vue @@ -206,12 +206,14 @@ diff --git a/src/frontend/src/views/projects/Projects.vue b/src/frontend/src/views/projects/Projects.vue index 0086482..ccc03e0 100644 --- a/src/frontend/src/views/projects/Projects.vue +++ b/src/frontend/src/views/projects/Projects.vue @@ -142,12 +142,14 @@