Please help me…
i’ve been trying to get order details information like LineItems, Total Price, Total Tax Price, …, with Admin API using Order ID which is got from Order API of Order Details in POS UI Extensions.
but i’m facing a problem that i can’t reach backend API file.
i created app/routes/api/test.jsx file for getting data from Admin API.
so, i want to access this file from POS UI Extensions, which code is written in app/extensions/order-detail-pos-ui/src/OrderModal.jsx
i’m using ngrok to make https requests.
When i send request, i can see 204 response for OPTIONS.
but it seems the GET request is not reached to server.
i attached codes.
I’m very new to Shopify app and POS UI Extensions, so i have lots of things i don’t know.
please give me your advice.
If you need more information, please let me know.
Thank you!
ーーーーーーーーーーーーー
<test.jsx>
import { authenticate } from "../shopify.server";
import { json } from "@shopify/remix-oxygen";
const ALLOWED_ORIGINS = [
"https://cdn.shopify.com",
"https://extensions.shopifycdn.com",
];
export const loader = async () => {
console.log("GET request received to api-test");
console.log("🚀 loader hit for api-test");
return Response.json(
{ ok: true },
{
headers: {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
},
);
};
<OrderModal.jsx>
import React, { useState, useEffect } from "react";
import {
Text,
Screen,
ScrollView,
useApi,
reactExtension,
Banner,
Box,
Stack,
SectionHeader,
} from "@shopify/ui-extensions-react/point-of-sale";
const Modal = () => {
const api = useApi();
const [authenticated, setAuthenticated] = useState();
const [error, setError] = useState();
const [sessionToken, setSessionToken] = useState();
const [apiResponse, setApiResponse] = useState();
useEffect(() => {
api.session.getSessionToken().then((token) => {
setSessionToken(token);
fetch(
`https://XXXXXXXXXXXXXXXXXXXXXXXXXXXX.ngrok-free.app/api/test?orderId=${api.order.id}`,
{
method: "GET",
mode: "cors",
credentials: "include",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log("API response:", data);
setApiResponse(data)
setAuthenticated(true);
})
.catch((err) => {
console.error("Fetch error:", err);
setError(err.toString());
});
});
}, []);
return (
<Screen name="API Test Results" title="API Response Details">
<ScrollView>
<Box padding="500">
<Banner
title={authenticated ? "API Request Successful" : "Processing..."}
variant={authenticated ? "success" : "info"}
visible
/>
</Box>
<Box padding="500">
<SectionHeader title="Authentication Info" />
<Text>Authentication Status: {authenticated ? "✅ Authenticated" : "⏳ Pending"}</Text>
{sessionToken && (
<Text truncate>Session Token: {sessionToken.substring(0, 15)}...</Text>
)}
</Box>
{error && (
<Box padding="500">
<SectionHeader title="Error Details" />
<Text color="critical">{error}</Text>
</Box>
)}
{apiResponse && (
<Box padding="500">
<SectionHeader title="API Response Data" />
<Stack direction="block" gap="200">
<Text>Status: {apiResponse.status || "N/A"}</Text>
<Text>Message: {apiResponse.message || "N/A"}</Text>
{/* <Text>Order ID: {apiResponse.orderId || "N/A"}</Text>
<Text>Timestamp: {apiResponse.timestamp || "N/A"}</Text> */}
</Stack>
</Box>
)}
<Box padding="500">
<SectionHeader title="Request Details" />
<Text>Order ID (from API): {api.order.id}</Text>
<Text>API Endpoint: /api/api-test</Text>
<Text>Method: GET</Text>
</Box>
</ScrollView>
</Screen>
);
};
export default reactExtension("pos.order-details.action.render", () => (
<Modal />
));
<vite.config.js>
import { vitePlugin as remix } from "@remix-run/dev";
import { installGlobals } from "@remix-run/node";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
installGlobals({ nativeFetch: true });
// Related: https://github.com/remix-run/remix/issues/2835#issuecomment-1144102176
// Replace the HOST env var with SHOPIFY_APP_URL so that it doesn't break the remix server. The CLI will eventually
// stop passing in HOST, so we can remove this workaround after the next major release.
if (
process.env.HOST &&
(!process.env.SHOPIFY_APP_URL ||
process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
process.env.SHOPIFY_APP_URL = process.env.HOST;
delete process.env.HOST;
}
const host = new URL(process.env.SHOPIFY_APP_URL || "http://localhost")
.hostname;
let hmrConfig;
if (host === "localhost") {
hmrConfig = {
protocol: "ws",
host: "localhost",
port: 64999,
clientPort: 64999,
};
} else {
hmrConfig = {
protocol: "wss",
host: host,
port: parseInt(process.env.FRONTEND_PORT) || 8002,
clientPort: 443,
};
}
export default defineConfig({
server: {
allowedHosts: [
host,
"XXXXXXXXXXXXXXXXXXXXXXXXXXXX.ngrok-free.app",
],
cors: {
origin: [
"https://extensions.shopifycdn.com",
"https://cdn.shopify.com",
],
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: ["Authorization", "Content-Type"],
credentials: true,
preflightContinue: false,
},
// cors: {
// preflightContinue: true,
// },
port: Number(process.env.PORT || 3000),
hmr: hmrConfig,
fs: {
// See https://vitejs.dev/config/server-options.html#server-fs-allow for more information
allow: ["app", "node_modules"],
},
},
plugins: [
remix({
ignoredRouteFiles: ["**/.*"],
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
v3_lazyRouteDiscovery: true,
v3_singleFetch: false,
v3_routeConfig: true,
},
}),
tsconfigPaths(),
],
build: {
assetsInlineLimit: 0,
},
optimizeDeps: {
include: ["@shopify/app-bridge-react", "@shopify/polaris"],
},
});