mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
fix(frontend): resolve production API URL fallback to localhost
Problem: The unified Docker image builds the frontend at build time without VITE_API_URL. Vite inlined import.meta.env.VITE_API_URL as undefined, so every API call fell back to the hardcoded 'http://localhost:8080'. This broke Casa deployments where the frontend loaded from the public domain but tried to reach the backend at localhost. Solution: 1. Centralize API URL resolution in lib/api-url.ts via getApiOrigin(). It checks runtime window.ENV first (injected by docker-entrypoint.sh at container startup), then build-time import.meta.env, then dev fallback. In production unified deployments it returns '' so API calls use same-origin relative URLs (/api/v1/...) that nginx proxies to the backend. 2. Replace all 50+ inline import.meta.env.VITE_API_URL || 'localhost' usages across 14 source files with getApiOrigin() / getApiV1BaseUrl(). 3. Add build args and runtime sed substitution to Dockerfile and docker-entrypoint.sh so the same image works for any deployment. 4. Pass VITE_API_URL through docker-compose.yml and CI/CD build-args. Verified: - Production bundle contains 0 occurrences of localhost:8080. - Container health check and /api/v1/auth/check-users both return 200. - Runtime injection correctly sets VITE_API_URL='' for same-origin and VITE_API_URL='https://domain' for external backend. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -145,6 +145,11 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
# Optional repository variables (Settings > Secrets and variables > Actions > Variables).
|
||||||
|
# VITE_API_URL defaults to empty for same-origin relative URLs in unified deployments.
|
||||||
|
build-args: |
|
||||||
|
VITE_API_URL=${{ vars.VITE_API_URL || '' }}
|
||||||
|
VITE_DEMO_MODE=${{ vars.VITE_DEMO_MODE || 'false' }}
|
||||||
|
|
||||||
# deploy:
|
# deploy:
|
||||||
# name: Deploy to Production
|
# name: Deploy to Production
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
# Stage 1: Build Frontend
|
# Stage 1: Build Frontend
|
||||||
FROM node:22-alpine AS frontend-builder
|
FROM node:22-alpine AS frontend-builder
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
|
|
||||||
|
# Accept build arguments for Vite environment variables.
|
||||||
|
# If unset, the frontend falls back to same-origin relative URLs in production.
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ARG VITE_DEMO_MODE=false
|
||||||
|
ENV VITE_API_URL=${VITE_API_URL}
|
||||||
|
ENV VITE_DEMO_MODE=${VITE_DEMO_MODE}
|
||||||
|
|
||||||
COPY frontend/package*.json ./
|
COPY frontend/package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ services:
|
|||||||
JWT_SECRET: ${JWT_SECRET:-}
|
JWT_SECRET: ${JWT_SECRET:-}
|
||||||
GIN_MODE: release
|
GIN_MODE: release
|
||||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-*}
|
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-*}
|
||||||
|
# VITE_API_URL defaults to empty for same-origin relative URLs.
|
||||||
|
# Set explicitly only when frontend and backend are on different origins.
|
||||||
|
VITE_API_URL: ${VITE_API_URL:-}
|
||||||
|
VITE_DEMO_MODE: ${VITE_DEMO_MODE:-false}
|
||||||
volumes:
|
volumes:
|
||||||
- trackeep_postgres:/var/lib/postgresql/data
|
- trackeep_postgres:/var/lib/postgresql/data
|
||||||
- trackeep_uploads:/app/uploads
|
- trackeep_uploads:/app/uploads
|
||||||
|
|||||||
@@ -97,6 +97,18 @@ for i in $(seq 1 30); do
|
|||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Runtime environment variable injection for frontend.
|
||||||
|
# The frontend is built with placeholders; at container startup we replace
|
||||||
|
# them so the same image works for any deployment target (Casa, local, etc.).
|
||||||
|
HTML_FILE="/usr/share/nginx/html/index.html"
|
||||||
|
if [ -f "$HTML_FILE" ]; then
|
||||||
|
VITE_API_URL=${VITE_API_URL:-}
|
||||||
|
VITE_DEMO_MODE=${VITE_DEMO_MODE:-false}
|
||||||
|
sed -i "s|VITE_API_URL_PLACEHOLDER|$VITE_API_URL|g" "$HTML_FILE"
|
||||||
|
sed -i "s|VITE_DEMO_MODE_PLACEHOLDER|$VITE_DEMO_MODE|g" "$HTML_FILE"
|
||||||
|
echo "Frontend env injected: VITE_API_URL='$VITE_API_URL', VITE_DEMO_MODE='$VITE_DEMO_MODE'"
|
||||||
|
fi
|
||||||
|
|
||||||
# Start nginx in foreground (keeps container alive)
|
# Start nginx in foreground (keeps container alive)
|
||||||
echo "Starting nginx on port 8080..."
|
echo "Starting nginx on port 8080..."
|
||||||
nginx -g "daemon off;"
|
nginx -g "daemon off;"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSignal, onMount, Show, For } from 'solid-js';
|
import { createSignal, onMount, Show, For } from 'solid-js';
|
||||||
import { Button } from './ui/Button';
|
import { Button } from './ui/Button';
|
||||||
|
import { getApiOrigin } from '@/lib/api-url';
|
||||||
|
|
||||||
interface TOTPSetupResponse {
|
interface TOTPSetupResponse {
|
||||||
secret: string;
|
secret: string;
|
||||||
@@ -42,7 +43,7 @@ export function TwoFactorAuth() {
|
|||||||
|
|
||||||
const fetchTOTPStatus = async () => {
|
const fetchTOTPStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/status`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/status`, {
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ export function TwoFactorAuth() {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/setup`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/setup`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -102,7 +103,7 @@ export function TwoFactorAuth() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/verify`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/verify`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -135,7 +136,7 @@ export function TwoFactorAuth() {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/enable`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/enable`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -171,7 +172,7 @@ export function TwoFactorAuth() {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/disable`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/disable`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -206,7 +207,7 @@ export function TwoFactorAuth() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/verify`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/verify`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -240,7 +241,7 @@ export function TwoFactorAuth() {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/regenerate`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/regenerate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSignal, For, Show, onMount } from 'solid-js';
|
import { createSignal, For, Show, onMount } from 'solid-js';
|
||||||
|
import { getApiOrigin } from '@/lib/api-url';
|
||||||
import { useSearchParams } from '@solidjs/router';
|
import { useSearchParams } from '@solidjs/router';
|
||||||
import {
|
import {
|
||||||
IconSearch,
|
IconSearch,
|
||||||
@@ -118,7 +119,7 @@ export const EnhancedSearch = () => {
|
|||||||
|
|
||||||
if (currentFilters.search_mode === 'semantic') {
|
if (currentFilters.search_mode === 'semantic') {
|
||||||
// Use semantic search API
|
// Use semantic search API
|
||||||
response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/semantic`, {
|
response = await fetch(`${getApiOrigin()}/api/v1/search/semantic`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -145,7 +146,7 @@ export const EnhancedSearch = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use enhanced full-text search API
|
// Use enhanced full-text search API
|
||||||
response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/enhanced`, {
|
response = await fetch(`${getApiOrigin()}/api/v1/search/enhanced`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSignal, For, Show, onMount } from 'solid-js';
|
import { createSignal, For, Show, onMount } from 'solid-js';
|
||||||
|
import { getApiOrigin } from '@/lib/api-url';
|
||||||
import {
|
import {
|
||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
@@ -61,7 +62,7 @@ export const SavedSearches = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ export const SavedSearches = () => {
|
|||||||
const loadTags = async () => {
|
const loadTags = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/tags`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/tags`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,7 @@ export const SavedSearches = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/${id}`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
@@ -160,7 +161,7 @@ export const SavedSearches = () => {
|
|||||||
const runSavedSearch = async (id: number) => {
|
const runSavedSearch = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/saved/${id}/run`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/search/saved/${id}/run`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
|
|||||||
Vendored
+4
@@ -49,6 +49,10 @@ interface ImportMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
ENV?: {
|
||||||
|
VITE_API_URL?: string;
|
||||||
|
VITE_DEMO_MODE?: string;
|
||||||
|
};
|
||||||
importMetaEnv?: {
|
importMetaEnv?: {
|
||||||
VITE_API_URL?: string;
|
VITE_API_URL?: string;
|
||||||
VITE_DEMO_MODE?: string;
|
VITE_DEMO_MODE?: string;
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Centralized API URL resolver.
|
||||||
|
*
|
||||||
|
* Problem: Vite bakes import.meta.env values at build time. When the unified
|
||||||
|
* Docker image is built without VITE_API_URL, every API call fell back to
|
||||||
|
* 'http://localhost:8080', which broke production deployments (e.g. Casa).
|
||||||
|
*
|
||||||
|
* Solution: This helper checks the runtime-injected window.ENV first
|
||||||
|
* (set by docker-entrypoint.sh via sed replacement in index.html), then
|
||||||
|
* build-time import.meta.env, then dev fallback. In production unified
|
||||||
|
* deployments (same origin) it returns '' so all API calls use relative
|
||||||
|
* URLs like '/api/v1/...' that nginx proxies to the backend.
|
||||||
|
*/
|
||||||
|
|
||||||
const DEFAULT_API_ORIGIN = 'http://localhost:8080';
|
const DEFAULT_API_ORIGIN = 'http://localhost:8080';
|
||||||
|
|
||||||
const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
|
const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
|
||||||
@@ -5,16 +19,30 @@ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
|
|||||||
const trimApiSuffix = (value: string): string => value.replace(/\/api\/v1$/, '');
|
const trimApiSuffix = (value: string): string => value.replace(/\/api\/v1$/, '');
|
||||||
|
|
||||||
export const getApiOrigin = (): string => {
|
export const getApiOrigin = (): string => {
|
||||||
const raw = (import.meta.env.VITE_API_URL as string | undefined)?.trim();
|
// 1. Runtime injection from index.html (highest priority for Docker deployments)
|
||||||
if (!raw) {
|
const runtimeUrl = ((window as any).ENV?.VITE_API_URL as string | undefined)?.trim();
|
||||||
|
if (runtimeUrl && runtimeUrl !== 'VITE_API_URL_PLACEHOLDER') {
|
||||||
|
const normalized = trimTrailingSlash(runtimeUrl);
|
||||||
|
return trimApiSuffix(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Build-time Vite env variable (for dev builds or pre-built images)
|
||||||
|
const buildUrl = (import.meta.env.VITE_API_URL as string | undefined)?.trim();
|
||||||
|
if (buildUrl) {
|
||||||
|
const normalized = trimTrailingSlash(buildUrl);
|
||||||
|
return trimApiSuffix(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Development fallback
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
return DEFAULT_API_ORIGIN;
|
return DEFAULT_API_ORIGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = trimTrailingSlash(raw);
|
// 4. Production unified deployment: same-origin relative URLs
|
||||||
return trimApiSuffix(normalized);
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApiV1BaseUrl = (): string => {
|
export const getApiV1BaseUrl = (): string => {
|
||||||
const origin = getApiOrigin();
|
const origin = getApiOrigin();
|
||||||
return `${origin}/api/v1`;
|
return origin ? `${origin}/api/v1` : '/api/v1';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ export const getSearchProvider = (): string => {
|
|||||||
import.meta.env.VITE_SERPER_API_KEY ? 'serper' : 'demo');
|
import.meta.env.VITE_SERPER_API_KEY ? 'serper' : 'demo');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get API base URL
|
// Delegates to getApiOrigin so all API URL resolution goes through the
|
||||||
|
// centralized helper that supports runtime env injection.
|
||||||
|
import { getApiOrigin } from './api-url';
|
||||||
|
|
||||||
export const getApiBaseUrl = (): string => {
|
export const getApiBaseUrl = (): string => {
|
||||||
return import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
return getApiOrigin();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getMockStats
|
getMockStats
|
||||||
} from './mockData';
|
} from './mockData';
|
||||||
import { isDemoMode } from './demo-mode';
|
import { isDemoMode } from './demo-mode';
|
||||||
|
import { getApiV1BaseUrl } from './api-url';
|
||||||
|
|
||||||
// Demo mode API client that falls back to mock data
|
// Demo mode API client that falls back to mock data
|
||||||
export class DemoModeApiClient {
|
export class DemoModeApiClient {
|
||||||
@@ -280,8 +281,8 @@ export class DemoModeApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create demo mode API client
|
// Uses getApiV1BaseUrl so demo client respects runtime env injection.
|
||||||
const demoApi = new DemoModeApiClient(import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1');
|
const demoApi = new DemoModeApiClient(getApiV1BaseUrl());
|
||||||
|
|
||||||
// Export demo mode API functions that match the regular API
|
// Export demo mode API functions that match the regular API
|
||||||
export const demoBookmarksApi = {
|
export const demoBookmarksApi = {
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ export interface WsEvent {
|
|||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
// Switched from raw import.meta.env to getApiOrigin for runtime env support.
|
||||||
|
import { getApiOrigin } from './api-url';
|
||||||
|
|
||||||
|
const API_BASE_URL = getApiOrigin();
|
||||||
|
|
||||||
function getToken() {
|
function getToken() {
|
||||||
return localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
return localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card';
|
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { useHaptics } from '@/lib/haptics';
|
import { useHaptics } from '@/lib/haptics';
|
||||||
|
import { getApiOrigin } from '@/lib/api-url';
|
||||||
|
|
||||||
interface AnalyticsData {
|
interface AnalyticsData {
|
||||||
period: {
|
period: {
|
||||||
@@ -183,7 +184,7 @@ export const Analytics = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/analytics/dashboard?days=${selectedPeriod()}`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/analytics/dashboard?days=${selectedPeriod()}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from 'lucide-solid'
|
} from 'lucide-solid'
|
||||||
import { AIProviderIcon } from '@/components/AIProviderIcon'
|
import { AIProviderIcon } from '@/components/AIProviderIcon'
|
||||||
import { useHaptics } from '@/lib/haptics'
|
import { useHaptics } from '@/lib/haptics'
|
||||||
|
import { getApiOrigin } from '@/lib/api-url'
|
||||||
|
|
||||||
interface AIModel {
|
interface AIModel {
|
||||||
id: string
|
id: string
|
||||||
@@ -133,7 +134,7 @@ export const AIChat = () => {
|
|||||||
|
|
||||||
const callAIAPI = async (message: string, modelId: string): Promise<string> => {
|
const callAIAPI = async (message: string, modelId: string): Promise<string> => {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080'
|
const apiUrl = getApiOrigin()
|
||||||
|
|
||||||
const response = await fetch(`${apiUrl}/api/v1/ai/chat`, {
|
const response = await fetch(`${apiUrl}/api/v1/ai/chat`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createEffect, createResource, createSignal, For, Show, onMount } from 'solid-js'
|
import { createEffect, createResource, createSignal, For, Show, onMount } from 'solid-js'
|
||||||
|
import { getApiOrigin } from '@/lib/api-url'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
import { Card } from '@/components/ui/Card'
|
import { Card } from '@/components/ui/Card'
|
||||||
@@ -64,7 +65,7 @@ const Chat = () => {
|
|||||||
const loadAIProviders = async () => {
|
const loadAIProviders = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -83,7 +84,7 @@ const Chat = () => {
|
|||||||
const loadAISettings = async () => {
|
const loadAISettings = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -175,7 +176,7 @@ const Chat = () => {
|
|||||||
const fetchSessions = async () => {
|
const fetchSessions = async () => {
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||||
headers: {
|
headers: {
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
},
|
},
|
||||||
@@ -239,7 +240,7 @@ const Chat = () => {
|
|||||||
const loadSessionMessages = async (sessionId: string) => {
|
const loadSessionMessages = async (sessionId: string) => {
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||||
headers: {
|
headers: {
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
},
|
},
|
||||||
@@ -340,7 +341,7 @@ const Chat = () => {
|
|||||||
payload.session_id = currentSessionId()
|
payload.session_id = currentSessionId()
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/send`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/chat/send`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -606,7 +607,7 @@ const Chat = () => {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${session.id}`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${session.id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSignal, For, Show, onCleanup, onMount } from 'solid-js';
|
import { createSignal, For, Show, onCleanup, onMount } from 'solid-js';
|
||||||
|
import { getApiOrigin } from '@/lib/api-url';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { toast } from '@/components/ui/Toast';
|
import { toast } from '@/components/ui/Toast';
|
||||||
@@ -973,7 +974,7 @@ export const Messages = () => {
|
|||||||
kind: 'voice_note',
|
kind: 'voice_note',
|
||||||
file_id: uploaded.id,
|
file_id: uploaded.id,
|
||||||
title: uploaded.original_name || 'Voice note',
|
title: uploaded.original_name || 'Voice note',
|
||||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const transcript = `${voiceFinalTranscript} ${voiceInterimTranscript}`.trim();
|
const transcript = `${voiceFinalTranscript} ${voiceInterimTranscript}`.trim();
|
||||||
@@ -1366,7 +1367,7 @@ export const Messages = () => {
|
|||||||
const loadMembers = async () => {
|
const loadMembers = async () => {
|
||||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/members?limit=200`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/members?limit=200`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
@@ -1385,7 +1386,7 @@ export const Messages = () => {
|
|||||||
const loadTeams = async () => {
|
const loadTeams = async () => {
|
||||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/teams?limit=200`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/teams?limit=200`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
@@ -1403,7 +1404,7 @@ export const Messages = () => {
|
|||||||
const loadAIProviders = async () => {
|
const loadAIProviders = async () => {
|
||||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
@@ -1424,7 +1425,7 @@ export const Messages = () => {
|
|||||||
const loadAISettings = async () => {
|
const loadAISettings = async () => {
|
||||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
@@ -1454,7 +1455,7 @@ export const Messages = () => {
|
|||||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||||
setAiShareLoadingSessions(true);
|
setAiShareLoadingSessions(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -1486,7 +1487,7 @@ export const Messages = () => {
|
|||||||
if (aiShareMessagesBySession()[sessionId]) return;
|
if (aiShareMessagesBySession()[sessionId]) return;
|
||||||
setAiShareLoadingMessages(true);
|
setAiShareLoadingMessages(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -1786,7 +1787,7 @@ export const Messages = () => {
|
|||||||
kind: uploaded.mime_type?.startsWith('image/') ? 'image' : 'file',
|
kind: uploaded.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||||
file_id: uploaded.id,
|
file_id: uploaded.id,
|
||||||
title: uploaded.original_name,
|
title: uploaded.original_name,
|
||||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||||
});
|
});
|
||||||
setUploadProgress({ done: i + 1, total: localFiles.length });
|
setUploadProgress({ done: i + 1, total: localFiles.length });
|
||||||
}
|
}
|
||||||
@@ -1796,7 +1797,7 @@ export const Messages = () => {
|
|||||||
kind: file.mime_type?.startsWith('image/') ? 'image' : 'file',
|
kind: file.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||||
file_id: file.id,
|
file_id: file.id,
|
||||||
title: file.original_name,
|
title: file.original_name,
|
||||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${file.id}/download`,
|
url: `${getApiOrigin()}/api/v1/files/${file.id}/download`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ export const Bookmarks = () => {
|
|||||||
|
|
||||||
const handleAddBookmark = async (bookmarkData: any) => {
|
const handleAddBookmark = async (bookmarkData: any) => {
|
||||||
try {
|
try {
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
const response = await fetch(`${API_BASE_URL}/bookmarks`, {
|
const response = await fetch(`${API_BASE_URL}/bookmarks`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -271,7 +270,6 @@ export const Bookmarks = () => {
|
|||||||
const deleteBookmark = async (bookmarkId: number) => {
|
const deleteBookmark = async (bookmarkId: number) => {
|
||||||
if (confirm('Are you sure you want to delete this bookmark?')) {
|
if (confirm('Are you sure you want to delete this bookmark?')) {
|
||||||
try {
|
try {
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
const response = await fetch(`${API_BASE_URL}/bookmarks/${bookmarkId}`, {
|
const response = await fetch(`${API_BASE_URL}/bookmarks/${bookmarkId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -322,7 +320,6 @@ export const Bookmarks = () => {
|
|||||||
if (!editingBookmark()) return;
|
if (!editingBookmark()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
const response = await fetch(`${API_BASE_URL}/bookmarks/${editingBookmark()!.id}`, {
|
const response = await fetch(`${API_BASE_URL}/bookmarks/${editingBookmark()!.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSignal, createEffect, onMount, For, Show } from 'solid-js'
|
import { createSignal, createEffect, onMount, For, Show } from 'solid-js'
|
||||||
|
import { getApiOrigin } from '@/lib/api-url'
|
||||||
import { DateRangePicker } from '@/components/ui/DateRangePicker';
|
import { DateRangePicker } from '@/components/ui/DateRangePicker';
|
||||||
import { ModalPortal } from '@/components/ui/ModalPortal';
|
import { ModalPortal } from '@/components/ui/ModalPortal';
|
||||||
import {
|
import {
|
||||||
@@ -149,9 +150,9 @@ export function Calendar() {
|
|||||||
|
|
||||||
// Fetch all calendar data in parallel
|
// Fetch all calendar data in parallel
|
||||||
const [upcomingRes, todayRes, deadlinesRes] = await Promise.all([
|
const [upcomingRes, todayRes, deadlinesRes] = await Promise.all([
|
||||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/upcoming`, { headers }),
|
fetch(`${getApiOrigin()}/api/v1/calendar/upcoming`, { headers }),
|
||||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/today`, { headers }),
|
fetch(`${getApiOrigin()}/api/v1/calendar/today`, { headers }),
|
||||||
fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/deadlines`, { headers })
|
fetch(`${getApiOrigin()}/api/v1/calendar/deadlines`, { headers })
|
||||||
])
|
])
|
||||||
|
|
||||||
if (upcomingRes.ok) {
|
if (upcomingRes.ok) {
|
||||||
@@ -247,7 +248,7 @@ export function Calendar() {
|
|||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/calendar`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -304,7 +305,7 @@ export function Calendar() {
|
|||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/${eventId}/toggle-complete`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/calendar/${eventId}/toggle-complete`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { AIProviderIcon } from '@/components/AIProviderIcon';
|
import { AIProviderIcon } from '@/components/AIProviderIcon';
|
||||||
import { useHaptics } from '@/lib/haptics';
|
import { useHaptics } from '@/lib/haptics';
|
||||||
import { getApiV1BaseUrl } from '@/lib/api-url';
|
import { getApiV1BaseUrl, getApiOrigin } from '@/lib/api-url';
|
||||||
|
|
||||||
interface BrowserExtensionApiKey {
|
interface BrowserExtensionApiKey {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -199,7 +199,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
const loadAISettings = async () => {
|
const loadAISettings = async () => {
|
||||||
try {
|
try {
|
||||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`;
|
const endpoint = `${getApiOrigin()}/api/v1/auth/ai/settings`;
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -219,7 +219,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
const loadAvailableAIProviders = async () => {
|
const loadAvailableAIProviders = async () => {
|
||||||
try {
|
try {
|
||||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`;
|
const endpoint = `${getApiOrigin()}/api/v1/ai/providers`;
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -241,7 +241,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
const loadSearchSettings = async () => {
|
const loadSearchSettings = async () => {
|
||||||
try {
|
try {
|
||||||
const endpoint = `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`;
|
const endpoint = `${getApiOrigin()}/api/v1/auth/search/settings`;
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -293,7 +293,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -371,7 +371,7 @@ export const Settings = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/search/settings`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/search/settings`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -1553,7 +1553,7 @@ export const Settings = () => {
|
|||||||
// Save email settings
|
// Save email settings
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/email/settings`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/email/settings`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -1582,7 +1582,7 @@ export const Settings = () => {
|
|||||||
// Test email configuration
|
// Test email configuration
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/email/test`, {
|
const response = await fetch(`${getApiOrigin()}/api/v1/auth/email/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user