Fix API routing: apiFetch helper prefixes basePath for all /api/v1/ calls
Without this, fetch('/api/v1/...') from the browser hits Apache root,
which routes /api/ to OliVAS (port 8000) instead of DeckForge (port 8001).
apiFetch prepends NEXT_PUBLIC_BASE_PATH so requests go through Next.js
rewrites to the correct backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3077159da3
commit
bebe2ac390
22 changed files with 99 additions and 66 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
|
|
@ -57,7 +58,7 @@ export default function ReviewWorkflow({ presentationId }: ReviewWorkflowProps)
|
|||
|
||||
const fetchReviewInfo = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/presentation/${presentationId}/review`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/presentation/${presentationId}/review`, {
|
||||
headers: getHeader(),
|
||||
});
|
||||
const data = await ApiResponseHandler.handleResponse(response, "Failed to fetch review info");
|
||||
|
|
@ -72,7 +73,7 @@ export default function ReviewWorkflow({ presentationId }: ReviewWorkflowProps)
|
|||
const handleStatusChange = async (newStatus: string) => {
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/presentation/${presentationId}/status`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/presentation/${presentationId}/status`, {
|
||||
method: "PUT",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({ status: newStatus, comment: comment || null }),
|
||||
|
|
@ -93,7 +94,7 @@ export default function ReviewWorkflow({ presentationId }: ReviewWorkflowProps)
|
|||
if (!comment.trim()) return;
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/presentation/${presentationId}/comment`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/presentation/${presentationId}/comment`, {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({ comment }),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect } from "react";
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import { getHeader } from "../services/api/header";
|
||||
|
|
@ -74,7 +75,7 @@ export function useBrandTheme() {
|
|||
}
|
||||
|
||||
// Fetch brand config for selected client
|
||||
fetch(`/api/v1/admin/clients/${selectedClientId}/brand`, {
|
||||
apiFetch(`/api/v1/admin/clients/${selectedClientId}/brand`, {
|
||||
headers: getHeader(),
|
||||
})
|
||||
.then((res) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getHeaderForFormData } from "./header";
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
import { ApiResponseHandler } from "./api-error-handler";
|
||||
import { ImageAssetResponse } from "./types";
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ export class ImagesApi {
|
|||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const response = await fetch(`/api/v1/ppt/images/upload`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/images/upload`, {
|
||||
method: "POST",
|
||||
headers: getHeaderForFormData(),
|
||||
body: formData,
|
||||
|
|
@ -23,7 +24,7 @@ export class ImagesApi {
|
|||
|
||||
static async getUploadedImages(): Promise<ImageAssetResponse[]> {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/images/uploaded`);
|
||||
const response = await apiFetch(`/api/v1/ppt/images/uploaded`);
|
||||
return await ApiResponseHandler.handleResponse(response, "Failed to get uploaded images") as ImageAssetResponse[];
|
||||
} catch (error:any) {
|
||||
console.log("Get uploaded images error:", error);
|
||||
|
|
@ -33,7 +34,7 @@ export class ImagesApi {
|
|||
|
||||
static async deleteImage(image_id: string): Promise<{success: boolean, message?: string}> {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/images/${image_id}`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/images/${image_id}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
return await ApiResponseHandler.handleResponse(response, "Failed to delete image") as {success: boolean, message?: string};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { ApiResponseHandler } from "./api-error-handler";
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
|
||||
class TemplateService {
|
||||
|
||||
static async getCustomTemplateSummaries() {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/template-management/summary`,);
|
||||
const response = await apiFetch(`/api/v1/ppt/template-management/summary`,);
|
||||
return await ApiResponseHandler.handleResponse(response, "Failed to get custom template summaries");
|
||||
} catch (error) {
|
||||
console.error("Failed to get custom template summaries", error);
|
||||
|
|
@ -14,7 +15,7 @@ class TemplateService {
|
|||
|
||||
static async getCustomTemplateDetails(templateId: string) {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/template-management/get-templates/${templateId}`,);
|
||||
const response = await apiFetch(`/api/v1/ppt/template-management/get-templates/${templateId}`,);
|
||||
return await ApiResponseHandler.handleResponse(response, "Failed to get custom template details");
|
||||
} catch (error) {
|
||||
console.error("Failed to get custom template details", error);
|
||||
|
|
@ -24,7 +25,7 @@ class TemplateService {
|
|||
|
||||
static async deleteCustomTemplate(presentationId: string) {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ppt/template-management/delete-templates/${presentationId}`, { method: "DELETE" });
|
||||
const response = await apiFetch(`/api/v1/ppt/template-management/delete-templates/${presentationId}`, { method: "DELETE" });
|
||||
return await ApiResponseHandler.handleResponseWithResult(response, "Failed to delete custom template");
|
||||
} catch (error) {
|
||||
console.error("Failed to delete custom template", error);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getHeader, getHeaderForFormData } from "./header";
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
import { ApiResponseHandler } from "./api-error-handler";
|
||||
|
||||
export interface ClientOption {
|
||||
|
|
@ -30,7 +31,7 @@ export class WizardApi {
|
|||
/** Fetch clients available to the current user */
|
||||
static async getClients(): Promise<ClientOption[]> {
|
||||
try {
|
||||
const response = await fetch("/api/v1/admin/clients", {
|
||||
const response = await apiFetch("/api/v1/admin/clients", {
|
||||
method: "GET",
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
@ -46,7 +47,7 @@ export class WizardApi {
|
|||
static async getMasterDecks(clientId?: string): Promise<MasterDeckOption[]> {
|
||||
try {
|
||||
const params = clientId ? `?client_id=${clientId}` : "";
|
||||
const response = await fetch(`/api/v1/admin/master-decks${params}`, {
|
||||
const response = await apiFetch(`/api/v1/admin/master-decks${params}`, {
|
||||
method: "GET",
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
@ -63,7 +64,7 @@ export class WizardApi {
|
|||
const formData = new FormData();
|
||||
files.forEach((file) => formData.append("files", file));
|
||||
|
||||
const response = await fetch("/api/v1/ppt/files/upload", {
|
||||
const response = await apiFetch("/api/v1/ppt/files/upload", {
|
||||
method: "POST",
|
||||
headers: getHeaderForFormData(),
|
||||
body: formData,
|
||||
|
|
@ -74,7 +75,7 @@ export class WizardApi {
|
|||
|
||||
/** Decompose uploaded documents */
|
||||
static async decomposeFiles(filePaths: string[]): Promise<any[]> {
|
||||
const response = await fetch("/api/v1/ppt/files/decompose", {
|
||||
const response = await apiFetch("/api/v1/ppt/files/decompose", {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({ file_paths: filePaths }),
|
||||
|
|
@ -94,7 +95,7 @@ export class WizardApi {
|
|||
client_id?: string;
|
||||
master_deck_id?: string;
|
||||
}) {
|
||||
const response = await fetch("/api/v1/ppt/presentation/generate/async", {
|
||||
const response = await apiFetch("/api/v1/ppt/presentation/generate/async", {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify(params),
|
||||
|
|
@ -105,7 +106,7 @@ export class WizardApi {
|
|||
|
||||
/** Poll job status */
|
||||
static async getJobStatus(jobId: string): Promise<JobStatus> {
|
||||
const response = await fetch(`/api/v1/ppt/jobs/${jobId}`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/jobs/${jobId}`, {
|
||||
method: "GET",
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
@ -114,7 +115,7 @@ export class WizardApi {
|
|||
|
||||
/** Cancel a job */
|
||||
static async cancelJob(jobId: string): Promise<void> {
|
||||
const response = await fetch(`/api/v1/ppt/jobs/${jobId}`, {
|
||||
const response = await apiFetch(`/api/v1/ppt/jobs/${jobId}`, {
|
||||
method: "DELETE",
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
@ -123,7 +124,7 @@ export class WizardApi {
|
|||
|
||||
/** Fetch URL content and extract text */
|
||||
static async fetchUrl(url: string): Promise<string> {
|
||||
const response = await fetch("/api/v1/ppt/files/fetch-url", {
|
||||
const response = await apiFetch("/api/v1/ppt/files/fetch-url", {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({ url }),
|
||||
|
|
@ -138,7 +139,7 @@ export class WizardApi {
|
|||
static async checkFollowUpQuestions(content: string): Promise<string[]> {
|
||||
if (!content || content.trim().length < 10) return [];
|
||||
try {
|
||||
const response = await fetch("/api/v1/ppt/content/follow-up-questions", {
|
||||
const response = await apiFetch("/api/v1/ppt/content/follow-up-questions", {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({ content }),
|
||||
|
|
@ -162,7 +163,7 @@ export class WizardApi {
|
|||
client_id?: string;
|
||||
master_deck_id?: string;
|
||||
}) {
|
||||
const response = await fetch("/api/v1/ppt/presentation/create", {
|
||||
const response = await apiFetch("/api/v1/ppt/presentation/create", {
|
||||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify(params),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { apiFetch } from '../../../../../lib/apiFetch';
|
||||
import { getHeader } from '@/app/(presentation-generator)/services/api/header';
|
||||
import { ApiResponseHandler } from '@/app/(presentation-generator)/services/api/api-error-handler';
|
||||
import { ProcessedSlide } from '@/app/(presentation-generator)/custom-template/types';
|
||||
|
|
@ -77,7 +78,7 @@ export const useTemplateLayoutsAutoSave = ({
|
|||
setSaveStatus('saving');
|
||||
console.log('🔄 Auto-saving template layouts...');
|
||||
|
||||
const response = await fetch('/api/v1/ppt/template/update', {
|
||||
const response = await apiFetch('/api/v1/ppt/template/update', {
|
||||
method: 'PUT',
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../lib/apiFetch';
|
||||
import { setCanChangeKeys, setLLMConfig } from '@/store/slices/userConfig';
|
||||
import { hasValidLLMConfig } from '@/utils/storeHelpers';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
|
|
@ -86,7 +87,7 @@ export function ConfigurationInitializer({ children }: { children: React.ReactNo
|
|||
|
||||
const checkIfSelectedCustomModelIsAvailable = async (llmConfig: LLMConfig) => {
|
||||
try {
|
||||
const response = await fetch('/api/v1/ppt/openai/models/available', {
|
||||
const response = await apiFetch('/api/v1/ppt/openai/models/available', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store/store';
|
||||
import {
|
||||
|
|
@ -56,7 +57,7 @@ interface AIUsageData {
|
|||
|
||||
async function fetchAnalytics(endpoint: string, clientId?: string) {
|
||||
const params = clientId ? `?client_id=${clientId}` : '';
|
||||
const response = await fetch(`/api/v1/admin/analytics/${endpoint}${params}`, {
|
||||
const response = await apiFetch(`/api/v1/admin/analytics/${endpoint}${params}`, {
|
||||
headers: getHeader(),
|
||||
});
|
||||
if (!response.ok) throw new Error(`Failed to fetch ${endpoint}`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../../../../../lib/apiFetch';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppDispatch, RootState } from '@/store/store';
|
||||
|
|
@ -78,7 +79,7 @@ export default function BrandConfigPage() {
|
|||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/brand/logo`, {
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/brand/logo`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
|
@ -95,7 +96,7 @@ export default function BrandConfigPage() {
|
|||
|
||||
const handleDeleteLogo = async (index: number) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/brand/logo/${index}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/brand/logo/${index}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (res.ok) {
|
||||
|
|
@ -115,7 +116,7 @@ export default function BrandConfigPage() {
|
|||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/brand/guideline`, {
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/brand/guideline`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -17,7 +18,7 @@ export default function ClientDetailPage() {
|
|||
useEffect(() => {
|
||||
const fetchClient = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}`);
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}`);
|
||||
if (res.ok) setClient(await res.json());
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import {
|
||||
Settings,
|
||||
Loader2,
|
||||
|
|
@ -78,7 +79,7 @@ export default function SettingsPage() {
|
|||
setLoadingModels(true);
|
||||
setAvailableModels([]);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/settings/models?provider=${provider}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/settings/models?provider=${provider}`, {
|
||||
headers: getHeader(),
|
||||
});
|
||||
if (res.ok) {
|
||||
|
|
@ -103,7 +104,7 @@ export default function SettingsPage() {
|
|||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch('/api/v1/admin/settings', { headers: getHeader() });
|
||||
const res = await apiFetch('/api/v1/admin/settings', { headers: getHeader() });
|
||||
if (res.status === 403) {
|
||||
setError('Super admin access required');
|
||||
return;
|
||||
|
|
@ -136,7 +137,7 @@ export default function SettingsPage() {
|
|||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/v1/admin/settings', {
|
||||
const res = await apiFetch('/api/v1/admin/settings', {
|
||||
method: 'PUT',
|
||||
headers: { ...getHeader(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
|
|
@ -161,7 +162,7 @@ export default function SettingsPage() {
|
|||
const body: Record<string, string> = { provider: 'google' };
|
||||
if (googleKey) body.api_key = googleKey;
|
||||
|
||||
const res = await fetch('/api/v1/admin/settings/test-connection', {
|
||||
const res = await apiFetch('/api/v1/admin/settings/test-connection', {
|
||||
method: 'POST',
|
||||
headers: { ...getHeader(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store/store';
|
||||
import {
|
||||
|
|
@ -90,7 +91,7 @@ export default function StoragePage() {
|
|||
// Load client list for super_admin
|
||||
useEffect(() => {
|
||||
if (isSuperAdmin) {
|
||||
fetch('/api/v1/admin/clients', { headers: getHeader() })
|
||||
apiFetch('/api/v1/admin/clients', { headers: getHeader() })
|
||||
.then((r) => (r.ok ? r.json() : []))
|
||||
.then((data) => setClients(data))
|
||||
.catch(() => {});
|
||||
|
|
@ -104,8 +105,8 @@ export default function StoragePage() {
|
|||
const params = selectedClientId ? `?client_id=${selectedClientId}` : '';
|
||||
const headers = getHeader();
|
||||
const [summaryRes, presRes] = await Promise.all([
|
||||
fetch(`/api/v1/admin/storage/summary${params}`, { headers }),
|
||||
fetch(`/api/v1/admin/storage/presentations${params}`, { headers }),
|
||||
apiFetch(`/api/v1/admin/storage/summary${params}`, { headers }),
|
||||
apiFetch(`/api/v1/admin/storage/presentations${params}`, { headers }),
|
||||
]);
|
||||
if (summaryRes.ok) setSummary(await summaryRes.json());
|
||||
if (presRes.ok) setPresentations(await presRes.json());
|
||||
|
|
@ -127,7 +128,7 @@ export default function StoragePage() {
|
|||
const handleDelete = async () => {
|
||||
if (!deleteTarget) return;
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/storage/presentations/${deleteTarget.id}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/storage/presentations/${deleteTarget.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
@ -145,7 +146,7 @@ export default function StoragePage() {
|
|||
|
||||
const handleBulkDelete = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/v1/admin/storage/presentations/bulk-delete', {
|
||||
const res = await apiFetch('/api/v1/admin/storage/presentations/bulk-delete', {
|
||||
method: 'POST',
|
||||
headers: { ...getHeader(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: Array.from(selectedIds) }),
|
||||
|
|
@ -167,7 +168,7 @@ export default function StoragePage() {
|
|||
setPurging(true);
|
||||
try {
|
||||
const params = selectedClientId ? `?client_id=${selectedClientId}` : '';
|
||||
const res = await fetch(`/api/v1/admin/storage/purge${params}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/storage/purge${params}`, {
|
||||
method: 'POST',
|
||||
headers: getHeader(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -43,7 +44,7 @@ export default function TemplateDetailPage() {
|
|||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/ppt/template-management/get-templates/${templateId}`);
|
||||
const res = await apiFetch(`/api/v1/ppt/template-management/get-templates/${templateId}`);
|
||||
if (!res.ok) throw new Error('Failed to load template');
|
||||
const data = await res.json();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { apiFetch } from '../../../lib/apiFetch';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
|
|
@ -35,7 +36,7 @@ export default function TemplatesPage() {
|
|||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch('/api/v1/ppt/template-management/summary');
|
||||
const res = await apiFetch('/api/v1/ppt/template-management/summary');
|
||||
if (!res.ok) throw new Error('Failed to load templates');
|
||||
const data = await res.json();
|
||||
const mapped: TemplateSummary[] = (data.presentations || []).map((item: any) => ({
|
||||
|
|
@ -58,7 +59,7 @@ export default function TemplatesPage() {
|
|||
if (!deleteTarget) return;
|
||||
setDeleting(true);
|
||||
try {
|
||||
const res = await fetch(`/api/v1/ppt/template-management/delete-templates/${deleteTarget.id}`, {
|
||||
const res = await apiFetch(`/api/v1/ppt/template-management/delete-templates/${deleteTarget.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!res.ok && res.status !== 204) throw new Error('Failed to delete');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../../../../lib/apiFetch';
|
||||
import { useParams } from 'next/navigation';
|
||||
import RoleBadge from '../../components/RoleBadge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -25,7 +26,7 @@ export default function UserDetailPage() {
|
|||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/admin/users/${params.id}`);
|
||||
const res = await apiFetch(`/api/v1/admin/users/${params.id}`);
|
||||
if (res.ok) setUser(await res.json());
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiFetch } from '../../lib/apiFetch';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store/store';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
|
@ -32,7 +33,7 @@ export default function LoginPage() {
|
|||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/dev-login', {
|
||||
const response = await apiFetch('/api/v1/auth/dev-login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiFetch } from '../lib/apiFetch';
|
||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
|
|
@ -53,7 +54,7 @@ export default function AnthropicConfig({
|
|||
|
||||
setModelsLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/ppt/anthropic/models/available', {
|
||||
const response = await apiFetch('/api/v1/ppt/anthropic/models/available', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiFetch } from '../lib/apiFetch';
|
||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
|
|
@ -50,7 +51,7 @@ export default function GoogleConfig({
|
|||
|
||||
setModelsLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/ppt/google/models/available', {
|
||||
const response = await apiFetch('/api/v1/ppt/google/models/available', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { apiFetch } from '../lib/apiFetch';
|
||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
|
|
@ -41,7 +42,7 @@ export default function OllamaConfig({
|
|||
const fetchOllamaModels = async () => {
|
||||
try {
|
||||
setOllamaModelsLoading(true);
|
||||
const response = await fetch('/api/v1/ppt/ollama/models/supported');
|
||||
const response = await apiFetch('/api/v1/ppt/ollama/models/supported');
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiFetch } from '../lib/apiFetch';
|
||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
|
|
@ -52,7 +53,7 @@ export default function OpenAIConfig({
|
|||
|
||||
setModelsLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/ppt/openai/models/available', {
|
||||
const response = await apiFetch('/api/v1/ppt/openai/models/available', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
12
frontend/lib/apiFetch.ts
Normal file
12
frontend/lib/apiFetch.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Wrapper around fetch that prepends the Next.js basePath so API calls
|
||||
* reach the correct backend when deployed under a sub-path (e.g. /ppt-tool).
|
||||
*
|
||||
* Usage: apiFetch('/api/v1/...', options) — identical to fetch(), just works.
|
||||
*/
|
||||
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
|
||||
|
||||
export function apiFetch(path: string, init?: RequestInit): Promise<Response> {
|
||||
const url = path.startsWith('/api/') ? `${BASE_PATH}${path}` : path;
|
||||
return fetch(url, init);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { apiFetch } from '../../lib/apiFetch';
|
||||
|
||||
export interface AdminUser {
|
||||
id: string;
|
||||
|
|
@ -94,7 +95,7 @@ const initialState: AdminState = {
|
|||
export const fetchUsers = createAsyncThunk(
|
||||
"admin/fetchUsers",
|
||||
async (_, { rejectWithValue }) => {
|
||||
const res = await fetch("/api/v1/admin/users");
|
||||
const res = await apiFetch("/api/v1/admin/users");
|
||||
if (!res.ok) return rejectWithValue("Failed to fetch users");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -103,7 +104,7 @@ export const fetchUsers = createAsyncThunk(
|
|||
export const updateUserRole = createAsyncThunk(
|
||||
"admin/updateUserRole",
|
||||
async ({ userId, role }: { userId: string; role: string }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/users/${userId}/role?role=${role}`, { method: "PUT" });
|
||||
const res = await apiFetch(`/api/v1/admin/users/${userId}/role?role=${role}`, { method: "PUT" });
|
||||
if (!res.ok) return rejectWithValue("Failed to update role");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -112,7 +113,7 @@ export const updateUserRole = createAsyncThunk(
|
|||
export const deactivateUser = createAsyncThunk(
|
||||
"admin/deactivateUser",
|
||||
async (userId: string, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/users/${userId}`, { method: "DELETE" });
|
||||
const res = await apiFetch(`/api/v1/admin/users/${userId}`, { method: "DELETE" });
|
||||
if (!res.ok) return rejectWithValue("Failed to deactivate user");
|
||||
return userId;
|
||||
}
|
||||
|
|
@ -123,7 +124,7 @@ export const deactivateUser = createAsyncThunk(
|
|||
export const fetchClients = createAsyncThunk(
|
||||
"admin/fetchClients",
|
||||
async (_, { rejectWithValue }) => {
|
||||
const res = await fetch("/api/v1/admin/clients");
|
||||
const res = await apiFetch("/api/v1/admin/clients");
|
||||
if (!res.ok) return rejectWithValue("Failed to fetch clients");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -132,7 +133,7 @@ export const fetchClients = createAsyncThunk(
|
|||
export const createClient = createAsyncThunk(
|
||||
"admin/createClient",
|
||||
async ({ name, review_policy }: { name: string; review_policy?: string }, { rejectWithValue }) => {
|
||||
const res = await fetch("/api/v1/admin/clients", {
|
||||
const res = await apiFetch("/api/v1/admin/clients", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, review_policy: review_policy || "self_approve" }),
|
||||
|
|
@ -162,7 +163,7 @@ export const fetchTeams = createAsyncThunk(
|
|||
export const fetchTeamDetail = createAsyncThunk(
|
||||
"admin/fetchTeamDetail",
|
||||
async (teamId: string) => {
|
||||
const res = await fetch(`/api/v1/admin/teams/${teamId}`);
|
||||
const res = await apiFetch(`/api/v1/admin/teams/${teamId}`);
|
||||
if (!res.ok) throw new Error("Failed to fetch team");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -171,7 +172,7 @@ export const fetchTeamDetail = createAsyncThunk(
|
|||
export const addTeamMember = createAsyncThunk(
|
||||
"admin/addTeamMember",
|
||||
async ({ teamId, userId }: { teamId: string; userId: string }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/teams/${teamId}/members`, {
|
||||
const res = await apiFetch(`/api/v1/admin/teams/${teamId}/members`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ user_id: userId }),
|
||||
|
|
@ -187,7 +188,7 @@ export const addTeamMember = createAsyncThunk(
|
|||
export const removeTeamMember = createAsyncThunk(
|
||||
"admin/removeTeamMember",
|
||||
async ({ teamId, userId }: { teamId: string; userId: string }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/teams/${teamId}/members/${userId}`, { method: "DELETE" });
|
||||
const res = await apiFetch(`/api/v1/admin/teams/${teamId}/members/${userId}`, { method: "DELETE" });
|
||||
if (!res.ok) return rejectWithValue("Failed to remove member");
|
||||
return { teamId, userId };
|
||||
}
|
||||
|
|
@ -199,7 +200,7 @@ export const fetchAuditLogs = createAsyncThunk(
|
|||
"admin/fetchAuditLogs",
|
||||
async (params?: Record<string, string>) => {
|
||||
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
||||
const res = await fetch(`/api/v1/admin/audit-log${query}`);
|
||||
const res = await apiFetch(`/api/v1/admin/audit-log${query}`);
|
||||
if (!res.ok) throw new Error("Failed to fetch audit logs");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -210,7 +211,7 @@ export const fetchAuditLogs = createAsyncThunk(
|
|||
export const fetchBrandConfig = createAsyncThunk(
|
||||
"admin/fetchBrandConfig",
|
||||
async (clientId: string) => {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/brand`);
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/brand`);
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) return null;
|
||||
throw new Error("Failed to fetch brand config");
|
||||
|
|
@ -222,7 +223,7 @@ export const fetchBrandConfig = createAsyncThunk(
|
|||
export const updateBrandConfig = createAsyncThunk(
|
||||
"admin/updateBrandConfig",
|
||||
async ({ clientId, data }: { clientId: string; data: Partial<BrandConfig> }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/brand`, {
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/brand`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
|
|
@ -237,7 +238,7 @@ export const updateBrandConfig = createAsyncThunk(
|
|||
export const fetchMasterDecks = createAsyncThunk(
|
||||
"admin/fetchMasterDecks",
|
||||
async (clientId: string) => {
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/master-decks`);
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/master-decks`);
|
||||
if (!res.ok) throw new Error("Failed to fetch master decks");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -248,7 +249,7 @@ export const uploadMasterDeck = createAsyncThunk(
|
|||
async ({ clientId, file }: { clientId: string; file: File }, { rejectWithValue }) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const res = await fetch(`/api/v1/admin/clients/${clientId}/master-decks`, {
|
||||
const res = await apiFetch(`/api/v1/admin/clients/${clientId}/master-decks`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
|
@ -263,7 +264,7 @@ export const uploadMasterDeck = createAsyncThunk(
|
|||
export const fetchMasterDeckDetail = createAsyncThunk(
|
||||
"admin/fetchMasterDeckDetail",
|
||||
async (deckId: string) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}`);
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}`);
|
||||
if (!res.ok) throw new Error("Failed to fetch master deck detail");
|
||||
return await res.json();
|
||||
}
|
||||
|
|
@ -272,7 +273,7 @@ export const fetchMasterDeckDetail = createAsyncThunk(
|
|||
export const updateMasterDeck = createAsyncThunk(
|
||||
"admin/updateMasterDeck",
|
||||
async ({ deckId, data }: { deckId: string; data: { name?: string; description?: string; is_active?: boolean } }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
|
|
@ -288,7 +289,7 @@ export const updateMasterDeckLayout = createAsyncThunk(
|
|||
{ deckId, layoutIndex, data }: { deckId: string; layoutIndex: number; data: { layout_name?: string; layout_type?: string; react_code?: string } },
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}/layouts/${layoutIndex}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}/layouts/${layoutIndex}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
|
|
@ -302,7 +303,7 @@ export const reparseMasterDeck = createAsyncThunk(
|
|||
"admin/reparseMasterDeck",
|
||||
async ({ deckId, parseMode }: { deckId: string; parseMode?: string }, { rejectWithValue }) => {
|
||||
const params = parseMode ? `?parse_mode=${parseMode}` : "";
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}/reparse${params}`, { method: "POST" });
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}/reparse${params}`, { method: "POST" });
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return rejectWithValue(data.detail || "Failed to trigger reparse");
|
||||
|
|
@ -314,7 +315,7 @@ export const reparseMasterDeck = createAsyncThunk(
|
|||
export const deleteMasterDeck = createAsyncThunk(
|
||||
"admin/deleteMasterDeck",
|
||||
async (deckId: string, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}`, { method: "DELETE" });
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}`, { method: "DELETE" });
|
||||
if (!res.ok) return rejectWithValue("Failed to delete master deck");
|
||||
return deckId;
|
||||
}
|
||||
|
|
@ -323,7 +324,7 @@ export const deleteMasterDeck = createAsyncThunk(
|
|||
export const deleteMasterDeckLayout = createAsyncThunk(
|
||||
"admin/deleteMasterDeckLayout",
|
||||
async ({ deckId, layoutIndex }: { deckId: string; layoutIndex: number }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}/layouts/${layoutIndex}`, {
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}/layouts/${layoutIndex}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!res.ok) return rejectWithValue("Failed to delete layout");
|
||||
|
|
@ -334,7 +335,7 @@ export const deleteMasterDeckLayout = createAsyncThunk(
|
|||
export const bulkDeleteMasterDeckLayouts = createAsyncThunk(
|
||||
"admin/bulkDeleteMasterDeckLayouts",
|
||||
async ({ deckId, indices }: { deckId: string; indices: number[] }, { rejectWithValue }) => {
|
||||
const res = await fetch(`/api/v1/admin/master-decks/${deckId}/layouts/bulk-delete`, {
|
||||
const res = await apiFetch(`/api/v1/admin/master-decks/${deckId}/layouts/bulk-delete`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ indices }),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue