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 @@