From ac063c641074c005dabb19aa0a6c9e5b5f407f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=BBuraw?= <9116238+krzysztofzuraw@users.noreply.github.com> Date: Wed, 16 Nov 2022 16:01:34 +0100 Subject: [PATCH] Update Dockerfile (#2523) Co-authored-by: Francisco Marques --- .github/workflows/publish-containers.yml | 74 +++++++++++++++++++ Dockerfile | 39 ++++++++-- Dockerfile.dev | 16 ---- README.md | 16 ++++ cypress/fixtures/urlList.js | 2 +- cypress/plugins/index.js | 2 +- cypress/support/e2e.js | 14 ++-- nginx/replace-api-url.sh | 16 ++++ src/apps/components/AppFrame/useAppActions.ts | 4 +- .../CustomAppDefaultToken.tsx | 10 +-- .../CustomAppDetailsPage.tsx | 12 +-- .../CustomAppDetails/CustomAppDetails.tsx | 6 +- src/auth/AuthProvider.test.tsx | 5 +- .../ResetPasswordPage/ResetPasswordPage.tsx | 4 +- src/auth/hooks/useAuthProvider.ts | 9 ++- src/auth/views/Login.tsx | 4 +- src/auth/views/ResetPassword.tsx | 4 +- src/auth/views/ResetPasswordSuccess.tsx | 4 +- src/config.ts | 8 +- src/graphql/client.ts | 6 +- src/index.html | 6 ++ src/index.tsx | 4 +- src/staff/views/StaffList/StaffList.tsx | 5 +- src/storybook/Decorator.tsx | 4 +- src/utils/urls.ts | 4 + testUtils/api.ts | 3 +- testUtils/setup.ts | 5 ++ webpack.config.js | 8 +- webpack.d.ts | 4 + 29 files changed, 222 insertions(+), 76 deletions(-) create mode 100644 .github/workflows/publish-containers.yml delete mode 100644 Dockerfile.dev create mode 100755 nginx/replace-api-url.sh diff --git a/.github/workflows/publish-containers.yml b/.github/workflows/publish-containers.yml new file mode 100644 index 000000000..0fe2353b6 --- /dev/null +++ b/.github/workflows/publish-containers.yml @@ -0,0 +1,74 @@ +name: Publish container image + +on: + push: + tags: + # Matches stable and pre-releases + - "[0-9]+.[0-9]+.[0-9]+*" + +jobs: + docker: + runs-on: ubuntu-20.04 + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Outputs the name of the repository (owner/repo) + - name: Get image name + id: image + run: | + IMAGE_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') + echo ::set-output name=image_name::ghcr.io/${IMAGE_NAME} + + # Tags stable versions as :latest + # Pre-releases, alphas, etc. as :snapshot + - name: Output image tags from git tag events + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: | + # Remove everything else than the tagged version + DASHBOARD_VERSION=${GITHUB_REF#refs/tags/} + echo " + DASHBOARD_VERSION=${DASHBOARD_VERSION} + CONTAINER_TAGS=${{ steps.image.outputs.image_name }}:${DASHBOARD_VERSION} + " >> "${GITHUB_ENV}" + + - name: Set up Docker QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and Push + id: docker_build + uses: docker/build-push-action@v2 + with: + builder: ${{ steps.buildx.outputs.name }} + context: ./ + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ env.CONTAINER_TAGS }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + build-args: | + COMMIT_ID=${{ github.sha }} + PROJECT_VERSION=$DASHBOARD_VERSION + + - name: Output image digest + run: | + echo $"\ + Digest: ${{ steps.docker_build.outputs.digest }} + Tags: ${{ env.CONTAINER_TAGS }}" diff --git a/Dockerfile b/Dockerfile index ee00e3d63..ef81e8073 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,46 @@ -FROM node:18 as builder +FROM node:18-alpine as builder WORKDIR /app COPY package*.json ./ -RUN npm install -COPY . . -ARG APP_MOUNT_URI +RUN npm ci --legacy-peer-deps + +COPY nginx/ nginx/ +COPY assets/ assets/ +COPY locale/ locale/ +COPY testUtils testUtils/ +COPY codegen.yml . +COPY webpack.config.js . +COPY tsconfig.json . +COPY *.d.ts . +COPY schema.graphql . +COPY introspection.json . +COPY src/ src/ + ARG API_URI +ARG APP_MOUNT_URI ARG MARKETPLACE_URL ARG SALEOR_APPS_ENDPOINT ARG STATIC_URL + ENV API_URI ${API_URI:-http://localhost:8000/graphql/} ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/} +ENV MARKETPLACE_URL ${MARKETPLACE_URL} +ENV SALEOR_APPS_ENDPOINT=${SALEOR_APPS_ENDPOINT} ENV STATIC_URL ${STATIC_URL:-/dashboard/} -RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} MARKETPLACE_URL=${MARKETPLACE_URL} SALEOR_APPS_ENDPOINT=${SALEOR_APPS_ENDPOINT} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build -FROM nginx:stable +RUN npm run build + +FROM nginx:stable-alpine as runner WORKDIR /app + COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf +COPY ./nginx/replace-api-url.sh /docker-entrypoint.d/50-replace-api-url.sh COPY --from=builder /app/build/ /app/ + +LABEL org.opencontainers.image.title="saleor/saleor-dashboard" \ + org.opencontainers.image.description="A GraphQL-powered, single-page dashboard application for Saleor." \ + org.opencontainers.image.url="https://saleor.io/" \ + org.opencontainers.image.source="https://github.com/saleor/saleor-dashboard" \ + org.opencontainers.image.revision="$COMMIT_ID" \ + org.opencontainers.image.version="$PROJECT_VERSION" \ + org.opencontainers.image.authors="Saleor Commerce (https://saleor.io)" \ + org.opencontainers.image.licenses="BSD 3" diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 07255aa39..000000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:18 -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . -ARG APP_MOUNT_URI -ARG API_URI -ARG MARKETPLACE_URL -ARG SALEOR_APPS_ENDPOINT -ARG STATIC_URL -ENV API_URI ${API_URI:-http://localhost:8000/graphql/} -ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/} -ENV STATIC_URL ${STATIC_URL:-/} - -EXPOSE 9000 -CMD npm start -- --host 0.0.0.0 diff --git a/README.md b/README.md index 73f28e2a4..f24559e44 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,22 @@ You are ready to run cypress commands like: npm run cy:open ``` +### Usage with docker + +Build docker image: + +```shell +docker build --tag saleor-dashboard . +``` + +Run nginx from docker and bind it to port on your machine (in this example 8080): + +```shell +docker run --publish 8080:80 --env "API_URL=" saleor-dashboard +``` + +Enter `http://localhost:8080/` to use dashboard. + ##### Usage with Sentry adapter: Sentry is used as the default tracker so no changes in code are necessary and the configuration is done via environment variables. diff --git a/cypress/fixtures/urlList.js b/cypress/fixtures/urlList.js index d183387fa..f9ad97da0 100644 --- a/cypress/fixtures/urlList.js +++ b/cypress/fixtures/urlList.js @@ -29,7 +29,7 @@ export const urlList = { variants: "variant/", vouchers: "discounts/vouchers/", variant: "variant/", - warehouses: "warehouses/" + warehouses: "warehouses/", }; export const addVariantUrl = productId => diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 314386b06..606fb594d 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -38,7 +38,7 @@ module.exports = async (on, config) => { config.env.mailHogUrl = process.env.CYPRESS_mailHogUrl; config.env.grepTags = process.env.CYPRESS_grepTags; - on("before:browser:launch", ({}, launchOptions) => { + on("before:browser:launch", (_browser = {}, launchOptions) => { launchOptions.args.push("--proxy-bypass-list=<-loopback>"); return launchOptions; }); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 02379ca1b..78fa5143b 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -1,19 +1,19 @@ // / -import "./customCommands/user"; +import "cypress-file-upload"; +import "cypress-mailhog"; +import "cypress-mochawesome-reporter/register"; import "./customCommands/basicOperations"; import "./customCommands/deleteElementsViaApi"; -import "./customCommands/softAssertions"; import "./customCommands/sharedElementsOperations/addressForm.js"; import "./customCommands/sharedElementsOperations/assignElementsForm.js"; import "./customCommands/sharedElementsOperations/confirmationMessages.js"; +import "./customCommands/sharedElementsOperations/deleteElement"; import "./customCommands/sharedElementsOperations/progressBar.js"; import "./customCommands/sharedElementsOperations/selects.js"; import "./customCommands/sharedElementsOperations/tables"; -import "./customCommands/sharedElementsOperations/deleteElement"; -import "cypress-mailhog"; -import "cypress-file-upload"; -import "cypress-mochawesome-reporter/register"; +import "./customCommands/softAssertions"; +import "./customCommands/user"; import { commandTimings } from "cypress-timings"; @@ -49,7 +49,7 @@ Cypress.Commands.add("addAliasToGraphRequest", operationName => { Cypress.on( "uncaught:exception", - (err, runnable) => + (_err, _runnable) => // returning false here prevents Cypress from // failing the test false, diff --git a/nginx/replace-api-url.sh b/nginx/replace-api-url.sh new file mode 100755 index 000000000..07aa5cbc6 --- /dev/null +++ b/nginx/replace-api-url.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Replaces the API_URL from the bundle's index.html file with the $API_URL env var. +# This script is automatically picked up by the nginx entrypoint on startup. + +set -e + +INDEX_BUNDLE_PATH="/app/dashboard/index.html" + +if [ -z "${API_URL}" ]; then + echo "No API_URL provided, using defaults." +else + echo "Setting API_URL to: $API_URL" + + sed -i "s#API_URL:.*#API_URL: \"$API_URL\",#" "$INDEX_BUNDLE_PATH" +fi diff --git a/src/apps/components/AppFrame/useAppActions.ts b/src/apps/components/AppFrame/useAppActions.ts index 17f8d4482..d0c134be7 100644 --- a/src/apps/components/AppFrame/useAppActions.ts +++ b/src/apps/components/AppFrame/useAppActions.ts @@ -6,7 +6,7 @@ import { RedirectAction, } from "@saleor/app-sdk/app-bridge"; import { appPath } from "@saleor/apps/urls"; -import { APP_MOUNT_URI } from "@saleor/config"; +import { getAppMountUri } from "@saleor/config"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import React from "react"; @@ -78,7 +78,7 @@ export const useAppActions = ( if (newContext) { window.open(to); } else if (appDeepUrlChange) { - const exactLocation = urlJoin(APP_MOUNT_URI, to); + const exactLocation = urlJoin(getAppMountUri(), to); // Change only url without reloading if we are in the same app window.history.pushState(null, "", exactLocation); diff --git a/src/apps/components/CustomAppDefaultToken/CustomAppDefaultToken.tsx b/src/apps/components/CustomAppDefaultToken/CustomAppDefaultToken.tsx index 9d7b7b2de..8ded3295d 100644 --- a/src/apps/components/CustomAppDefaultToken/CustomAppDefaultToken.tsx +++ b/src/apps/components/CustomAppDefaultToken/CustomAppDefaultToken.tsx @@ -10,14 +10,14 @@ import { FormattedMessage } from "react-intl"; import { useStyles } from "./styles"; export interface CustomAppDefaultTokenProps { - apiUri: string; + apiUrl: string; token: string; - onApiUriClick: () => void; + onApiUrlClick: () => void; onTokenClose: () => void; } const CustomAppDefaultToken: React.FC = props => { - const { apiUri, token, onApiUriClick, onTokenClose } = props; + const { apiUrl, token, onApiUrlClick, onTokenClose } = props; const classes = useStyles(props); const [copied, copy] = useClipboard(); @@ -38,8 +38,8 @@ const CustomAppDefaultToken: React.FC = props => { defaultMessage="This token gives you access to your shop's API, which you'll find here: {url}" values={{ url: ( - - {apiUri} + + {apiUrl} ), }} diff --git a/src/apps/components/CustomAppDetailsPage/CustomAppDetailsPage.tsx b/src/apps/components/CustomAppDetailsPage/CustomAppDetailsPage.tsx index b67677f98..8f30ad841 100644 --- a/src/apps/components/CustomAppDetailsPage/CustomAppDetailsPage.tsx +++ b/src/apps/components/CustomAppDetailsPage/CustomAppDetailsPage.tsx @@ -36,14 +36,14 @@ export interface CustomAppDetailsPageFormData { permissions: PermissionEnum[]; } export interface CustomAppDetailsPageProps { - apiUri: string; + apiUrl: string; disabled: boolean; errors: AppErrorFragment[]; permissions: ShopInfoQuery["shop"]["permissions"]; saveButtonBarState: ConfirmButtonTransitionState; app: AppUpdateMutation["appUpdate"]["app"]; token: string; - onApiUriClick: () => void; + onApiUrlClick: () => void; onTokenDelete: (id: string) => void; onTokenClose: () => void; onTokenCreate: () => void; @@ -58,14 +58,14 @@ export interface CustomAppDetailsPageProps { const CustomAppDetailsPage: React.FC = props => { const { - apiUri, + apiUrl, disabled, errors, permissions, saveButtonBarState, app, token, - onApiUriClick, + onApiUrlClick, onTokenClose, onTokenCreate, onTokenDelete, @@ -136,9 +136,9 @@ const CustomAppDetailsPage: React.FC = props => { {token && ( <> diff --git a/src/apps/views/CustomAppDetails/CustomAppDetails.tsx b/src/apps/views/CustomAppDetails/CustomAppDetails.tsx index 645929d38..125253660 100644 --- a/src/apps/views/CustomAppDetails/CustomAppDetails.tsx +++ b/src/apps/views/CustomAppDetails/CustomAppDetails.tsx @@ -5,7 +5,7 @@ import TokenDeleteDialog from "@saleor/apps/components/TokenDeleteDialog"; import { appMessages } from "@saleor/apps/messages"; import NotFoundPage from "@saleor/components/NotFoundPage"; import { WindowTitle } from "@saleor/components/WindowTitle"; -import { API_URI } from "@saleor/config"; +import { getApiUrl } from "@saleor/config"; import { AppTokenCreateMutation, AppTokenDeleteMutation, @@ -220,11 +220,11 @@ export const CustomAppDetails: React.FC = ({ <> open(API_URI, "blank")} + onApiUrlClick={() => open(getApiUrl(), "blank")} onSubmit={handleSubmit} onTokenClose={onTokenClose} onTokenCreate={() => openModal("create-token")} diff --git a/src/auth/AuthProvider.test.tsx b/src/auth/AuthProvider.test.tsx index 46a6e988a..d49da4fef 100644 --- a/src/auth/AuthProvider.test.tsx +++ b/src/auth/AuthProvider.test.tsx @@ -1,3 +1,4 @@ +import { getApiUrl } from "@saleor/config"; import { createSaleorClient, SaleorProvider } from "@saleor/sdk"; import setupApi from "@test/api"; import { act, renderHook } from "@testing-library/react-hooks"; @@ -15,7 +16,7 @@ function renderAuthProvider() { }; const notify = jest.fn(); const saleorClient = createSaleorClient({ - apiUrl: process.env.API_URI, + apiUrl: getApiUrl(), channel: "", }); const wrapper = ({ children }) => ( @@ -50,7 +51,7 @@ beforeEach(() => { sessionStorage.clear(); }); -xdescribe("User", () => { +describe("User", () => { it("will be logged in if has valid credentials", async done => { const hook = renderAuthProvider(); diff --git a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx index 10b6822e2..225383fd4 100644 --- a/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx +++ b/src/auth/components/ResetPasswordPage/ResetPasswordPage.tsx @@ -3,7 +3,7 @@ import { Button } from "@saleor/components/Button"; import Form from "@saleor/components/Form"; import FormSpacer from "@saleor/components/FormSpacer"; import { IconButton } from "@saleor/components/IconButton"; -import { APP_MOUNT_URI } from "@saleor/config"; +import { getAppMountUri } from "@saleor/config"; import { RequestPasswordResetMutation } from "@saleor/graphql"; import { SubmitPromise } from "@saleor/hooks/useForm"; import { commonMessages } from "@saleor/intl"; @@ -36,7 +36,7 @@ const ResetPasswordPage: React.FC = props => {
{({ change: handleChange, data, submit: handleSubmit }) => ( <> - + diff --git a/src/auth/hooks/useAuthProvider.ts b/src/auth/hooks/useAuthProvider.ts index beab07c1a..81188136b 100644 --- a/src/auth/hooks/useAuthProvider.ts +++ b/src/auth/hooks/useAuthProvider.ts @@ -1,6 +1,6 @@ import { ApolloClient } from "@apollo/client"; import { IMessageContext } from "@saleor/components/messages"; -import { APP_DEFAULT_URI, APP_MOUNT_URI, DEMO_MODE } from "@saleor/config"; +import { DEMO_MODE } from "@saleor/config"; import { useUserDetailsQuery } from "@saleor/graphql"; import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -16,6 +16,7 @@ import { login as loginWithCredentialsManagementAPI, saveCredentials, } from "@saleor/utils/credentialsManagement"; +import { getAppMountUriForRedirect } from "@saleor/utils/urls"; import { useEffect, useRef, useState } from "react"; import { IntlShape } from "react-intl"; import urlJoin from "url-join"; @@ -88,8 +89,10 @@ export function useAuthProvider({ }); const handleLogout = async () => { - const path = APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI; - const returnTo = urlJoin(window.location.origin, path); + const returnTo = urlJoin( + window.location.origin, + getAppMountUriForRedirect(), + ); const result = await logout({ input: JSON.stringify({ diff --git a/src/auth/views/Login.tsx b/src/auth/views/Login.tsx index 8a602806a..0579446c6 100644 --- a/src/auth/views/Login.tsx +++ b/src/auth/views/Login.tsx @@ -1,7 +1,7 @@ -import { APP_DEFAULT_URI, APP_MOUNT_URI } from "@saleor/config"; import { useAvailableExternalAuthenticationsQuery } from "@saleor/graphql"; import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useNavigator from "@saleor/hooks/useNavigator"; +import { getAppMountUriForRedirect } from "@saleor/utils/urls"; import React, { useEffect } from "react"; import urlJoin from "url-join"; import useRouter from "use-react-router"; @@ -52,7 +52,7 @@ const LoginView: React.FC = ({ params }) => { const result = await requestLoginByExternalPlugin(pluginId, { redirectUri: urlJoin( window.location.origin, - APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI, + getAppMountUriForRedirect(), loginCallbackPath, ), }); diff --git a/src/auth/views/ResetPassword.tsx b/src/auth/views/ResetPassword.tsx index 6efd31927..5cfaf0952 100644 --- a/src/auth/views/ResetPassword.tsx +++ b/src/auth/views/ResetPassword.tsx @@ -1,8 +1,8 @@ -import { APP_MOUNT_URI } from "@saleor/config"; import { useRequestPasswordResetMutation } from "@saleor/graphql"; import useNavigator from "@saleor/hooks/useNavigator"; import { commonMessages } from "@saleor/intl"; import { extractMutationErrors } from "@saleor/misc"; +import { getAppMountUriForRedirect } from "@saleor/utils/urls"; import React from "react"; import { useIntl } from "react-intl"; import urlJoin from "url-join"; @@ -49,7 +49,7 @@ const ResetPasswordView: React.FC = () => { email: data.email, redirectUrl: urlJoin( window.location.origin, - APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, + getAppMountUriForRedirect(), newPasswordUrl().replace(/\?/, ""), ), }, diff --git a/src/auth/views/ResetPasswordSuccess.tsx b/src/auth/views/ResetPasswordSuccess.tsx index 8d8841dc8..20a0287e1 100644 --- a/src/auth/views/ResetPasswordSuccess.tsx +++ b/src/auth/views/ResetPasswordSuccess.tsx @@ -1,4 +1,4 @@ -import { APP_MOUNT_URI } from "@saleor/config"; +import { getAppMountUri } from "@saleor/config"; import useNavigator from "@saleor/hooks/useNavigator"; import React from "react"; @@ -7,7 +7,7 @@ import ResetPasswordSuccessPage from "../components/ResetPasswordSuccessPage"; const ResetPasswordSuccessView: React.FC = () => { const navigate = useNavigator(); - return navigate(APP_MOUNT_URI)} />; + return navigate(getAppMountUri())} />; }; ResetPasswordSuccessView.displayName = "ResetPasswordSuccessView"; export default ResetPasswordSuccessView; diff --git a/src/config.ts b/src/config.ts index 664f40851..752b1653b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,10 +2,10 @@ import packageInfo from "../package.json"; import { SearchVariables } from "./hooks/makeSearch"; import { ListSettings, ListViews, Pagination } from "./types"; -export const IS_TEST = process.env.NODE_ENV === "test"; -export const APP_MOUNT_URI = IS_TEST ? "/" : process.env.APP_MOUNT_URI || "/"; -export const APP_DEFAULT_URI = "/"; -export const API_URI = process.env.API_URI; +export const getAppDefaultUri = () => "/"; +export const getAppMountUri = () => + window.__SALEOR_CONFIG__.APP_MOUNT_URI || getAppDefaultUri(); +export const getApiUrl = () => window.__SALEOR_CONFIG__.API_URL; export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10); export const IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE === "true"; export const MARKETPLACE_URL = process.env.MARKETPLACE_URL; diff --git a/src/graphql/client.ts b/src/graphql/client.ts index 3e3703524..0aaed0c62 100644 --- a/src/graphql/client.ts +++ b/src/graphql/client.ts @@ -4,7 +4,7 @@ import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client"; import { createFetch, createSaleorClient } from "@saleor/sdk"; import { createUploadLink } from "apollo-upload-client"; -import { API_URI } from "../config"; +import { getApiUrl } from "../config"; import introspectionQueryResultData from "./fragmentTypes.generated"; import { TypedTypePolicies } from "./typePolicies.generated"; @@ -21,7 +21,7 @@ const attachVariablesLink = new ApolloLink((operation, forward) => export const link = attachVariablesLink.concat( createUploadLink({ credentials: "include", - uri: API_URI, + uri: getApiUrl(), fetch: createFetch(), }), ); @@ -70,6 +70,6 @@ export const apolloClient = new ApolloClient({ }); export const saleorClient = createSaleorClient({ - apiUrl: API_URI, + apiUrl: getApiUrl(), channel: "", }); diff --git a/src/index.html b/src/index.html index 7dcdf4cdd..d7f1fc351 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,12 @@ /> Saleor e-commerce + diff --git a/src/index.tsx b/src/index.tsx index 0d3f64dbb..840e7a2dd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -36,7 +36,7 @@ import { LocaleProvider } from "./components/Locale"; import MessageManagerProvider from "./components/messages"; import { ShopProvider } from "./components/Shop"; import { WindowTitle } from "./components/WindowTitle"; -import { APP_MOUNT_URI, DEMO_MODE, GTM_ID } from "./config"; +import { DEMO_MODE, getAppMountUri, GTM_ID } from "./config"; import ConfigurationSection from "./configuration"; import { getConfigMenuItemsPermissions } from "./configuration/utils"; import AppStateProvider from "./containers/AppState"; @@ -80,7 +80,7 @@ errorTracker.init(); const App: React.FC = () => ( - + diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index a871dc903..8b18885c5 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -4,7 +4,7 @@ import SaveFilterTabDialog, { SaveFilterTabDialogFormData, } from "@saleor/components/SaveFilterTabDialog"; import { useShopLimitsQuery } from "@saleor/components/Shop/queries"; -import { APP_MOUNT_URI, DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; +import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { useStaffListQuery, useStaffMemberAddMutation } from "@saleor/graphql"; import useListSettings from "@saleor/hooks/useListSettings"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -23,6 +23,7 @@ import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { mapEdgesToItems } from "@saleor/utils/maps"; import { getSortParams } from "@saleor/utils/sort"; +import { getAppMountUriForRedirect } from "@saleor/utils/urls"; import React from "react"; import { useIntl } from "react-intl"; import urlJoin from "url-join"; @@ -159,7 +160,7 @@ export const StaffList: React.FC = ({ params }) => { lastName: variables.lastName, redirectUrl: urlJoin( window.location.origin, - APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, + getAppMountUriForRedirect(), newPasswordUrl().replace(/\?/, ""), ), }, diff --git a/src/storybook/Decorator.tsx b/src/storybook/Decorator.tsx index bc3e2e55c..ca0ba99ae 100644 --- a/src/storybook/Decorator.tsx +++ b/src/storybook/Decorator.tsx @@ -10,7 +10,7 @@ import { ApolloMockedProvider } from "../../testUtils/ApolloMockedProvider"; import { Provider as DateProvider } from "../components/Date/DateContext"; import MessageManagerProvider from "../components/messages"; import { TimezoneProvider } from "../components/Timezone"; -import { APP_MOUNT_URI } from "../config"; +import { getAppMountUri } from "../config"; export const Decorator = storyFn => ( @@ -24,7 +24,7 @@ export const Decorator = storyFn => ( - +
/^https?:\/\//.test(url); + +export const getAppMountUriForRedirect = () => + getAppMountUri() === getAppDefaultUri() ? "" : getAppMountUri(); diff --git a/testUtils/api.ts b/testUtils/api.ts index d44e88262..d8408b220 100644 --- a/testUtils/api.ts +++ b/testUtils/api.ts @@ -3,6 +3,7 @@ import { BatchHttpLink } from "@apollo/client/link/batch-http"; import NodeHttpAdapter from "@pollyjs/adapter-node-http"; import { Polly } from "@pollyjs/core"; import FSPersister from "@pollyjs/persister-fs"; +import { getApiUrl } from "@saleor/config"; import { createFetch } from "@saleor/sdk"; import path from "path"; import { setupPolly } from "setup-polly-jest"; @@ -36,7 +37,7 @@ function setupApi() { const cache = new InMemoryCache(); const link = new BatchHttpLink({ fetch: createFetch(), - uri: process.env.API_URI || "http://localhost:8000/graphql/", + uri: getApiUrl(), }); const apolloClient = new ApolloClient({ cache, diff --git a/testUtils/setup.ts b/testUtils/setup.ts index 584a11a70..c3a3ac668 100644 --- a/testUtils/setup.ts +++ b/testUtils/setup.ts @@ -1 +1,6 @@ document.getElementById = () => document.createElement("div"); + +window.__SALEOR_CONFIG__ = { + API_URL: "http://localhost:8000/graphql/", + APP_MOUNT_URI: "/", +}; diff --git a/webpack.config.js b/webpack.config.js index 967542bcd..1ec28cfc5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,12 +35,16 @@ const htmlWebpackPlugin = new HtmlWebpackPlugin({ filename: "index.html", hash: true, template: "./src/index.html", + templateParameters: { + // URI is kept for backwards compatibility. + // See more at https://github.com/saleor/saleor-dashboard/issues/2502 + API_URL: process.env.API_URI, + APP_MOUNT_URI: process.env.APP_MOUNT_URI, + }, }); const environmentPlugin = new webpack.EnvironmentPlugin({ - API_URI: "", MARKETPLACE_URL: "", SALEOR_APPS_ENDPOINT: "", - APP_MOUNT_URI: "/", DEMO_MODE: false, ENVIRONMENT: "", GTM_ID: "", diff --git a/webpack.d.ts b/webpack.d.ts index 83a284da5..297b69044 100644 --- a/webpack.d.ts +++ b/webpack.d.ts @@ -7,4 +7,8 @@ declare module "*.svg" { declare interface Window { PasswordCredential: PasswordCredential; + __SALEOR_CONFIG__: { + API_URL: string; + APP_MOUNT_URI: string; + }; }