refactor shopify storefront
This commit is contained in:
65
services/media-adapter.ts
Normal file
65
services/media-adapter.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type {
|
||||
MediaAdapter,
|
||||
MediaItem,
|
||||
MediaPage,
|
||||
} from "@reacteditor/plugin-media";
|
||||
|
||||
const MEDIA_BASE = "https://www.frontend-ai.com";
|
||||
const MEDIA_API_KEY = (import.meta.env.VITE_API_KEY as string | undefined) ?? "";
|
||||
|
||||
export const frontendAiMediaAdapter: MediaAdapter = {
|
||||
fetchList: async ({ query, cursor, signal }) => {
|
||||
const url = new URL("/api/media", MEDIA_BASE);
|
||||
if (query) url.searchParams.set("query", query);
|
||||
if (cursor) url.searchParams.set("cursor", cursor);
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { "X-Api-Key": MEDIA_API_KEY },
|
||||
signal,
|
||||
});
|
||||
if (!res.ok) throw new Error(`List failed: ${res.status}`);
|
||||
return (await res.json()) as MediaPage;
|
||||
},
|
||||
|
||||
upload: (file, opts) =>
|
||||
new Promise<MediaItem>((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `${MEDIA_BASE}/api/media`);
|
||||
xhr.setRequestHeader("X-Api-Key", MEDIA_API_KEY);
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) opts?.onProgress?.(e.loaded / e.total);
|
||||
};
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 400) {
|
||||
reject(new Error(xhr.responseText || `Upload failed: ${xhr.status}`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(xhr.responseText) as MediaItem);
|
||||
} catch (err) {
|
||||
reject(err instanceof Error ? err : new Error(String(err)));
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => reject(new Error("Network error"));
|
||||
xhr.onabort = () => {
|
||||
const err = new Error("Aborted");
|
||||
err.name = "AbortError";
|
||||
reject(err);
|
||||
};
|
||||
opts?.signal?.addEventListener("abort", () => xhr.abort());
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
xhr.send(fd);
|
||||
}),
|
||||
|
||||
delete: async (id) => {
|
||||
const res = await fetch(
|
||||
`${MEDIA_BASE}/api/media/${encodeURIComponent(id)}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: { "X-Api-Key": MEDIA_API_KEY },
|
||||
},
|
||||
);
|
||||
if (!res.ok) throw new Error(`Delete failed: ${res.status}`);
|
||||
},
|
||||
};
|
||||
63
services/shopify/client.ts
Normal file
63
services/shopify/client.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export const SHOPIFY_API_VERSION = '2026-04';
|
||||
|
||||
type Credentials = {
|
||||
domain: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
let currentCreds: Partial<Credentials> = {};
|
||||
|
||||
export function setShopifyCredentials(creds: Credentials) {
|
||||
currentCreds = { domain: creds.domain, token: creds.token };
|
||||
}
|
||||
|
||||
export function getShopifyCredentials(): Partial<Credentials> {
|
||||
return currentCreds;
|
||||
}
|
||||
|
||||
export const SHOPIFY_STORE_DOMAIN = currentCreds.domain ?? '';
|
||||
|
||||
export async function shopifyFetch<T = any>({
|
||||
query,
|
||||
variables = {},
|
||||
credentials,
|
||||
}: {
|
||||
query: string;
|
||||
variables?: Record<string, any>;
|
||||
credentials?: Partial<Credentials>;
|
||||
}): Promise<{ data: T; errors?: any[] }> {
|
||||
const domain = credentials?.domain ?? currentCreds.domain;
|
||||
const token = credentials?.token ?? currentCreds.token;
|
||||
const apiVersion = SHOPIFY_API_VERSION;
|
||||
|
||||
if (!domain) {
|
||||
throw new Error(
|
||||
'[shopifyFetch] missing domain. Wrap your tree in <ShopifyProvider domain="..."> or call setShopifyCredentials() before rendering.',
|
||||
);
|
||||
}
|
||||
|
||||
const url = `https://${domain}/api/${apiVersion}/graphql.json`;
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
headers['X-Shopify-Storefront-Access-Token'] = token;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ query, variables }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`Shopify HTTP ${response.status}: ${body}`);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
if (json.errors) {
|
||||
throw new Error(`Shopify GraphQL errors: ${JSON.stringify(json.errors)}`);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
Reference in New Issue
Block a user