diff --git a/SETUP.md b/SETUP.md
index fffbdcc..6975699 100644
--- a/SETUP.md
+++ b/SETUP.md
@@ -1,8 +1,11 @@
Shopify App Setup
===
-To install the Shopify app you'll first need to create an API access token. Head over to your Shopify store and login
-to the store admin.
+Follow these steps to install and configure the Shopify app using either an Access Token or OAuth credentials.
+
+## Using Access Token
+
+Head over to your Shopify store and login to the store admin.
From the store admin dashboard, navigate to the "Settings" page using the button in the bottom left.
@@ -80,3 +83,39 @@ To configure who can see and use the Shopify app, head to the "Permissions" tab
you'd like to have access.
When you're happy, click "Install".
+
+## Using OAuth
+
+Go to your Shopify Partner account and navigate to the Apps section in the sidebar. On the "Apps" page, click "Create app".
+
+[](/docs/assets/setup/shopify-setup-oauth-01.png)
+
+On the "Create a new app" page click "Create app manually" under "Use Shopify Partners", give your app a name (e.g. Deskpro) and click "Create".
+
+[](/docs/assets/setup/shopify-setup-oauth-02.png)
+
+Once the app is created, you'll see a screen with its details. Copy the `Client ID` and `Client Secret`, then enter them in the Settings tab in Deskpro.
+
+[](/docs/assets/setup/shopify-setup-oauth-05.png)
+
+Next, click "Choose distribution" on the same page to make your app live.
+
+Select a distribution method. Public apps require approval from Shopify, while Custom apps are intended for use on a single store without requiring approval. This guide follows the Custom app setup.
+
+[](/docs/assets/setup/shopify-setup-oauth-03.png)
+
+Enter your store url and click "Generate Link"
+
+
+Now, click Configuration in the sidebar. Enter the callback URL from the Settings tab in Deskpro into the "Allowed redirection URL(s)" field. Set "Embed app in Shopify admin" to `false`.
+
+[](/docs/assets/setup/shopify-setup-oauth-04.png)
+
+
+Once you've made these changes, click "Save and release".
+
+To configure who can see and use the Shopify app, head to the "Permissions" tab and select those users and/or groups
+you'd like to have access.
+
+When you're happy, click "Install".
+
diff --git a/docs/assets/setup/shopify-setup-oauth-01.png b/docs/assets/setup/shopify-setup-oauth-01.png
new file mode 100644
index 0000000..4dddda3
Binary files /dev/null and b/docs/assets/setup/shopify-setup-oauth-01.png differ
diff --git a/docs/assets/setup/shopify-setup-oauth-02.png b/docs/assets/setup/shopify-setup-oauth-02.png
new file mode 100644
index 0000000..53584e1
Binary files /dev/null and b/docs/assets/setup/shopify-setup-oauth-02.png differ
diff --git a/docs/assets/setup/shopify-setup-oauth-03.png b/docs/assets/setup/shopify-setup-oauth-03.png
new file mode 100644
index 0000000..f6d528c
Binary files /dev/null and b/docs/assets/setup/shopify-setup-oauth-03.png differ
diff --git a/docs/assets/setup/shopify-setup-oauth-04.png b/docs/assets/setup/shopify-setup-oauth-04.png
new file mode 100644
index 0000000..efc3e6e
Binary files /dev/null and b/docs/assets/setup/shopify-setup-oauth-04.png differ
diff --git a/docs/assets/setup/shopify-setup-oauth-05.png b/docs/assets/setup/shopify-setup-oauth-05.png
new file mode 100644
index 0000000..0004db9
Binary files /dev/null and b/docs/assets/setup/shopify-setup-oauth-05.png differ
diff --git a/manifest.json b/manifest.json
index c4aca8f..b0b2d30 100644
--- a/manifest.json
+++ b/manifest.json
@@ -8,9 +8,16 @@
"isSingleInstall": false,
"hasDevMode": true,
"serveUrl": "https://apps-cdn.deskpro-service.com/__name__/__version__",
+ "secrets": "hy5diyldVvLk+hwqrKPn3hy82isOK9JpbvD18IfoofGwpo1nNTup39yNWkuVIPODUrTIfuoY4nAwi+/QcTVK/7uecibF8+FY5Ibypyr/dwwDldkPB5CjyxPpW54fdUhjGfDRiwKvvvphqE+zpi57x49wPrANgZblWoaDvmYhvlObKIW7mTRO280EOzPactM4CHonN70pBydYkC2CAT3GeSGplmCTbS9vo1Hfgmz4SL2KsepeePVXnHaa0XIMreiUvCx05l7ozJvfZlxwClocy6k5WX2HHGF6PNxo/haXXlLCYpHiARIQ7F7+0UhSC9jqAxS77+LCORJXgM89+hew/pNQktTEQMLrr9i8Gy2s7G9DRSH96npgu+EeIkvHk1vn1hxJUwZgi+WLNn1lWGhmtXs+eLIFII/PMelE7EdphsTrwhf06JvMrNGys7ZImnA46RaacLa43I8JGyktD9Ll2l7Fvz5DoG7SIGO25DfA5pDivHa7gax/7ymm+5N5ltBTPUpBTi0Sigq1WCUBR9/bv+Jdp5QmbkBBr9EQnDARZrHo3DcClPC+CTQ4qofXql8B/5GwxqXuqVwoPo2DUN7lJWUx6p3TCYLZY7lvvHFm8HMsq/WJdMFasHa5+MphTBxtSVF4+vpSILBjQZj+7B2mEc+rqNYikufZHIP0aYYfmETByvD8sAxHz5XgGKA72HBfSHrPSQyNRUeZFcfq05QJx8fpa/ZzbhByiazQB3AmQ1rKhkq26SPmxojKmDcLBYaIwMmaplZEc5qTszIcRTZxOUn2WMlsovjOjW5C2LlFhYtqCoOhQR9Wu7bLoH85J7yleEXNQLfR28C3S0xPCbbdp0DRRdPOx8jT0qTXHrDuJJIDlkWf9iJJnE9ygTdWVczVm8PVK3duIqgzsfD5SrJyyfIxwQSv+wd9cNX9PbQ345iT2SGPvWRomBStTn9q8azA1ejsnVG7kIrNujSKIrp+9bSzGkkWcrFragaw2loyoyQs8OGrDVEbXdVdwyHxK0GlP7Gi17hplNx6a/ZrEyhsrdcqJBK+rR7uYM3mKcQniZJAUc+4fhANjGqUEYxNJ2ZKq79stOxU+QV+tXfB/36HaVJOIfLy2Z2K85c+0URADXzFS8mH9hjyLIUZuviMJHQtpngI6zwnZoXfK0eMxrOU+kC0+cQKD4r1niQGc1rYg6mFNKlq4E+0esEUgxJItKbMxPhQ4D+ZcrliokUfAlr0k6qThzWg3MT26mUlpn8Z1liwg3ojogBMogYsmfqB74JCulEgdN2EmpdsAU9lm9cmvJWV8eaHG2mLFxiux2RLQ+JYFmVjXCKW1y+xgfINublCW53QG28S/DmnKGI5DZim5+jqEkv5kTUXdL7XtCkxjm4heGWGhtX2/Or6XLBDOtR+zmc/OlD3NkcJeyyG7fy3672flgTbv1WzkUKgcX3+dU0/HjsAftX0lFbKrr39zuIavogTIcmBHuVytKicfeLFCqvTneoGpXZqvkVhEaY/2qFbXpCc5Akw1oiMz1iifJ1G",
"targets": [
- { "target": "ticket_sidebar", "entrypoint": "index.html" },
- { "target": "user_sidebar", "entrypoint": "index.html" }
+ {
+ "target": "ticket_sidebar",
+ "entrypoint": "index.html"
+ },
+ {
+ "target": "user_sidebar",
+ "entrypoint": "index.html"
+ }
],
"entityAssociations": {
"linkedShopifyCustomers": {
@@ -20,38 +27,100 @@
}
},
"settings": {
+ "use_advanced_connect": {
+ "title": "Advanced Connect",
+ "description": "Follow the setup guide and use your credentials to connect the app to Deskpro.",
+ "type": "boolean",
+ "default": false,
+ "isRequired": false,
+ "isBackendOnly": false,
+ "order": 5
+ },
+ "use_access_token": {
+ "title": "Use Access Token",
+ "type": "boolean",
+ "isRequired": false,
+ "isBackendOnly": false,
+ "default": false,
+ "condition": "settings.use_advanced_connect != false",
+ "order": 10
+ },
"shop_name": {
"title": "Shop name",
"description": "Your Shop name",
"type": "string",
"isRequired": true,
"isBackendOnly": false,
- "order": 10
+ "order": 20
},
"access_token": {
"title": "Access Token",
"description": "Your shopify access token, please follow the app setup guide to get this",
"type": "string",
- "isRequired": true,
+ "isRequired": false,
"isBackendOnly": true,
- "order": 20
+ "condition": "settings.use_advanced_connect != false && settings.use_access_token == true",
+ "order": 30
},
"verify_settings": {
"title": "",
"type": "app_embedded",
- "options": { "entrypoint": "#/admin/verify_settings" },
+ "options": {
+ "entrypoint": "#/admin/verify_settings",
+ "height": "30px"
+ },
"isRequired": false,
+ "condition": "settings.use_advanced_connect != false && settings.use_access_token == true",
"isBackendOnly": true,
- "order": 30
+ "order": 40
+ },
+ "client_id": {
+ "title": "Client ID",
+ "type": "string",
+ "isRequired": false,
+ "isBackendOnly": false,
+ "condition": "settings.use_advanced_connect != false && settings.use_access_token != true",
+ "order": 50
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "type": "string",
+ "isRequired": false,
+ "isBackendOnly": true,
+ "condition": "settings.use_advanced_connect != false && settings.use_access_token != true",
+ "order": 60
+ },
+ "callback_url": {
+ "title": "Callback URL",
+ "type": "app_embedded",
+ "options": {
+ "entrypoint": "#/admin/callback"
+ },
+ "isRequired": false,
+ "isBackendOnly": true,
+ "condition": "settings.use_advanced_connect != false && settings.use_access_token != true",
+ "order": 70
}
},
"proxy": {
"whitelist": [
{
"url": "https://(.*).myshopify.com/admin/api/.*",
- "methods": ["GET", "POST", "PUT", "DELETE"],
+ "methods": [
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE"
+ ],
+ "timeout": 30
+ },
+ {
+ "url": "https://(.*).myshopify.com/admin/oauth/.*",
+ "methods": [
+ "POST"
+ ],
"timeout": 30
}
]
}
-}
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index b40a412..3263dce 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"bumpManifestVer": "node ./bin/bumpManifestVer.js"
},
"dependencies": {
- "@deskpro/app-sdk": "^5.1.1",
+ "@deskpro/app-sdk": "^6.0.3",
"@deskpro/deskpro-ui": "^8.2.0",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@heroicons/react": "1.0.6",
@@ -74,6 +74,6 @@
"slugify": "^1.6.6",
"ts-jest": "^29.2.5",
"typescript": "^5.7.2",
- "vite": "^6.0.11"
+ "vite": "^6.2.2"
}
-}
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1b3afa4..db8dbbc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@deskpro/app-sdk':
- specifier: ^5.1.1
- version: 5.1.1(@deskpro/deskpro-ui@8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
+ specifier: ^6.0.3
+ version: 6.0.3(@deskpro/deskpro-ui@8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@deskpro/deskpro-ui':
specifier: ^8.2.0
version: 8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -181,8 +181,8 @@ importers:
specifier: ^5.7.2
version: 5.7.2
vite:
- specifier: ^6.0.11
- version: 6.0.11(@types/node@22.10.5)(jiti@2.4.2)(sass@1.83.1)(yaml@2.7.0)
+ specifier: ^6.2.2
+ version: 6.2.2(@types/node@22.10.5)(jiti@2.4.2)(sass@1.84.0)(yaml@2.7.0)
packages:
@@ -560,8 +560,8 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
- '@deskpro/app-sdk@5.1.1':
- resolution: {integrity: sha512-GVwjVb/8EX4aBHtDu7YqWKpVPxslyFy0nAEjbo59hycy73KPqV0UdOCQNM5LasZnrC1U6epV/BYVQrUDplTzuw==}
+ '@deskpro/app-sdk@6.0.3':
+ resolution: {integrity: sha512-7IYRxJ6SRCKrsSFO5bZwGYhRoEvX08LPv0pDGIxhNejrqp+eaDb8hjobKnPKwK22pRNJ/riExGPsbGXeSuuCZQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
'@deskpro/deskpro-ui': ^8.0.0
@@ -602,152 +602,152 @@ packages:
resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==}
engines: {node: '>=18.0.0'}
- '@esbuild/aix-ppc64@0.24.2':
- resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
+ '@esbuild/aix-ppc64@0.25.1':
+ resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.24.2':
- resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
+ '@esbuild/android-arm64@0.25.1':
+ resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.24.2':
- resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
+ '@esbuild/android-arm@0.25.1':
+ resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.24.2':
- resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
+ '@esbuild/android-x64@0.25.1':
+ resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.24.2':
- resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
+ '@esbuild/darwin-arm64@0.25.1':
+ resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.24.2':
- resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
+ '@esbuild/darwin-x64@0.25.1':
+ resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.24.2':
- resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
+ '@esbuild/freebsd-arm64@0.25.1':
+ resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.24.2':
- resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
+ '@esbuild/freebsd-x64@0.25.1':
+ resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.24.2':
- resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
+ '@esbuild/linux-arm64@0.25.1':
+ resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.24.2':
- resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
+ '@esbuild/linux-arm@0.25.1':
+ resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.24.2':
- resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
+ '@esbuild/linux-ia32@0.25.1':
+ resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.24.2':
- resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
+ '@esbuild/linux-loong64@0.25.1':
+ resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.24.2':
- resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
+ '@esbuild/linux-mips64el@0.25.1':
+ resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.24.2':
- resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
+ '@esbuild/linux-ppc64@0.25.1':
+ resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.24.2':
- resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
+ '@esbuild/linux-riscv64@0.25.1':
+ resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.24.2':
- resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
+ '@esbuild/linux-s390x@0.25.1':
+ resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.24.2':
- resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
+ '@esbuild/linux-x64@0.25.1':
+ resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/netbsd-arm64@0.24.2':
- resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
+ '@esbuild/netbsd-arm64@0.25.1':
+ resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.24.2':
- resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
+ '@esbuild/netbsd-x64@0.25.1':
+ resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.24.2':
- resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
+ '@esbuild/openbsd-arm64@0.25.1':
+ resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.24.2':
- resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
+ '@esbuild/openbsd-x64@0.25.1':
+ resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/sunos-x64@0.24.2':
- resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
+ '@esbuild/sunos-x64@0.25.1':
+ resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.24.2':
- resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
+ '@esbuild/win32-arm64@0.25.1':
+ resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.24.2':
- resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
+ '@esbuild/win32-ia32@0.25.1':
+ resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.24.2':
- resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
+ '@esbuild/win32-x64@0.25.1':
+ resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -2254,8 +2254,8 @@ packages:
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
engines: {node: '>= 0.4'}
- esbuild@0.24.2:
- resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
+ esbuild@0.25.1:
+ resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==}
engines: {node: '>=18'}
hasBin: true
@@ -3096,8 +3096,8 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
- libphonenumber-js@1.11.17:
- resolution: {integrity: sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==}
+ libphonenumber-js@1.11.19:
+ resolution: {integrity: sha512-bW/Yp/9dod6fmyR+XqSUL1N5JE7QRxQ3KrBIbYS1FTv32e5i3SEtQVX+71CYNv8maWNSOgnlCoNp9X78f/cKiA==}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -3455,8 +3455,8 @@ packages:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
- postcss@8.4.49:
- resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -3719,8 +3719,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
- sass@1.83.1:
- resolution: {integrity: sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==}
+ sass@1.84.0:
+ resolution: {integrity: sha512-XDAbhEPJRxi7H0SxrnOpiXFQoUJHwkR2u3Zc4el+fK/Tt5Hpzw5kkQ59qVDfvdaUq6gCrEZIbySFBM2T9DNKHg==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -4140,8 +4140,8 @@ packages:
resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==}
engines: {node: '>=12'}
- vite@6.0.11:
- resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==}
+ vite@6.2.2:
+ resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
@@ -4774,7 +4774,7 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
- '@deskpro/app-sdk@5.1.1(@deskpro/deskpro-ui@8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
+ '@deskpro/app-sdk@6.0.3(@deskpro/deskpro-ui@8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@deskpro/deskpro-ui': 8.2.0(@types/web@0.0.86)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
'@fortawesome/fontawesome-svg-core': 6.7.2
@@ -4790,7 +4790,7 @@ snapshots:
fuse.js: 7.0.0
handlebars: 4.7.7
i18n-iso-countries: 7.13.0
- libphonenumber-js: 1.11.17
+ libphonenumber-js: 1.11.19
modern-normalize: 1.1.0
penpal: 6.2.1
react: 18.3.1
@@ -4799,7 +4799,7 @@ snapshots:
react-flatpickr: 3.10.7(react@18.3.1)
react-intl: 5.25.1(react@18.3.1)(typescript@5.7.2)
regenerator-runtime: 0.14.1
- sass: 1.83.1
+ sass: 1.84.0
shortcut-buttons-flatpickr: 0.4.0
styled-components: 6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ts-pattern: 4.3.0
@@ -4863,79 +4863,79 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@esbuild/aix-ppc64@0.24.2':
+ '@esbuild/aix-ppc64@0.25.1':
optional: true
- '@esbuild/android-arm64@0.24.2':
+ '@esbuild/android-arm64@0.25.1':
optional: true
- '@esbuild/android-arm@0.24.2':
+ '@esbuild/android-arm@0.25.1':
optional: true
- '@esbuild/android-x64@0.24.2':
+ '@esbuild/android-x64@0.25.1':
optional: true
- '@esbuild/darwin-arm64@0.24.2':
+ '@esbuild/darwin-arm64@0.25.1':
optional: true
- '@esbuild/darwin-x64@0.24.2':
+ '@esbuild/darwin-x64@0.25.1':
optional: true
- '@esbuild/freebsd-arm64@0.24.2':
+ '@esbuild/freebsd-arm64@0.25.1':
optional: true
- '@esbuild/freebsd-x64@0.24.2':
+ '@esbuild/freebsd-x64@0.25.1':
optional: true
- '@esbuild/linux-arm64@0.24.2':
+ '@esbuild/linux-arm64@0.25.1':
optional: true
- '@esbuild/linux-arm@0.24.2':
+ '@esbuild/linux-arm@0.25.1':
optional: true
- '@esbuild/linux-ia32@0.24.2':
+ '@esbuild/linux-ia32@0.25.1':
optional: true
- '@esbuild/linux-loong64@0.24.2':
+ '@esbuild/linux-loong64@0.25.1':
optional: true
- '@esbuild/linux-mips64el@0.24.2':
+ '@esbuild/linux-mips64el@0.25.1':
optional: true
- '@esbuild/linux-ppc64@0.24.2':
+ '@esbuild/linux-ppc64@0.25.1':
optional: true
- '@esbuild/linux-riscv64@0.24.2':
+ '@esbuild/linux-riscv64@0.25.1':
optional: true
- '@esbuild/linux-s390x@0.24.2':
+ '@esbuild/linux-s390x@0.25.1':
optional: true
- '@esbuild/linux-x64@0.24.2':
+ '@esbuild/linux-x64@0.25.1':
optional: true
- '@esbuild/netbsd-arm64@0.24.2':
+ '@esbuild/netbsd-arm64@0.25.1':
optional: true
- '@esbuild/netbsd-x64@0.24.2':
+ '@esbuild/netbsd-x64@0.25.1':
optional: true
- '@esbuild/openbsd-arm64@0.24.2':
+ '@esbuild/openbsd-arm64@0.25.1':
optional: true
- '@esbuild/openbsd-x64@0.24.2':
+ '@esbuild/openbsd-x64@0.25.1':
optional: true
- '@esbuild/sunos-x64@0.24.2':
+ '@esbuild/sunos-x64@0.25.1':
optional: true
- '@esbuild/win32-arm64@0.24.2':
+ '@esbuild/win32-arm64@0.25.1':
optional: true
- '@esbuild/win32-ia32@0.24.2':
+ '@esbuild/win32-ia32@0.25.1':
optional: true
- '@esbuild/win32-x64@0.24.2':
+ '@esbuild/win32-x64@0.25.1':
optional: true
'@eslint-community/eslint-utils@4.4.1(eslint@8.4.1)':
@@ -6829,33 +6829,33 @@ snapshots:
dependencies:
es-errors: 1.3.0
- esbuild@0.24.2:
+ esbuild@0.25.1:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.24.2
- '@esbuild/android-arm': 0.24.2
- '@esbuild/android-arm64': 0.24.2
- '@esbuild/android-x64': 0.24.2
- '@esbuild/darwin-arm64': 0.24.2
- '@esbuild/darwin-x64': 0.24.2
- '@esbuild/freebsd-arm64': 0.24.2
- '@esbuild/freebsd-x64': 0.24.2
- '@esbuild/linux-arm': 0.24.2
- '@esbuild/linux-arm64': 0.24.2
- '@esbuild/linux-ia32': 0.24.2
- '@esbuild/linux-loong64': 0.24.2
- '@esbuild/linux-mips64el': 0.24.2
- '@esbuild/linux-ppc64': 0.24.2
- '@esbuild/linux-riscv64': 0.24.2
- '@esbuild/linux-s390x': 0.24.2
- '@esbuild/linux-x64': 0.24.2
- '@esbuild/netbsd-arm64': 0.24.2
- '@esbuild/netbsd-x64': 0.24.2
- '@esbuild/openbsd-arm64': 0.24.2
- '@esbuild/openbsd-x64': 0.24.2
- '@esbuild/sunos-x64': 0.24.2
- '@esbuild/win32-arm64': 0.24.2
- '@esbuild/win32-ia32': 0.24.2
- '@esbuild/win32-x64': 0.24.2
+ '@esbuild/aix-ppc64': 0.25.1
+ '@esbuild/android-arm': 0.25.1
+ '@esbuild/android-arm64': 0.25.1
+ '@esbuild/android-x64': 0.25.1
+ '@esbuild/darwin-arm64': 0.25.1
+ '@esbuild/darwin-x64': 0.25.1
+ '@esbuild/freebsd-arm64': 0.25.1
+ '@esbuild/freebsd-x64': 0.25.1
+ '@esbuild/linux-arm': 0.25.1
+ '@esbuild/linux-arm64': 0.25.1
+ '@esbuild/linux-ia32': 0.25.1
+ '@esbuild/linux-loong64': 0.25.1
+ '@esbuild/linux-mips64el': 0.25.1
+ '@esbuild/linux-ppc64': 0.25.1
+ '@esbuild/linux-riscv64': 0.25.1
+ '@esbuild/linux-s390x': 0.25.1
+ '@esbuild/linux-x64': 0.25.1
+ '@esbuild/netbsd-arm64': 0.25.1
+ '@esbuild/netbsd-x64': 0.25.1
+ '@esbuild/openbsd-arm64': 0.25.1
+ '@esbuild/openbsd-x64': 0.25.1
+ '@esbuild/sunos-x64': 0.25.1
+ '@esbuild/win32-arm64': 0.25.1
+ '@esbuild/win32-ia32': 0.25.1
+ '@esbuild/win32-x64': 0.25.1
escalade@3.2.0: {}
@@ -7984,7 +7984,7 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
- libphonenumber-js@1.11.17: {}
+ libphonenumber-js@1.11.19: {}
lines-and-columns@1.2.4: {}
@@ -8312,7 +8312,7 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
- postcss@8.4.49:
+ postcss@8.5.3:
dependencies:
nanoid: 3.3.8
picocolors: 1.1.1
@@ -8596,7 +8596,7 @@ snapshots:
safer-buffer@2.1.2: {}
- sass@1.83.1:
+ sass@1.84.0:
dependencies:
chokidar: 4.0.3
immutable: 5.0.3
@@ -8997,16 +8997,16 @@ snapshots:
value-or-promise@1.0.12: {}
- vite@6.0.11(@types/node@22.10.5)(jiti@2.4.2)(sass@1.83.1)(yaml@2.7.0):
+ vite@6.2.2(@types/node@22.10.5)(jiti@2.4.2)(sass@1.84.0)(yaml@2.7.0):
dependencies:
- esbuild: 0.24.2
- postcss: 8.4.49
+ esbuild: 0.25.1
+ postcss: 8.5.3
rollup: 4.31.0
optionalDependencies:
'@types/node': 22.10.5
fsevents: 2.3.3
jiti: 2.4.2
- sass: 1.83.1
+ sass: 1.84.0
yaml: 2.7.0
w3c-xmlserializer@4.0.0:
diff --git a/src/App.tsx b/src/App.tsx
index 136cfd4..43ad3f9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,24 +1,11 @@
+import { AdminCallbackPage, EditCustomerPage, EditOrderPage, HomePage, LinkCustomerPage, ListOrdersPage, LoadingAppPage, LoginPage, LogoutPage, VerifySettings, ViewCustomerPage, ViewOrderPage } from "./pages";
+import { isNavigatePayload } from "./utils";
+import { match } from "ts-pattern";
import { Routes, Route, useNavigate } from "react-router-dom";
import { useDebouncedCallback } from "use-debounce";
-import { match } from "ts-pattern";
-import {
- useDeskproAppClient,
- useDeskproAppEvents,
-} from "@deskpro/app-sdk";
-import { isNavigatePayload } from "./utils";
-import {
- HomePage,
- ViewOrderPage,
- EditOrderPage,
- LoadingAppPage,
- ListOrdersPage,
- VerifySettings,
- EditCustomerPage,
- ViewCustomerPage,
- LinkCustomerPage,
-} from "./pages";
-import type { FC } from "react";
+import { useDeskproAppClient, useDeskproAppEvents } from "@deskpro/app-sdk";
import type { EventPayload } from "./types";
+import type { FC } from "react";
const App: FC = () => {
const navigate = useNavigate();
@@ -41,15 +28,18 @@ const App: FC = () => {
return (
- } />
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
);
}
diff --git a/src/constants.ts b/src/constants.ts
index c2f1ccb..be06a7a 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -8,6 +8,9 @@ export const ENTITY = 'linkedShopifyCustomers';
export const DEFAULT_ERROR = "There was an error!";
+export const OAUTH2_ACCESS_TOKEN_PATH = "oauth2/access_token"
+export const OAUTH2_REFRESH_TOKEN_PATH = "oauth2/refresh_token"
+
export const placeholders = {
SHOP_NAME: "__shop_name__",
ACCESS_TOKEN: "__access_token__",
diff --git a/src/pages/AdminCallbackPage/AdminCallbackPage.tsx b/src/pages/AdminCallbackPage/AdminCallbackPage.tsx
new file mode 100644
index 0000000..02cea1c
--- /dev/null
+++ b/src/pages/AdminCallbackPage/AdminCallbackPage.tsx
@@ -0,0 +1,53 @@
+import { CopyToClipboardInput, LoadingSpinner, OAuth2Result, useDeskproLatestAppContext, useInitialisedDeskproAppClient, } from "@deskpro/app-sdk";
+import { createSearchParams } from "react-router-dom";
+import { P1 } from "@deskpro/deskpro-ui";
+import { Settings } from "@/types";
+import { useState } from "react";
+import styled from "styled-components";
+import type { FC } from "react";
+
+const Description = styled(P1)`
+ margin-top: 8px;
+ margin-bottom: 16px;
+ color: ${({ theme }) => theme.colors.grey80};
+`;
+
+const AdminCallbackPage: FC = () => {
+ const [callbackUrl, setCallbackUrl] = useState(null)
+ const {context} = useDeskproLatestAppContext()
+
+ useInitialisedDeskproAppClient(async (client) => {
+ const oauth2 = await client.startOauth2Local(
+ ({ callbackUrl, state }) => {
+ return `https://${context?.settings.shop_name}.myshopify.com/admin/oauth2/authorize?${createSearchParams([
+ ["response_type", "code"],
+ ["client_id", "xxx"],
+ ["state", state],
+ ["redirect_uri", callbackUrl],
+ ])}`
+ },
+ /code=(?[0-9a-f]+)/,
+ async (): Promise => ({ data: { access_token: "", refresh_token: "" } })
+ )
+
+ const url = new URL(oauth2.authorizationUrl);
+ const redirectUri = url.searchParams.get("redirect_uri")
+
+ if (redirectUri) {
+ setCallbackUrl(redirectUri)
+ }
+ })
+
+ if (!callbackUrl) {
+ return ()
+ }
+
+ return (
+ <>
+
+ The callback URL will be required during the Shopify app setup
+ >
+ );
+};
+
+export { AdminCallbackPage };
diff --git a/src/pages/AdminCallbackPage/index.ts b/src/pages/AdminCallbackPage/index.ts
new file mode 100644
index 0000000..2f4f193
--- /dev/null
+++ b/src/pages/AdminCallbackPage/index.ts
@@ -0,0 +1 @@
+export { AdminCallbackPage } from "./AdminCallbackPage";
diff --git a/src/pages/LinkCustomerPage/LinkCustomerPage.tsx b/src/pages/LinkCustomerPage/LinkCustomerPage.tsx
index 0eff23f..314bbbd 100644
--- a/src/pages/LinkCustomerPage/LinkCustomerPage.tsx
+++ b/src/pages/LinkCustomerPage/LinkCustomerPage.tsx
@@ -1,5 +1,4 @@
-import {useMemo, useState, useEffect, useCallback} from "react";
-import get from "lodash/get";
+import { useState, useEffect, useCallback} from "react";
import isEmpty from "lodash/isEmpty";
import { useDebouncedCallback } from "use-debounce";
import { useNavigate, useSearchParams } from "react-router-dom";
@@ -17,25 +16,25 @@ import { useSearch } from "./hooks";
import { LinkCustomer } from "../../components";
import type { FC, ChangeEvent } from "react";
import type { CustomerType } from "../../services/shopify/types";
+import { ContextData, Settings } from "@/types";
const LinkCustomerPage: FC = () => {
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const { client } = useDeskproAppClient();
- const { context } = useDeskproLatestAppContext();
- const [search, setSearch] = useState("");
- const [selectedCustomerId, setSelectedCustomerId] = useState("");
const [isEditMode, setIsEditMode] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const [search, setSearch] = useState("");
+ const [searchParams] = useSearchParams();
+ const [selectedCustomerId, setSelectedCustomerId] = useState("");
+ const { client } = useDeskproAppClient();
+ const { context } = useDeskproLatestAppContext();
const { isLoading, customers } = useSearch(search);
- const customerId = useMemo(() => searchParams.get("customerId"), [searchParams]);
- const dpUser = useMemo(() => {
- return get(context, ["data", "ticket", "primaryUser"])
- || get(context, ["data", "user"])
- }, [context]);
+ const customerId = searchParams.get("customerId")
+ const dpUser = context?.data?.ticket?.primaryUser || context?.data?.user
+ const navigate = useNavigate();
useSetTitle("Link Customer");
+ const isUsingOAuth = context?.settings?.use_access_token !== true || context.settings.use_advanced_connect === false
+
useRegisterElements(({ registerElement }) => {
registerElement("refresh", { type: "refresh_button" });
@@ -45,6 +44,19 @@ const LinkCustomerPage: FC = () => {
payload: { type: "changePage", path: "/home" }
});
}
+
+ if (isUsingOAuth) {
+ registerElement("menu", {
+ type: "menu",
+ items: [{
+ title: "Logout",
+ payload: {
+ type: "changePage",
+ path: `/logout`,
+ },
+ }],
+ });
+ }
}, [isEditMode]);
useEffect(() => {
diff --git a/src/pages/LoadingAppPage/LoadingAppPage.tsx b/src/pages/LoadingAppPage/LoadingAppPage.tsx
index 610146f..a17921f 100644
--- a/src/pages/LoadingAppPage/LoadingAppPage.tsx
+++ b/src/pages/LoadingAppPage/LoadingAppPage.tsx
@@ -1,22 +1,85 @@
-import { FC } from "react";
-import { LoadingSpinner } from "@deskpro/app-sdk";
+import { ErrorBlock } from "@/components/common";
+import { FC, useState } from "react";
+import { getEntityCustomerList } from "@/services/deskpro";
+import { getShopInfo } from "@/services/shopify";
+import { LoadingSpinner, useDeskproAppClient, useDeskproElements, useDeskproLatestAppContext, useInitialisedDeskproAppClient } from "@deskpro/app-sdk";
+import { Settings, ContextData } from "@/types";
+import { Stack } from "@deskpro/deskpro-ui";
import { useNavigate } from "react-router-dom";
-import { useTryToLinkCustomer, useRegisterElements } from "../../hooks";
const LoadingAppPage: FC = () => {
+ const { client } = useDeskproAppClient()
+ const { context } = useDeskproLatestAppContext()
+
+ const [isAuthenticated, setIsAuthenticated] = useState(false)
+ const [isFetchingAuth, setIsFetchingAuth] = useState(true)
+
const navigate = useNavigate();
- useRegisterElements(({ registerElement }) => {
- registerElement("refresh", { type: "refresh_button" });
+ // Determine authentication method from settings
+ const isUsingOAuth = context?.settings?.use_access_token !== true || context.settings.use_advanced_connect === false
+ const user = context?.data?.ticket?.primaryUser || context?.data?.user
+
+ useDeskproElements(({ registerElement, clearElements }) => {
+ clearElements()
+ registerElement("refresh", { type: "refresh_button" })
});
- useTryToLinkCustomer(
- () => navigate("/home"),
- () => navigate("/link_customer"),
- );
+ useInitialisedDeskproAppClient((client) => {
+ client.setTitle("Shopify")
+
+ if (!context || !context?.settings || !user) {
+ return
+ }
+
+ // Store the authentication method in the user state
+ client.setUserState("isUsingOAuth", isUsingOAuth)
+
+ // Verify authentication status
+ // If OAuth2 mode and the user is logged in the request would be make with their stored access token
+ // If access token mode the request would be made with the access token provided in the app setup
+ getShopInfo(client)
+ .then((result) => {
+ if (result?.data?.shop) {
+ setIsAuthenticated(true)
+ }
+ })
+ .catch(() => { })
+ .finally(() => {
+ setIsFetchingAuth(false)
+ })
+ }, [context, context?.settings])
+
+ if (!client || !user || isFetchingAuth) {
+ return ()
+ }
+ if (isAuthenticated) {
+
+ getEntityCustomerList(client, user.id)
+ .then((customers) => {
+ customers.length < 1 ? navigate("/link_customer") :
+ navigate("/home")
+ })
+ .catch(() => {})
+ } else {
+
+ if (isUsingOAuth) {
+ navigate("/login")
+ } else {
+ // Show error for invalid access tokens (expired or not present)
+ return (
+
+
+
+ )
+ }
+
+ }
+
+
return (
-
+
);
};
diff --git a/src/pages/Login/LoginPage.tsx b/src/pages/Login/LoginPage.tsx
new file mode 100644
index 0000000..a33f62d
--- /dev/null
+++ b/src/pages/Login/LoginPage.tsx
@@ -0,0 +1,36 @@
+import { AnchorButton, H3, Stack } from "@deskpro/deskpro-ui"
+import { ErrorBlock } from "@/components/common"
+import { FC } from "react"
+import { useDeskproElements, useInitialisedDeskproAppClient } from "@deskpro/app-sdk"
+import useLogin from "./useLogin"
+
+const LoginPage: FC = () => {
+ useDeskproElements(({ registerElement, clearElements }) => {
+ clearElements()
+ registerElement("refresh", { type: "refresh_button" })
+ })
+
+ useInitialisedDeskproAppClient((client)=>{
+ client.setTitle("Login")
+ }, [])
+
+ const { onSignIn, authUrl, isLoading, error } = useLogin();
+
+ return (
+
+ Log into your Shopify account.
+
+
+ {error && }
+
+ )
+}
+
+export default LoginPage
\ No newline at end of file
diff --git a/src/pages/Login/index.ts b/src/pages/Login/index.ts
new file mode 100644
index 0000000..f815230
--- /dev/null
+++ b/src/pages/Login/index.ts
@@ -0,0 +1 @@
+export { default } from "./LoginPage";
diff --git a/src/pages/Login/useLogin.ts b/src/pages/Login/useLogin.ts
new file mode 100644
index 0000000..7cc2155
--- /dev/null
+++ b/src/pages/Login/useLogin.ts
@@ -0,0 +1,135 @@
+import { ContextData, Settings } from "@/types";
+import { createSearchParams, useNavigate } from "react-router-dom";
+import { getAccessToken, getShopInfo } from "@/services/shopify";
+import { getEntityCustomerList } from "@/services/deskpro";
+import { IOAuth2, OAuth2Result, useDeskproLatestAppContext, useInitialisedDeskproAppClient } from "@deskpro/app-sdk";
+import { OAUTH2_ACCESS_TOKEN_PATH, OAUTH2_REFRESH_TOKEN_PATH } from "@/constants";
+import { useCallback, useState } from "react";
+
+interface UseLogin {
+ onSignIn: () => void,
+ authUrl: string | null,
+ error: null | string,
+ isLoading: boolean,
+};
+
+export default function useLogin(): UseLogin {
+ const [authUrl, setAuthUrl] = useState(null)
+ const [error, setError] = useState(null)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isPolling, setIsPolling] = useState(false)
+ const [oAuth2Context, setOAuth2Context] = useState(null)
+ const navigate = useNavigate()
+
+ const { context } = useDeskproLatestAppContext()
+ const isUsingOAuth = context?.settings?.use_access_token !== true || context.settings.use_advanced_connect === false
+ const user = context?.data?.ticket?.primaryUser || context?.data?.user
+
+ useInitialisedDeskproAppClient(async (client) => {
+ if (!user) {
+ return
+ }
+
+ // Ensure they aren't using access tokens
+ if (!isUsingOAuth) {
+ setError("Enable OAuth to access this page");
+ return
+
+ }
+ const mode = context?.settings.use_advanced_connect === false ? 'global' : 'local';
+
+ const clientId = context?.settings.client_id;
+ if (mode === 'local' && typeof clientId !== 'string') {
+ // Local mode requires a clientId.
+ setError("A client ID is required");
+ return
+ }
+ const oAuth2Response = mode === "local" ?
+ await client.startOauth2Local(
+ ({ state, callbackUrl }) => {
+ return `https://${context?.settings.shop_name}.myshopify.com/admin/oauth/authorize?${createSearchParams([
+ ["client_id", clientId ?? ""],
+ ["state", state],
+ ["redirect_uri", callbackUrl],
+ ["scope", "read_inventory,write_assigned_fulfillment_orders,read_assigned_fulfillment_orders,write_customers,read_customers,write_draft_orders,read_draft_orders,write_order_edits,read_order_edits,write_orders,read_orders,write_product_listings,read_product_listings,write_products,read_products"]
+ ])}`
+ },
+ /\bcode=(?[^]+)/,
+ async (code: string): Promise => {
+ // Extract the callback URL from the authorization URL
+ const url = new URL(oAuth2Response.authorizationUrl);
+ const redirectUri = url.searchParams.get("redirect_uri");
+
+ if (!redirectUri) {
+ throw new Error("Failed to get callback URL");
+ }
+
+ const data = await getAccessToken(client, code);
+
+ return { data }
+ }
+ )
+ // Global Proxy Service
+ : await client.startOauth2Global("0ad23fa9caf394119372cd5db27dba4b");
+
+ setAuthUrl(mode === "local" ? oAuth2Response.authorizationUrl : `${oAuth2Response.authorizationUrl}&subdomain=${context.settings.shop_name}`)
+ setOAuth2Context(oAuth2Response)
+
+ }, [setAuthUrl, context?.settings.use_advanced_connect])
+
+ useInitialisedDeskproAppClient((client) => {
+ if (!user || !oAuth2Context) {
+ return
+ }
+
+ const startPolling = async () => {
+ try {
+ const result = await oAuth2Context.poll()
+
+ await client.setUserState(OAUTH2_ACCESS_TOKEN_PATH, result.data.access_token, { backend: true })
+
+ if (result.data.refresh_token) {
+ await client.setUserState(OAUTH2_REFRESH_TOKEN_PATH, result.data.refresh_token, { backend: true })
+ }
+
+
+ try {
+ const shopResult = await getShopInfo(client)
+
+ if (!shopResult?.data?.shop) {
+ throw new Error()
+ }
+ } catch {
+ throw new Error("Error authenticating user")
+ }
+
+
+ getEntityCustomerList(client, user.id)
+ .then((customers) => {
+ customers.length < 1 ? navigate("/link_customer") :
+ navigate("/home")
+ })
+ .catch(() => { navigate("/link_customer") })
+ } catch (error) {
+ setError(error instanceof Error ? error.message : 'Unknown error');
+ } finally {
+ setIsLoading(false)
+ setIsPolling(false)
+ }
+ }
+
+ if (isPolling) {
+ void startPolling()
+ }
+ }, [isPolling, user, oAuth2Context, navigate])
+
+ const onSignIn = useCallback(() => {
+ setIsLoading(true);
+ setIsPolling(true);
+ window.open(authUrl ?? "", '_blank');
+ }, [setIsLoading, authUrl]);
+
+
+ return { authUrl, onSignIn, error, isLoading }
+
+}
\ No newline at end of file
diff --git a/src/pages/LogoutPage/LogoutPage.tsx b/src/pages/LogoutPage/LogoutPage.tsx
new file mode 100644
index 0000000..c84fffd
--- /dev/null
+++ b/src/pages/LogoutPage/LogoutPage.tsx
@@ -0,0 +1,22 @@
+import { FC } from "react";
+import { LoadingSpinner, useInitialisedDeskproAppClient } from "@deskpro/app-sdk";
+import { OAUTH2_ACCESS_TOKEN_PATH } from "@/constants";
+import { useNavigate } from "react-router-dom";
+
+const LogoutPage: FC = () => {
+ const navigate = useNavigate();
+
+ useInitialisedDeskproAppClient((client) => {
+ client.setBadgeCount(0)
+
+ client.deleteUserState(OAUTH2_ACCESS_TOKEN_PATH)
+ .catch(() => { })
+ .finally(() => {
+ navigate("/login");
+ });
+ }, [navigate])
+
+ return ()
+}
+
+export default LogoutPage
\ No newline at end of file
diff --git a/src/pages/LogoutPage/index.ts b/src/pages/LogoutPage/index.ts
new file mode 100644
index 0000000..8efcfe6
--- /dev/null
+++ b/src/pages/LogoutPage/index.ts
@@ -0,0 +1 @@
+export { default } from "./LogoutPage";
diff --git a/src/pages/ViewCustomerPage/ViewCustomerPage.tsx b/src/pages/ViewCustomerPage/ViewCustomerPage.tsx
index bfb163c..2b9642d 100644
--- a/src/pages/ViewCustomerPage/ViewCustomerPage.tsx
+++ b/src/pages/ViewCustomerPage/ViewCustomerPage.tsx
@@ -1,69 +1,81 @@
+import { ContextData, Settings } from "@/types";
+import { useCustomer } from "../../hooks";
import { useMemo } from "react";
import { useSearchParams } from "react-router-dom";
-import {
- LoadingSpinner,
- useDeskproAppTheme,
- useDeskproAppClient,
-} from "@deskpro/app-sdk";
-import {
- useSetTitle,
- useExternalLink,
- useRegisterElements,
-} from "../../hooks";
-import { useCustomer } from "../../hooks";
import { ViewCustomer } from "../../components";
+import { LoadingSpinner, useDeskproAppClient, useDeskproAppTheme, useDeskproElements, useDeskproLatestAppContext } from "@deskpro/app-sdk";
+import { useExternalLink, useSetTitle } from "../../hooks";
import type { FC } from "react";
const ViewCustomerPage: FC = () => {
- const [searchParams] = useSearchParams();
- const customerId = searchParams.get("customerId");
- const { theme } = useDeskproAppTheme();
- const { client } = useDeskproAppClient();
- const { getCustomerLink } = useExternalLink();
- const { isLoading, customer } = useCustomer(customerId);
- const customerLink = useMemo(() => {
- return getCustomerLink(customer?.legacyResourceId);
- }, [getCustomerLink, customer?.legacyResourceId]);
+ const [searchParams] = useSearchParams();
+ const customerId = searchParams.get("customerId");
+ const { theme } = useDeskproAppTheme();
+ const { client } = useDeskproAppClient();
+ const { getCustomerLink } = useExternalLink();
+ const { context } = useDeskproLatestAppContext()
+ const { isLoading, customer } = useCustomer(customerId);
+ const customerLink = useMemo(() => {
+ return getCustomerLink(customer?.legacyResourceId);
+ }, [getCustomerLink, customer?.legacyResourceId]);
- useSetTitle(customer?.displayName || "Shopify");
+ const isUsingOAuth = context?.settings?.use_access_token !== true || context.settings.use_advanced_connect === false
- useRegisterElements(({ registerElement }) => {
- registerElement("refresh", { type: "refresh_button" });
- registerElement("home", {
- type: "home_button",
- payload: { type: "changePage", path: "/home" }
- });
- registerElement("edit", {
- type: "edit_button",
- payload: {
- type: "changePage",
- path: {
- pathname: "/edit_customer",
- search: `?customerId=${customer?.id}`,
- }
- },
- });
+ useSetTitle(customer?.displayName || "Shopify");
- if (customerLink) {
- registerElement("external", {
- type: "cta_external_link",
- url: customerLink,
- hasIcon: true,
- });
+ useDeskproElements(({ registerElement, clearElements }) => {
+ clearElements()
+ registerElement("refresh", { type: "refresh_button" })
+ registerElement("home", {
+ type: "home_button",
+ payload: { type: "changePage", path: "/home" }
+ });
+
+ registerElement("edit", {
+ type: "edit_button",
+ payload: {
+ type: "changePage",
+ path: {
+ pathname: "/edit_customer",
+ search: `?customerId=${customer?.id}`,
}
- }, [client, customer]);
+ },
+ })
+ if (customerLink) {
+ registerElement("external", {
+ type: "cta_external_link",
+ url: customerLink,
+ hasIcon: true,
+ });
+ }
- if (isLoading) {
- return
+ if (isUsingOAuth) {
+ registerElement("menu", {
+ type: "menu",
+ items: [{
+ title: "Logout",
+ payload: {
+ type: "changePage",
+ path: `/logout`,
+ },
+ }],
+ });
}
- return (
-
- );
+ }, [client, customer, context, context?.settings]);
+
+
+ if (isLoading) {
+ return
+ }
+
+ return (
+
+ );
};
export { ViewCustomerPage };
diff --git a/src/pages/index.ts b/src/pages/index.ts
index 1678ab4..f3bb88c 100644
--- a/src/pages/index.ts
+++ b/src/pages/index.ts
@@ -1,9 +1,12 @@
+export { AdminCallbackPage } from "./AdminCallbackPage";
+export { default as LoginPage } from "./Login";
+export { default as LogoutPage } from "./LogoutPage";
export { EditCustomerPage } from "./EditCustomerPage";
export { EditOrderPage } from "./EditOrderPage";
export { HomePage } from "./HomePage";
export { LinkCustomerPage } from "./LinkCustomerPage";
export { ListOrdersPage } from "./ListOrdersPage";
export { LoadingAppPage } from "./LoadingAppPage";
+export { VerifySettings } from "./VerifySettings";
export { ViewCustomerPage } from "./ViewCustomerPage";
export { ViewOrderPage } from "./ViewOrderPage";
-export { VerifySettings } from "./VerifySettings";
diff --git a/src/services/shopify/baseGraphQLRequest.ts b/src/services/shopify/baseGraphQLRequest.ts
index 27343fb..252483c 100644
--- a/src/services/shopify/baseGraphQLRequest.ts
+++ b/src/services/shopify/baseGraphQLRequest.ts
@@ -1,11 +1,11 @@
+import { getQueryParams } from "@/utils";
+import { GRAPHQL_URL, OAUTH2_ACCESS_TOKEN_PATH, placeholders } from "@/constants";
+import { proxyFetch, adminGenericProxyFetch } from "@deskpro/app-sdk";
+import { ShopifyError } from "./ShopifyError";
import has from "lodash/has";
import isEmpty from "lodash/isEmpty";
import isString from "lodash";
-import { proxyFetch, adminGenericProxyFetch } from "@deskpro/app-sdk";
-import { GRAPHQL_URL, placeholders } from "../../constants";
-import { getQueryParams } from "../../utils";
-import { ShopifyError } from "./ShopifyError";
-import type { Request } from "../../types";
+import type { Request } from "@/types";
const baseGraphQLRequest: Request = async (client, {
url,
@@ -22,13 +22,15 @@ const baseGraphQLRequest: Request = async (client, {
const baseUrl = rawUrl ? rawUrl : `${GRAPHQL_URL(settings?.shop_name)}${url || ""}`;
const params = getQueryParams(queryParams);
+ const isUsingOAuth2 = (await client.getUserState("isUsingOAuth"))[0].data
+
const requestUrl = `${baseUrl}${isEmpty(params) ? "": `?${params}`}`;
const options: RequestInit = {
method,
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
- "X-Shopify-Access-Token": settings?.access_token || placeholders.ACCESS_TOKEN,
+ "X-Shopify-Access-Token": settings?.access_token || (isUsingOAuth2 ? `[user[${OAUTH2_ACCESS_TOKEN_PATH}]]` : placeholders.ACCESS_TOKEN) ,
...customHeaders,
},
};
diff --git a/src/services/shopify/getAccessToken.ts b/src/services/shopify/getAccessToken.ts
new file mode 100644
index 0000000..3d500f6
--- /dev/null
+++ b/src/services/shopify/getAccessToken.ts
@@ -0,0 +1,30 @@
+import { placeholders } from "@/constants";
+import { IDeskproClient, proxyFetch } from "@deskpro/app-sdk";
+
+export default async function getAccessToken(
+ client: IDeskproClient,
+ code: string,
+) {
+ try {
+ const fetch = await proxyFetch(client);
+
+ const response = await fetch(`https://${placeholders.SHOP_NAME}.myshopify.com/admin/oauth/access_token`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ client_id: "__client_id__",
+ client_secret: "__client_secret__",
+ code: code
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to fetch access token");
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ throw new Error("Error fetching access token");
+ }
+}
diff --git a/src/services/shopify/index.ts b/src/services/shopify/index.ts
index a05126e..e005d88 100644
--- a/src/services/shopify/index.ts
+++ b/src/services/shopify/index.ts
@@ -1,7 +1,8 @@
-export { ShopifyError } from "./ShopifyError";
-export { getShopInfo } from "./getShopInfo";
-export { getCustomers } from "./getCustomers";
export { getCustomer } from "./getCustomer";
+export { getCustomers } from "./getCustomers";
export { getOrder } from "./getOrder";
+export { getShopInfo } from "./getShopInfo";
export { setCustomer } from "./setCustomer";
export { setOrder } from "./setOrder";
+export { ShopifyError } from "./ShopifyError";
+export { default as getAccessToken } from "./getAccessToken"
diff --git a/src/types.ts b/src/types.ts
index a433428..28f2a36 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -28,6 +28,34 @@ export type Request = (
export type Settings = {
shop_name?: string,
access_token?: string,
+ client_id?: string,
+ use_advanced_connect?: boolean,
+ use_access_token?: boolean,
+};
+
+export type ContextData = {
+ ticket?: {
+ id: string,
+ subject: string,
+ permalinkUrl: string,
+ primaryUser: {
+ id: string,
+ email: string
+ displayName: string
+ firstName: string
+ lastName: string
+ }
+ },
+ user?: {
+ id: string
+ isAgent: boolean
+ firstName: string
+ lastName: string
+ name: string
+ titlePrefix: string
+ primaryEmail: string
+ emails: string[]
+ }
};
export type NavigateToChangePage = { type: "changePage", path: To };
diff --git a/tsconfig.json b/tsconfig.json
index 87248a1..492399f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -15,7 +15,11 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
- "types": ["vite/client", "jest", "@testing-library/jest-dom", "@typescript/lib-dom"]
+ "types": ["vite/client", "jest", "@testing-library/jest-dom", "@typescript/lib-dom"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
},
"include": ["./src"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 5442ca8..b0e99ed 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,14 +1,22 @@
import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
import copy from "rollup-plugin-copy";
+import path from "path";
+import react from "@vitejs/plugin-react";
+
// https://vitejs.dev/config/
export default defineConfig({
base: "",
plugins: [react()],
server: {
+ port: 3003,
allowedHosts: true,
},
+ resolve:{
+ alias: {
+ "@": path.resolve(__dirname, "src"),
+ },
+ },
build: {
rollupOptions: {
onwarn(warning, warn) {