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
36 changes: 36 additions & 0 deletions frontend/hooks/useUpdateAsset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { assetApiClient } from '@/lib/api/assets';
import { queryKeys } from '@/lib/query/keys';
import type { Asset } from '@/lib/query/types/asset';

export function useUpdateAsset(assetId: string) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: Record<string, unknown>) =>
assetApiClient.updateAsset(assetId, data),

onMutate: async (newData) => {
// Optimistic update
await queryClient.cancelQueries({ queryKey: queryKeys.assets.detail(assetId) });
const previousAsset = queryClient.getQueryData<Asset>(queryKeys.assets.detail(assetId));
if (previousAsset) {
queryClient.setQueryData<Asset>(queryKeys.assets.detail(assetId), { ...previousAsset, ...newData });
}
return { previousAsset };
},

onError: (_err, _newData, context) => {
// Rollback on error
if (context?.previousAsset) {
queryClient.setQueryData(queryKeys.assets.detail(assetId), context.previousAsset);
}
},

onSettled: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.assets.detail(assetId) });
},
});
}
27 changes: 27 additions & 0 deletions frontend/lib/api/asset-photos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { api } from '../api';

export interface AssetPhoto {
id: string;
url: string;
isPrimary: boolean;
createdAt: string;
}

export const assetPhotosApiClient = {
list: (assetId: string): Promise<AssetPhoto[]> =>
api.get<AssetPhoto[]>(`/assets/${assetId}/photos`).then((r) => r.data),

upload: (assetId: string, file: File): Promise<AssetPhoto> => {
const form = new FormData();
form.append('file', file);
return api.post<AssetPhoto>(`/assets/${assetId}/photos`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
}).then((r) => r.data);
},

delete: (assetId: string, photoId: string): Promise<void> =>
api.delete(`/assets/${assetId}/photos/${photoId}`).then(() => undefined),

setPrimary: (assetId: string, photoId: string): Promise<AssetPhoto> =>
api.patch<AssetPhoto>(`/assets/${assetId}/photos/${photoId}/primary`).then((r) => r.data),
};
20 changes: 20 additions & 0 deletions frontend/lib/api/http-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Single HTTP client for the frontend.
*
* All API requests should go through the `api` axios instance from `lib/api.ts`.
* Per-resource modules in `lib/api/*.ts` wrap specific endpoints.
*
* The legacy `apiClient.request` wrapper in `lib/api/client.ts` is deprecated;
* new code should use the resource modules directly. Raw `fetch('/api/...')`
* calls should be replaced with `api.get/post/patch/delete(...)` to ensure
* the correct base URL and auth token are attached.
*
* @example
* import { api } from '@/lib/api';
* const { data } = await api.get('/assets');
*
* @example
* import { assetApiClient } from '@/lib/api/assets';
* const assets = await assetApiClient.getAssets();
*/
export { api } from '../api';
23 changes: 23 additions & 0 deletions frontend/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
output: "standalone",
poweredByHeader: false,
images: {
remotePatterns: [
{ protocol: "https", hostname: "**" },
],
},
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' http://localhost:6003 ws://localhost:6003 https://horizon-testnet.stellar.org https://soroban-testnet.stellar.org; font-src 'self' data:;",
},
],
},
];
},
};

export default nextConfig;
Loading