Skip to content

Commit 63cf031

Browse files
committed
production-ready
0 parents  commit 63cf031

File tree

77 files changed

+2772
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+2772
-0
lines changed

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Monorepo
2+
node_modules
3+
package-lock.json
4+
5+
# Client
6+
apps/client/.turbo
7+
apps/client/dist
8+
apps/client/node_modules
9+
apps/client/.env
10+
11+
# Server
12+
apps/server/.turbo
13+
apps/server/dist
14+
apps/server/node_modules
15+
apps/server/.env

apps/client/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SHOPIFY_API_KEY=
2+
SHOPIFY_APP_URL=https://ngrok.io
3+
SERVER_PORT=3000
4+
CLIENT_PORT=5173

apps/client/.eslintrc.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module.exports = {
2+
root: true,
3+
env: { browser: true, es2020: true },
4+
extends: [
5+
'eslint:recommended',
6+
'plugin:@typescript-eslint/recommended',
7+
'plugin:react-hooks/recommended',
8+
],
9+
ignorePatterns: ['dist', '.eslintrc.cjs'],
10+
parser: '@typescript-eslint/parser',
11+
plugins: ['react-refresh'],
12+
rules: {
13+
'react-refresh/only-export-components': [
14+
'warn',
15+
{ allowConstantExport: true },
16+
],
17+
},
18+
}

apps/client/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Shopify x React x Nest Boilerplate</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/Index.tsx"></script>
11+
</body>
12+
</html>

apps/client/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "client",
3+
"type": "module",
4+
"scripts": {
5+
"dev": "vite",
6+
"build": "tsc && vite build",
7+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
8+
"preview": "vite preview"
9+
},
10+
"dependencies": {
11+
"@apollo/client": "^3.8.2",
12+
"@shopify/app-bridge": "^3.7.9",
13+
"@shopify/app-bridge-react": "^3.7.9",
14+
"@shopify/app-bridge-types": "^0.0.3",
15+
"@shopify/polaris": "^11.14.0",
16+
"axios": "^1.5.0",
17+
"raviger": "^4.1.2",
18+
"react": "^18.2.0",
19+
"react-dom": "^18.2.0",
20+
"react-router-dom": "^6.15.0",
21+
"zustand": "^4.4.1"
22+
},
23+
"devDependencies": {
24+
"@types/react": "^18.2.15",
25+
"@types/react-dom": "^18.2.7",
26+
"@typescript-eslint/eslint-plugin": "^6.0.0",
27+
"@typescript-eslint/parser": "^6.0.0",
28+
"@vitejs/plugin-react-swc": "^3.3.2",
29+
"eslint": "^8.45.0",
30+
"eslint-plugin-react-hooks": "^4.6.0",
31+
"eslint-plugin-react-refresh": "^0.4.3",
32+
"typescript": "^5.0.2",
33+
"vite": "^4.4.5"
34+
}
35+
}

apps/client/src/App.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NavigationMenu } from "@shopify/app-bridge-react";
2+
import { AppProvider as PolarisProvider } from "@shopify/polaris";
3+
import "@shopify/polaris/build/esm/styles.css";
4+
import translations from "@shopify/polaris/locales/en.json";
5+
import { usePath, useRoutes } from "raviger";
6+
import routes from "./Routes";
7+
import ApolloClientProvider from "@/providers/ApolloClientProvider";
8+
import { AppBridgeProvider } from "@/providers/AppBridgeProvider";
9+
10+
const App: React.FunctionComponent = () => {
11+
const currentPath = usePath();
12+
const RouteComponents = useRoutes(routes);
13+
14+
return (
15+
<PolarisProvider i18n={translations}>
16+
<AppBridgeProvider>
17+
<NavigationMenu
18+
navigationLinks={[
19+
{
20+
label: "Fetch Data",
21+
destination: "/debug/getData",
22+
},
23+
{
24+
label: "Billing API",
25+
destination: "/debug/billing",
26+
},
27+
]}
28+
matcher={(link) => currentPath === link.destination}
29+
/>
30+
<ApolloClientProvider>{RouteComponents}</ApolloClientProvider>
31+
</AppBridgeProvider>
32+
</PolarisProvider>
33+
);
34+
};
35+
36+
export default App;

apps/client/src/Exitframe.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useAppBridge } from "@shopify/app-bridge-react";
2+
import { Redirect } from "@shopify/app-bridge/actions";
3+
import { HorizontalStack, Spinner } from "@shopify/polaris";
4+
import { useEffect } from "react";
5+
6+
const ExitFrame = () => {
7+
const app = useAppBridge();
8+
9+
useEffect(() => {
10+
const shop = new URLSearchParams(location.search).get("shop");
11+
const host = new URLSearchParams(location.search).get("host");
12+
const redirect = Redirect.create(app);
13+
redirect.dispatch(
14+
Redirect.Action.REMOTE,
15+
`${process.env.SHOPIFY_APP_URL}/auth?shop=${shop}&host=${host}`
16+
);
17+
}, []);
18+
19+
return (
20+
<HorizontalStack blockAlign="center" align="center">
21+
<Spinner />
22+
</HorizontalStack>
23+
);
24+
};
25+
26+
export default ExitFrame;

apps/client/src/Index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom/client";
3+
import App from "@/App.tsx";
4+
5+
ReactDOM.createRoot(document.getElementById("root")!).render(
6+
<React.StrictMode>
7+
<App />
8+
</React.StrictMode>
9+
);

apps/client/src/NotFound.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Card, EmptyState, Layout, Page } from "@shopify/polaris";
2+
3+
const NotFound = () => {
4+
return (
5+
<Page>
6+
<Layout>
7+
<Layout.Section fullWidth>
8+
<Card>
9+
<EmptyState
10+
heading="Page Not Found"
11+
image="https://cdn.shopify.com/s/files/1/0262/4071/2726/files/emptystate-files.png"
12+
>
13+
<p>
14+
Oops! The page you are looking for might have been removed, had
15+
its name changed, or is temporarily unavailable.
16+
</p>
17+
</EmptyState>
18+
</Card>
19+
</Layout.Section>
20+
</Layout>
21+
</Page>
22+
);
23+
};
24+
25+
export default NotFound;

apps/client/src/Routes.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import ExitFrame from "@/Exitframe";
2+
import NotFound from "@/NotFound";
3+
4+
import Index from "@/pages/Index";
5+
import DebugIndex from "@/pages/debug/DebugIndex";
6+
import ActiveWebhooks from "@/pages/debug/ActiveWebhooks";
7+
import GetData from "@/pages/debug/GetData";
8+
import BillingAPI from "@/pages/debug/BillingAPI";
9+
import DevNotes from "@/pages/debug/DevNotes";
10+
11+
const routes = {
12+
"/": () => <Index />,
13+
"/debug": () => <DebugIndex />,
14+
"/debug/activeWebhooks": () => <ActiveWebhooks />,
15+
"/debug/getData": () => <GetData />,
16+
"/debug/billing": () => <BillingAPI />,
17+
"/debug/devNotes": () => <DevNotes />,
18+
"/exitframe": () => <ExitFrame />,
19+
"*": () => <NotFound />,
20+
};
21+
22+
export default routes;

apps/client/src/hooks/useFetch.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useAppBridge } from "@shopify/app-bridge-react";
2+
import { authenticatedFetch } from "@shopify/app-bridge/utilities";
3+
import { Redirect } from "@shopify/app-bridge/actions";
4+
import { useStore } from "@/stores";
5+
6+
function useFetch() {
7+
const app = useAppBridge();
8+
const fetchFunction = authenticatedFetch(app);
9+
const embedded = useStore((state) => state.embedded);
10+
const shop = useStore((state) => state.shop);
11+
const host = useStore((state) => state.host);
12+
13+
return async (uri: string, options?: RequestInit | undefined) => {
14+
const response = await fetchFunction(
15+
`https://${process.env.SHOPIFY_APP_ORIGIN}${uri}?embedded=${embedded}&shop=${shop}&host=${host}`,
16+
options
17+
);
18+
19+
if (
20+
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
21+
) {
22+
const authUrlHeader = response.headers.get(
23+
"X-Shopify-API-Request-Failure-Reauthorize-Url"
24+
);
25+
26+
const redirect = Redirect.create(app);
27+
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/exitframe`);
28+
return null;
29+
}
30+
31+
return response;
32+
};
33+
}
34+
35+
export default useFetch;

0 commit comments

Comments
 (0)