Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 2 additions & 20 deletions apps/admin/src/app/(authenticated)/appearance/menus/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,15 @@
* owns create / select / item-level CRUD with drag-to-reorder.
*/
import type { ReactElement } from 'react';
import { cookies } from 'next/headers';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import { MenusClient } from './MenusClient';
import type { MenuListResponse } from './types';

export const dynamic = 'force-dynamic';

async function fetchInitial(): Promise<MenuListResponse | null> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}
try {
const res = await fetch(`${apiBaseUrl}/api/v1/admin/menus`, {
method: 'GET',
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
});
const res = await serverApiFetch('/api/v1/admin/menus');
if (!res.ok) return null;
return (await res.json()) as MenuListResponse;
} catch {
Expand Down
20 changes: 8 additions & 12 deletions apps/admin/src/app/(authenticated)/appearance/themes/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/**
* Themes admin API client — small fetch wrappers over the
* /api/v1/admin/themes surface. Server-side calls forward the
* inbound cookie header so the API auth middleware sees the
* session; client-side calls rely on `credentials: 'include'` to
* carry the cookie cross-origin (admin runs on :3001, api on :8080).
* inbound cookie header via `serverApiFetch` so the API auth
* middleware sees the session; client-side calls (install/activate)
* rely on `credentials: 'include'` to carry the cookie cross-origin
* (admin runs on :3001, api on :8080).
*/

import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import type { InstallResponse, ThemesListResponse } from './types';

const LIST_URL = '/api/v1/admin/themes';
Expand All @@ -16,17 +18,11 @@ const ACTIVATE_URL = '/api/v1/admin/themes/activate';
/**
* Server-side list fetch. Returns `null` on any non-2xx so the
* caller can render an empty-state without short-circuiting the
* whole page render.
* whole page render. Cookie forwarding is handled by `serverApiFetch`.
*/
export async function fetchThemesList(cookieHeader: string): Promise<ThemesListResponse | null> {
export async function fetchThemesList(): Promise<ThemesListResponse | null> {
try {
const res = await fetch(`${apiBaseUrl}${LIST_URL}`, {
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
});
const res = await serverApiFetch(LIST_URL);
if (!res.ok) {
return null;
}
Expand Down
13 changes: 1 addition & 12 deletions apps/admin/src/app/(authenticated)/appearance/themes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,13 @@
*/

import type { ReactElement } from 'react';
import { cookies } from 'next/headers';
import { fetchThemesList } from './api';
import { ThemesGalleryClient } from './ThemesGalleryClient';

export const dynamic = 'force-dynamic';

export default async function ThemesPage(): Promise<ReactElement> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}
const data = await fetchThemesList(cookieHeader);
const data = await fetchThemesList();
return (
<ThemesGalleryClient
initialThemes={data?.themes ?? []}
Expand Down
32 changes: 4 additions & 28 deletions apps/admin/src/app/(authenticated)/comments/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
* island so the server component stays free of mutation logic.
*/
import { ArrowLeft } from 'lucide-react';
import { cookies } from 'next/headers';
import Link from 'next/link';
import { type ReactElement } from 'react';

import { Headline } from '@/components/ui/headline';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import { cn } from '@/lib/utils';

import { StatusBadge } from '../components/StatusBadge';
Expand All @@ -41,36 +40,15 @@ interface PageProps {
params: Promise<{ id: string }>;
}

async function authHeaders(): Promise<HeadersInit> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}
return {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
};
}

async function fetchComment(id: string): Promise<Comment | null> {
// We fetch via the list endpoint filtered by a single post — the
// detail endpoint isn't strictly required for the first cut.
// Instead we hit list and grep; if the comment isn't there
// (status-filtered out), we widen and re-fetch. The cost is
// bounded and the code stays simple until a dedicated GET-by-id
// lands.
const headers = await authHeaders();
try {
const res = await fetch(
`${apiBaseUrl}/api/v1/admin/comments?limit=100`,
{ method: 'GET', headers, cache: 'no-store' },
);
const res = await serverApiFetch('/api/v1/admin/comments?limit=100');
if (!res.ok) return null;
const wire = (await res.json()) as WireListResponse;
const found = wire.data.find((c) => c.id === id);
Expand All @@ -81,11 +59,9 @@ async function fetchComment(id: string): Promise<Comment | null> {
}

async function fetchThread(postId: string): Promise<CommentListResponse | null> {
const headers = await authHeaders();
try {
const res = await fetch(
`${apiBaseUrl}/api/v1/admin/comments?post_id=${encodeURIComponent(postId)}&limit=100`,
{ method: 'GET', headers, cache: 'no-store' },
const res = await serverApiFetch(
`/api/v1/admin/comments?post_id=${encodeURIComponent(postId)}&limit=100`,
);
if (!res.ok) return null;
const wire = (await res.json()) as WireListResponse;
Expand Down
31 changes: 6 additions & 25 deletions apps/admin/src/app/(authenticated)/comments/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
* style table inside the CommentListClient island. The skeleton +
* error states use the brand's paper-2 + danger-soft tokens.
*/
import { cookies } from 'next/headers';
import { Suspense, type ReactElement } from 'react';

import { Headline } from '@/components/ui/headline';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';

import { CommentListClient } from './CommentListClient';
import {
Expand Down Expand Up @@ -76,40 +75,22 @@ function FetchFailureState({ reason }: { reason: string }): ReactElement {
}

/**
* Server-side fetch helper. Forwards the session cookie so the API
* sees the operator. Returns `null` on any failure so the caller can
* render a friendly state without crashing the layout.
* Server-side fetch helper. Forwards the session cookie via
* `serverApiFetch` so the API sees the operator. Returns `null` on
* any failure so the caller can render a friendly state without
* crashing the layout.
*/
async function fetchInitialComments(
params: { status?: string; postId?: string; userId?: string },
): Promise<{ data: CommentListResponse | null; error: string | null }> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}

const qs = new URLSearchParams();
if (params.status) qs.set('status', params.status);
if (params.postId) qs.set('post_id', params.postId);
if (params.userId) qs.set('user_id', params.userId);
qs.set('limit', '30');

const url = `${apiBaseUrl}/api/v1/admin/comments?${qs.toString()}`;
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
});
const res = await serverApiFetch(`/api/v1/admin/comments?${qs.toString()}`);
if (!res.ok) {
return { data: null, error: `HTTP ${res.status}` };
}
Expand Down
26 changes: 4 additions & 22 deletions apps/admin/src/app/(authenticated)/jobs/dlq/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
* italic accent so an operator immediately sees what kind of failure
* they're inspecting.
*/
import { cookies } from 'next/headers';
import Link from 'next/link';
import { ChevronLeft } from 'lucide-react';
import { Suspense, type ReactElement } from 'react';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import { Card } from '@/components/ui/card';
import { DLQDetailClient } from './DLQDetailClient';
import type { ArchivedTask } from '../types';
Expand All @@ -26,27 +25,10 @@ async function fetchTask(
id: string,
queue: string,
): Promise<{ data: ArchivedTask | null; error: string | null }> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}

const url = `${apiBaseUrl}/api/v1/admin/jobs/dlq/${encodeURIComponent(id)}?queue=${encodeURIComponent(queue)}`;
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
});
const res = await serverApiFetch(
`/api/v1/admin/jobs/dlq/${encodeURIComponent(id)}?queue=${encodeURIComponent(queue)}`,
);
if (!res.ok) {
return { data: null, error: `HTTP ${res.status}` };
}
Expand Down
26 changes: 4 additions & 22 deletions apps/admin/src/app/(authenticated)/jobs/dlq/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
*
* Issue #262.
*/
import { cookies } from 'next/headers';
import { type ReactElement, Suspense } from 'react';
import Link from 'next/link';
import { ChevronLeft } from 'lucide-react';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import { Headline } from '@/components/ui/headline';
import { Card } from '@/components/ui/card';
import { DLQListClient } from './DLQListClient';
Expand All @@ -34,27 +33,10 @@ export const dynamic = 'force-dynamic';
async function fetchInitialDLQ(
queue: string,
): Promise<{ data: DLQListResponse | null; error: string | null }> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}

const url = `${apiBaseUrl}/api/v1/admin/jobs/dlq?queue=${encodeURIComponent(queue)}&limit=30`;
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
});
const res = await serverApiFetch(
`/api/v1/admin/jobs/dlq?queue=${encodeURIComponent(queue)}&limit=30`,
);
if (!res.ok) {
return { data: null, error: `HTTP ${res.status}` };
}
Expand Down
25 changes: 3 additions & 22 deletions apps/admin/src/app/(authenticated)/media/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,18 @@
* off to the client-side editor for alt-text + caption editing,
* deletion, and storage-URL display.
*/
import { cookies } from 'next/headers';
import { notFound } from 'next/navigation';
import type { ReactElement } from 'react';
import { apiBaseUrl } from '@/lib/api-client';
import { serverApiFetch } from '@/lib/server-api';
import { MediaDetailClient } from './MediaDetailClient';
import type { MediaAsset } from '../types';

export const dynamic = 'force-dynamic';

async function fetchAsset(id: string): Promise<MediaAsset | null> {
let cookieHeader = '';
try {
const store = await cookies();
cookieHeader = store
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
} catch {
cookieHeader = '';
}
try {
const res = await fetch(
`${apiBaseUrl}/api/v1/admin/media/${encodeURIComponent(id)}`,
{
method: 'GET',
headers: {
Accept: 'application/json',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
cache: 'no-store',
},
const res = await serverApiFetch(
`/api/v1/admin/media/${encodeURIComponent(id)}`,
);
if (res.status === 404) return null;
if (!res.ok) return null;
Expand Down
Loading
Loading