feat(api): refactored to api client to be per feature
This commit is contained in:
10
src/frontend/.idea/.gitignore
generated
vendored
Normal file
10
src/frontend/.idea/.gitignore
generated
vendored
Normal file
@@ -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/
|
||||
4
src/frontend/.idea/encodings.xml
generated
Normal file
4
src/frontend/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
src/frontend/.idea/frontend.iml
generated
Normal file
8
src/frontend/.idea/frontend.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
src/frontend/.idea/modules.xml
generated
Normal file
8
src/frontend/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/frontend.iml" filepath="$PROJECT_DIR$/.idea/frontend.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
src/frontend/.idea/vcs.xml
generated
Normal file
6
src/frontend/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
14
src/frontend/src/api/analytics.js
Normal file
14
src/frontend/src/api/analytics.js
Normal file
@@ -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()}`);
|
||||
},
|
||||
};
|
||||
15
src/frontend/src/api/apiKeys.js
Normal file
15
src/frontend/src/api/apiKeys.js
Normal file
@@ -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}`);
|
||||
},
|
||||
};
|
||||
15
src/frontend/src/api/assets.js
Normal file
15
src/frontend/src/api/assets.js
Normal file
@@ -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}`);
|
||||
},
|
||||
};
|
||||
47
src/frontend/src/api/auth.js
Normal file
47
src/frontend/src/api/auth.js
Normal file
@@ -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);
|
||||
},
|
||||
};
|
||||
91
src/frontend/src/api/base.js
Normal file
91
src/frontend/src/api/base.js
Normal file
@@ -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();
|
||||
20
src/frontend/src/api/billing.js
Normal file
20
src/frontend/src/api/billing.js
Normal file
@@ -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`);
|
||||
},
|
||||
};
|
||||
@@ -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();
|
||||
19
src/frontend/src/api/domains.js
Normal file
19
src/frontend/src/api/domains.js
Normal file
@@ -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`);
|
||||
},
|
||||
};
|
||||
12
src/frontend/src/api/index.js
Normal file
12
src/frontend/src/api/index.js
Normal file
@@ -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';
|
||||
44
src/frontend/src/api/links.js
Normal file
44
src/frontend/src/api/links.js
Normal file
@@ -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()}`);
|
||||
},
|
||||
};
|
||||
23
src/frontend/src/api/projects.js
Normal file
23
src/frontend/src/api/projects.js
Normal file
@@ -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}`);
|
||||
},
|
||||
};
|
||||
35
src/frontend/src/api/qrcodes.js
Normal file
35
src/frontend/src/api/qrcodes.js
Normal file
@@ -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}`);
|
||||
},
|
||||
};
|
||||
8
src/frontend/src/api/usage.js
Normal file
8
src/frontend/src/api/usage.js
Normal file
@@ -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);
|
||||
},
|
||||
};
|
||||
23
src/frontend/src/api/workspaces.js
Normal file
23
src/frontend/src/api/workspaces.js
Normal file
@@ -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}`);
|
||||
},
|
||||
};
|
||||
36
src/frontend/src/stores/analytics.js
Normal file
36
src/frontend/src/stores/analytics.js
Normal file
@@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
59
src/frontend/src/stores/assets.js
Normal file
59
src/frontend/src/stores/assets.js
Normal file
@@ -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 = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
73
src/frontend/src/stores/domains.js
Normal file
73
src/frontend/src/stores/domains.js
Normal file
@@ -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 = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
8
src/frontend/src/stores/index.js
Normal file
8
src/frontend/src/stores/index.js
Normal file
@@ -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';
|
||||
119
src/frontend/src/stores/links.js
Normal file
119
src/frontend/src/stores/links.js
Normal file
@@ -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 = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
76
src/frontend/src/stores/projects.js
Normal file
76
src/frontend/src/stores/projects.js
Normal file
@@ -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 = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
107
src/frontend/src/stores/qrcodes.js
Normal file
107
src/frontend/src/stores/qrcodes.js
Normal file
@@ -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 = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
@@ -200,9 +200,11 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useAnalyticsStore } from '../../stores/analytics.js';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const analyticsStore = useAnalyticsStore();
|
||||
|
||||
const periods = [
|
||||
{ label: '24h', value: '24h' },
|
||||
@@ -214,12 +216,12 @@ const period = ref('7d');
|
||||
const isCustomRange = ref(false);
|
||||
const startDate = ref('');
|
||||
const endDate = ref('');
|
||||
const analytics = computed(() => workspaceStore.analytics);
|
||||
const analytics = computed(() => analyticsStore.analytics);
|
||||
|
||||
const setPeriod = async (p) => {
|
||||
isCustomRange.value = false;
|
||||
period.value = p;
|
||||
await workspaceStore.fetchAnalytics(p);
|
||||
await analyticsStore.fetchAnalytics(p);
|
||||
};
|
||||
|
||||
const toggleCustomRange = () => {
|
||||
@@ -235,7 +237,7 @@ const toggleCustomRange = () => {
|
||||
|
||||
const applyCustomRange = async () => {
|
||||
if (startDate.value && endDate.value) {
|
||||
await workspaceStore.fetchAnalytics(null, startDate.value, endDate.value);
|
||||
await analyticsStore.fetchAnalytics(null, startDate.value, endDate.value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -267,12 +269,12 @@ const getReferrerPercentage = (value) => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await workspaceStore.fetchAnalytics(period.value);
|
||||
await analyticsStore.fetchAnalytics(period.value);
|
||||
});
|
||||
|
||||
watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
if (workspaceStore.currentWorkspaceId) {
|
||||
await workspaceStore.fetchAnalytics(period.value);
|
||||
await analyticsStore.fetchAnalytics(period.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { api } from '../../api/client';
|
||||
import { authApi } from '../../api/auth.js';
|
||||
|
||||
const email = ref('');
|
||||
const loading = ref(false);
|
||||
@@ -65,7 +65,7 @@ const handleSubmit = async () => {
|
||||
error.value = '';
|
||||
|
||||
try {
|
||||
await api.forgotPassword(email.value);
|
||||
await authApi.forgotPassword(email.value);
|
||||
submitted.value = true;
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { api } from '../../api/client';
|
||||
import { authApi } from '../../api/auth.js';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -92,7 +92,7 @@ const handleSubmit = async () => {
|
||||
error.value = '';
|
||||
|
||||
try {
|
||||
await api.resetPassword(token.value, password.value);
|
||||
await authApi.resetPassword(token.value, password.value);
|
||||
success.value = true;
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { api } from '../../api/client';
|
||||
import { authApi } from '../../api/auth.js';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -100,7 +100,7 @@ onMounted(async () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
await api.verifyEmail(token.value);
|
||||
await authApi.verifyEmail(token.value);
|
||||
success.value = true;
|
||||
} catch (err) {
|
||||
error.value = err.message || 'Unable to verify your email. The link may be invalid or expired.';
|
||||
|
||||
@@ -205,8 +205,9 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { billingApi } from '../../api/billing.js';
|
||||
import { usageApi } from '../../api/usage.js';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
|
||||
const route = useRoute();
|
||||
@@ -239,8 +240,8 @@ async function loadData() {
|
||||
const workspaceId = workspaceStore.currentWorkspace?.id;
|
||||
|
||||
const [usageData, subData] = await Promise.all([
|
||||
api.getUsage(workspaceId),
|
||||
workspaceId ? api.getSubscription(workspaceId) : Promise.resolve(null),
|
||||
usageApi.get(workspaceId),
|
||||
workspaceId ? billingApi.getSubscription(workspaceId) : Promise.resolve(null),
|
||||
]);
|
||||
|
||||
usage.value = usageData;
|
||||
@@ -265,7 +266,7 @@ async function upgrade(plan) {
|
||||
const successUrl = window.location.origin + '/billing';
|
||||
const cancelUrl = window.location.origin + '/billing';
|
||||
|
||||
const { url } = await api.createCheckoutSession(
|
||||
const { url } = await billingApi.createCheckoutSession(
|
||||
workspaceId,
|
||||
plan,
|
||||
successUrl,
|
||||
@@ -285,7 +286,7 @@ async function openPortal() {
|
||||
|
||||
try {
|
||||
const returnUrl = window.location.origin + '/billing';
|
||||
const { url } = await api.createPortalSession(returnUrl);
|
||||
const { url } = await billingApi.createPortalSession(returnUrl);
|
||||
window.location.href = url;
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<p class="stat-value">{{ workspaceStore.links.length }}</p>
|
||||
<p class="stat-value">{{ linksStore.links.length }}</p>
|
||||
<p class="stat-label">Active Links</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,9 +173,13 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useLinksStore } from '../../stores/links.js';
|
||||
import { useAnalyticsStore } from '../../stores/analytics.js';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const linksStore = useLinksStore();
|
||||
const analyticsStore = useAnalyticsStore();
|
||||
|
||||
const periods = [
|
||||
{ label: '24h', value: '24h' },
|
||||
@@ -184,11 +188,11 @@ const periods = [
|
||||
];
|
||||
|
||||
const period = ref('7d');
|
||||
const analytics = computed(() => workspaceStore.analytics);
|
||||
const analytics = computed(() => analyticsStore.analytics);
|
||||
|
||||
const setPeriod = async (p) => {
|
||||
period.value = p;
|
||||
await workspaceStore.fetchAnalytics(p);
|
||||
await analyticsStore.fetchAnalytics(p);
|
||||
};
|
||||
|
||||
const maxEvents = computed(() => {
|
||||
@@ -256,14 +260,14 @@ const getCountryName = (countryCode) => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await workspaceStore.fetchLinks();
|
||||
await workspaceStore.fetchAnalytics(period.value);
|
||||
await linksStore.fetchLinks();
|
||||
await analyticsStore.fetchAnalytics(period.value);
|
||||
});
|
||||
|
||||
watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
if (workspaceStore.currentWorkspaceId) {
|
||||
await workspaceStore.fetchLinks();
|
||||
await workspaceStore.fetchAnalytics(period.value);
|
||||
await linksStore.fetchLinks();
|
||||
await analyticsStore.fetchAnalytics(period.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -206,12 +206,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useDomainsStore } from '../../stores/domains.js';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const domainsStore = useDomainsStore();
|
||||
|
||||
const domains = computed(() => workspaceStore.domains);
|
||||
const domains = computed(() => domainsStore.domains);
|
||||
const loading = ref(true);
|
||||
const adding = ref(false);
|
||||
const verifying = ref(false);
|
||||
@@ -242,7 +244,7 @@ watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
async function loadDomains() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await workspaceStore.fetchDomains();
|
||||
await domainsStore.fetchDomains();
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
} finally {
|
||||
@@ -254,7 +256,7 @@ async function addDomain() {
|
||||
adding.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const domain = await workspaceStore.addDomain(newDomain.value);
|
||||
const domain = await domainsStore.addDomain(newDomain.value);
|
||||
showAddModal.value = false;
|
||||
newDomain.value = '';
|
||||
// Show verification instructions for the new domain
|
||||
@@ -277,7 +279,7 @@ async function verifyDomain() {
|
||||
verifying.value = true;
|
||||
verifyError.value = '';
|
||||
try {
|
||||
await workspaceStore.verifyDomain(verifyingDomain.value.id);
|
||||
await domainsStore.verifyDomain(verifyingDomain.value.id);
|
||||
showVerifyModal.value = false;
|
||||
verifyingDomain.value = null;
|
||||
} catch (err) {
|
||||
@@ -295,7 +297,7 @@ function confirmDelete(domain) {
|
||||
async function deleteDomain() {
|
||||
deleting.value = true;
|
||||
try {
|
||||
await workspaceStore.deleteDomain(domainToDelete.value.id);
|
||||
await domainsStore.deleteDomain(domainToDelete.value.id);
|
||||
showDeleteModal.value = false;
|
||||
domainToDelete.value = null;
|
||||
} catch (err) {
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { linksApi } from '../../api/links.js';
|
||||
|
||||
const route = useRoute();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
@@ -170,7 +170,7 @@ const fetchData = async () => {
|
||||
if (!workspaceId) return;
|
||||
|
||||
try {
|
||||
link.value = await api.getLink(workspaceId, linkId);
|
||||
link.value = await linksApi.get(workspaceId, linkId);
|
||||
await fetchAnalytics();
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch link:', err);
|
||||
@@ -184,7 +184,7 @@ const fetchAnalytics = async () => {
|
||||
if (!workspaceId) return;
|
||||
|
||||
try {
|
||||
analytics.value = await api.getLinkAnalytics(workspaceId, linkId, period.value);
|
||||
analytics.value = await linksApi.getAnalytics(workspaceId, linkId, period.value);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch analytics:', err);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="links-list" v-if="workspaceStore.links.length">
|
||||
<div class="links-list" v-if="linksStore.links.length">
|
||||
<div
|
||||
v-for="link in workspaceStore.links"
|
||||
v-for="link in linksStore.links"
|
||||
:key="link.id"
|
||||
class="link-card"
|
||||
>
|
||||
@@ -311,10 +311,12 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useLinksStore } from '../../stores/links.js';
|
||||
import { linksApi } from '../../api/links.js';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const linksStore = useLinksStore();
|
||||
|
||||
const showCreateModal = ref(false);
|
||||
const showDeleteModal = ref(false);
|
||||
@@ -431,12 +433,12 @@ const saveLink = async () => {
|
||||
};
|
||||
|
||||
if (editingLink.value) {
|
||||
await workspaceStore.updateLink(editingLink.value.id, data);
|
||||
await linksStore.updateLink(editingLink.value.id, data);
|
||||
} else {
|
||||
if (formData.value.slug) {
|
||||
data.slug = formData.value.slug;
|
||||
}
|
||||
await workspaceStore.createLink(data);
|
||||
await linksStore.createLink(data);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
@@ -454,7 +456,7 @@ const confirmDelete = (link) => {
|
||||
|
||||
const deleteLink = async () => {
|
||||
if (deletingLink.value) {
|
||||
await workspaceStore.deleteLink(deletingLink.value.id);
|
||||
await linksStore.deleteLink(deletingLink.value.id);
|
||||
showDeleteModal.value = false;
|
||||
deletingLink.value = null;
|
||||
}
|
||||
@@ -517,12 +519,8 @@ const importBulkLinks = async () => {
|
||||
bulkError.value = '';
|
||||
|
||||
try {
|
||||
const workspaceId = workspaceStore.currentWorkspaceId;
|
||||
const result = await api.bulkCreateLinks(workspaceId, parsedBulkLinks.value);
|
||||
const result = await linksStore.bulkCreateLinks(parsedBulkLinks.value);
|
||||
bulkResults.value = result;
|
||||
|
||||
// Refresh links list
|
||||
await workspaceStore.fetchLinks();
|
||||
} catch (err) {
|
||||
bulkError.value = err.message;
|
||||
} finally {
|
||||
@@ -532,30 +530,30 @@ const importBulkLinks = async () => {
|
||||
|
||||
const toggleDeleted = async () => {
|
||||
showDeleted.value = true;
|
||||
await workspaceStore.fetchLinks({ includeDeleted: true });
|
||||
await linksStore.fetchLinks({ includeDeleted: true });
|
||||
};
|
||||
|
||||
const restoreLink = async (link) => {
|
||||
try {
|
||||
await api.restoreLink(workspaceStore.currentWorkspaceId, link.id);
|
||||
await workspaceStore.fetchLinks({ includeDeleted: showDeleted.value });
|
||||
await linksStore.restoreLink(link.id);
|
||||
await linksStore.fetchLinks({ includeDeleted: showDeleted.value });
|
||||
} catch (err) {
|
||||
console.error('Failed to restore link:', err);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await workspaceStore.fetchLinks();
|
||||
await linksStore.fetchLinks();
|
||||
});
|
||||
|
||||
watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
if (workspaceStore.currentWorkspaceId) {
|
||||
await workspaceStore.fetchLinks({ includeDeleted: showDeleted.value });
|
||||
await linksStore.fetchLinks({ includeDeleted: showDeleted.value });
|
||||
}
|
||||
});
|
||||
|
||||
watch(showDeleted, async (value) => {
|
||||
await workspaceStore.fetchLinks({ includeDeleted: value });
|
||||
await linksStore.fetchLinks({ includeDeleted: value });
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -142,12 +142,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useProjectsStore } from '../../stores/projects.js';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const projects = computed(() => workspaceStore.projects);
|
||||
const projects = computed(() => projectsStore.projects);
|
||||
const loading = ref(true);
|
||||
const saving = ref(false);
|
||||
const deleting = ref(false);
|
||||
@@ -174,7 +176,7 @@ watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
async function loadProjects() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await workspaceStore.fetchProjects();
|
||||
await projectsStore.fetchProjects();
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
} finally {
|
||||
@@ -186,7 +188,7 @@ async function createProject() {
|
||||
saving.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await workspaceStore.createProject(form.value.name, form.value.description);
|
||||
await projectsStore.createProject(form.value.name, form.value.description);
|
||||
closeModals();
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
@@ -205,7 +207,7 @@ async function updateProject() {
|
||||
saving.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await workspaceStore.updateProject(editingProject.value.id, {
|
||||
await projectsStore.updateProject(editingProject.value.id, {
|
||||
name: form.value.name,
|
||||
description: form.value.description
|
||||
});
|
||||
@@ -225,7 +227,7 @@ function confirmDelete(project) {
|
||||
async function deleteProject() {
|
||||
deleting.value = true;
|
||||
try {
|
||||
await workspaceStore.deleteProject(projectToDelete.value.id);
|
||||
await projectsStore.deleteProject(projectToDelete.value.id);
|
||||
showDeleteModal.value = false;
|
||||
projectToDelete.value = null;
|
||||
} catch (err) {
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<label for="linkId">Link *</label>
|
||||
<select id="linkId" v-model="formData.linkId" required>
|
||||
<option value="">Select a link</option>
|
||||
<option v-for="link in workspaceStore.links" :key="link.id" :value="link.id">
|
||||
<option v-for="link in linksStore.links" :key="link.id" :value="link.id">
|
||||
{{ link.title || link.slug }} ({{ link.slug }})
|
||||
</option>
|
||||
</select>
|
||||
@@ -254,12 +254,18 @@
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useLinksStore } from '../../stores/links.js';
|
||||
import { useAssetsStore } from '../../stores/assets.js';
|
||||
import { useQRCodesStore } from '../../stores/qrcodes.js';
|
||||
import { qrcodesApi } from '../../api/qrcodes.js';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const linksStore = useLinksStore();
|
||||
const assetsStore = useAssetsStore();
|
||||
const qrcodesStore = useQRCodesStore();
|
||||
|
||||
const isEditing = computed(() => !!route.params.id);
|
||||
const saving = ref(false);
|
||||
@@ -267,7 +273,7 @@ const error = ref('');
|
||||
const previewUrl = ref('');
|
||||
const previewTimeout = ref(null);
|
||||
const uploadingLogo = ref(false);
|
||||
const assets = computed(() => workspaceStore.assets.filter(a => a.type === 'Logo'));
|
||||
const assets = computed(() => assetsStore.assets.filter(a => a.type === 'Logo'));
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
@@ -296,7 +302,7 @@ const eyeShapes = [
|
||||
];
|
||||
|
||||
const getLogoUrl = (assetId) => {
|
||||
const asset = workspaceStore.assets.find(a => a.id === assetId);
|
||||
const asset = assetsStore.assets.find(a => a.id === assetId);
|
||||
return asset?.url || '';
|
||||
};
|
||||
|
||||
@@ -306,7 +312,7 @@ const handleLogoUpload = async (event) => {
|
||||
|
||||
uploadingLogo.value = true;
|
||||
try {
|
||||
const asset = await workspaceStore.uploadAsset(file);
|
||||
const asset = await assetsStore.uploadAsset(file);
|
||||
formData.value.logoAssetId = asset.id;
|
||||
// If adding a logo, suggest high error correction
|
||||
if (formData.value.style.errorCorrectionLevel !== 'H') {
|
||||
@@ -373,7 +379,7 @@ const fetchPreview = async () => {
|
||||
// For now, we'll just show a placeholder until saved
|
||||
if (isEditing.value) {
|
||||
try {
|
||||
const preview = await api.getQRCodePreview(workspaceStore.currentWorkspaceId, route.params.id);
|
||||
const preview = await qrcodesStore.getQRCodePreview(route.params.id);
|
||||
previewUrl.value = preview.dataUrl;
|
||||
} catch (err) {
|
||||
console.error('Preview error:', err);
|
||||
@@ -400,9 +406,9 @@ const save = async () => {
|
||||
if (!formData.value.logoAssetId) {
|
||||
data.removeLogo = true;
|
||||
}
|
||||
await workspaceStore.updateQRCode(route.params.id, data);
|
||||
await qrcodesStore.updateQRCode(route.params.id, data);
|
||||
} else {
|
||||
await workspaceStore.createQRCode(data);
|
||||
await qrcodesStore.createQRCode(data);
|
||||
}
|
||||
|
||||
router.push('/qrcodes');
|
||||
@@ -416,7 +422,7 @@ const save = async () => {
|
||||
const downloadQR = async (format) => {
|
||||
if (!isEditing.value) return;
|
||||
|
||||
const url = api.getQRCodeExportUrl(workspaceStore.currentWorkspaceId, route.params.id, format, 512);
|
||||
const url = qrcodesStore.getQRCodeExportUrl(route.params.id, format, 512);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${formData.value.name}.${format}`;
|
||||
@@ -427,7 +433,7 @@ const loadExisting = async () => {
|
||||
if (!isEditing.value) return;
|
||||
|
||||
try {
|
||||
const qr = await api.getQRCode(workspaceStore.currentWorkspaceId, route.params.id);
|
||||
const qr = await qrcodesApi.get(workspaceStore.currentWorkspaceId, route.params.id);
|
||||
const defaultStyle = {
|
||||
foregroundColor: '#000000',
|
||||
backgroundColor: '#ffffff',
|
||||
@@ -460,8 +466,8 @@ watch(() => formData.value.logoAssetId, () => {
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
workspaceStore.fetchLinks(),
|
||||
workspaceStore.fetchAssets(),
|
||||
linksStore.fetchLinks(),
|
||||
assetsStore.fetchAssets(),
|
||||
]);
|
||||
if (isEditing.value) {
|
||||
await loadExisting();
|
||||
|
||||
@@ -168,8 +168,8 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { qrcodesApi } from '../../api/qrcodes.js';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
|
||||
const route = useRoute();
|
||||
@@ -208,8 +208,8 @@ async function loadData() {
|
||||
if (!workspaceId) return;
|
||||
|
||||
const [qrData, analyticsData] = await Promise.all([
|
||||
api.getQRCode(workspaceId, id.value),
|
||||
api.getQRCodeAnalytics(workspaceId, id.value, period.value)
|
||||
qrcodesApi.get(workspaceId, id.value),
|
||||
qrcodesApi.getAnalytics(workspaceId, id.value, period.value)
|
||||
]);
|
||||
|
||||
qrCode.value = qrData;
|
||||
@@ -226,7 +226,7 @@ async function setPeriod(newPeriod) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const workspaceId = workspaceStore.currentWorkspace?.id;
|
||||
analytics.value = await api.getQRCodeAnalytics(workspaceId, id.value, newPeriod);
|
||||
analytics.value = await qrcodesApi.getAnalytics(workspaceId, id.value, newPeriod);
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
} finally {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
</router-link>
|
||||
</header>
|
||||
|
||||
<div class="qrcodes-grid" v-if="workspaceStore.qrcodes.length">
|
||||
<div class="qrcodes-grid" v-if="qrcodesStore.qrcodes.length">
|
||||
<div
|
||||
v-for="qr in workspaceStore.qrcodes"
|
||||
v-for="qr in qrcodesStore.qrcodes"
|
||||
:key="qr.id"
|
||||
class="qr-card"
|
||||
>
|
||||
@@ -98,19 +98,23 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { api } from '../../api/client';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import { useQRCodesStore } from '../../stores/qrcodes.js';
|
||||
import { useLinksStore } from '../../stores/links.js';
|
||||
import { qrcodesApi } from '../../api/qrcodes.js';
|
||||
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const qrcodesStore = useQRCodesStore();
|
||||
const linksStore = useLinksStore();
|
||||
|
||||
const previews = ref({});
|
||||
const showDeleteModal = ref(false);
|
||||
const deletingQR = ref(null);
|
||||
|
||||
const fetchPreviews = async () => {
|
||||
for (const qr of workspaceStore.qrcodes) {
|
||||
for (const qr of qrcodesStore.qrcodes) {
|
||||
try {
|
||||
const preview = await api.getQRCodePreview(workspaceStore.currentWorkspaceId, qr.id);
|
||||
const preview = await qrcodesStore.getQRCodePreview(qr.id);
|
||||
previews.value[qr.id] = preview.dataUrl;
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch preview:', err);
|
||||
@@ -119,12 +123,12 @@ const fetchPreviews = async () => {
|
||||
};
|
||||
|
||||
const getLinkSlug = (linkId) => {
|
||||
const link = workspaceStore.links.find(l => l.id === linkId);
|
||||
const link = linksStore.links.find(l => l.id === linkId);
|
||||
return link ? `/${link.slug}` : 'No link';
|
||||
};
|
||||
|
||||
const downloadQR = async (qr, format) => {
|
||||
const url = api.getQRCodeExportUrl(workspaceStore.currentWorkspaceId, qr.id, format, 512);
|
||||
const url = qrcodesStore.getQRCodeExportUrl(qr.id, format, 512);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${qr.name}.${format}`;
|
||||
@@ -138,7 +142,7 @@ const confirmDelete = (qr) => {
|
||||
|
||||
const deleteQR = async () => {
|
||||
if (deletingQR.value) {
|
||||
await workspaceStore.deleteQRCode(deletingQR.value.id);
|
||||
await qrcodesStore.deleteQRCode(deletingQR.value.id);
|
||||
delete previews.value[deletingQR.value.id];
|
||||
showDeleteModal.value = false;
|
||||
deletingQR.value = null;
|
||||
@@ -146,16 +150,16 @@ const deleteQR = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await workspaceStore.fetchQRCodes();
|
||||
await workspaceStore.fetchLinks();
|
||||
await qrcodesStore.fetchQRCodes();
|
||||
await linksStore.fetchLinks();
|
||||
await fetchPreviews();
|
||||
});
|
||||
|
||||
watch(() => workspaceStore.currentWorkspaceId, async () => {
|
||||
if (workspaceStore.currentWorkspaceId) {
|
||||
previews.value = {};
|
||||
await workspaceStore.fetchQRCodes();
|
||||
await workspaceStore.fetchLinks();
|
||||
await qrcodesStore.fetchQRCodes();
|
||||
await linksStore.fetchLinks();
|
||||
await fetchPreviews();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -277,9 +277,10 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { api } from '../../api/client';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { useWorkspaceStore } from '../../stores/workspace';
|
||||
import { authApi } from '../../api/auth.js';
|
||||
import { apiKeysApi } from '../../api/apiKeys.js';
|
||||
import { useAuthStore } from '../../stores/auth.js';
|
||||
import { useWorkspaceStore } from '../../stores/workspace.js';
|
||||
import AppLayout from '../../components/layout/AppLayout.vue';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -323,7 +324,7 @@ const deleteKeyError = ref('');
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await api.getProfile();
|
||||
const data = await authApi.getProfile();
|
||||
profile.value = data;
|
||||
await loadApiKeys();
|
||||
} catch (err) {
|
||||
@@ -345,7 +346,7 @@ async function loadApiKeys() {
|
||||
|
||||
loadingKeys.value = true;
|
||||
try {
|
||||
const result = await api.listApiKeys(workspaceId);
|
||||
const result = await apiKeysApi.list(workspaceId);
|
||||
apiKeys.value = result.apiKeys;
|
||||
} catch (err) {
|
||||
console.error('Failed to load API keys:', err);
|
||||
@@ -360,7 +361,7 @@ async function updateProfile() {
|
||||
profileSuccess.value = '';
|
||||
|
||||
try {
|
||||
const data = await api.updateProfile({ email: profile.value.email });
|
||||
const data = await authApi.updateProfile({ email: profile.value.email });
|
||||
profile.value = data;
|
||||
profileSuccess.value = 'Profile updated successfully';
|
||||
} catch (err) {
|
||||
@@ -381,7 +382,7 @@ async function changePassword() {
|
||||
passwordSuccess.value = '';
|
||||
|
||||
try {
|
||||
await api.changePassword(
|
||||
await authApi.changePassword(
|
||||
passwordForm.value.currentPassword,
|
||||
passwordForm.value.newPassword
|
||||
);
|
||||
@@ -400,7 +401,7 @@ async function resendVerification() {
|
||||
resendingVerification.value = true;
|
||||
profileError.value = '';
|
||||
try {
|
||||
await api.resendVerification();
|
||||
await authApi.resendVerification();
|
||||
profileSuccess.value = 'Verification email sent! Check your inbox.';
|
||||
} catch (err) {
|
||||
profileError.value = err.message;
|
||||
@@ -414,7 +415,7 @@ async function deleteAccount() {
|
||||
deleteError.value = '';
|
||||
|
||||
try {
|
||||
await api.deleteAccount(deletePassword.value);
|
||||
await authApi.deleteAccount(deletePassword.value);
|
||||
authStore.logout();
|
||||
router.push('/');
|
||||
} catch (err) {
|
||||
@@ -440,7 +441,7 @@ async function createApiKey() {
|
||||
expiresAt = date.toISOString();
|
||||
}
|
||||
|
||||
const result = await api.createApiKey(workspaceId, newKeyName.value, expiresAt);
|
||||
const result = await apiKeysApi.create(workspaceId, newKeyName.value, expiresAt);
|
||||
newlyCreatedKey.value = result.key;
|
||||
apiKeys.value.unshift({
|
||||
id: result.id,
|
||||
@@ -487,7 +488,7 @@ async function deleteApiKey() {
|
||||
deleteKeyError.value = '';
|
||||
|
||||
try {
|
||||
await api.deleteApiKey(workspaceId, keyToDelete.value.id);
|
||||
await apiKeysApi.delete(workspaceId, keyToDelete.value.id);
|
||||
apiKeys.value = apiKeys.value.filter(k => k.id !== keyToDelete.value.id);
|
||||
keyToDelete.value = null;
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user