Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storefront v2 #667

Merged
merged 112 commits into from
Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
f60cfa6
Remove client side use of logger as its dependencies are not transpiled
Mar 16, 2020
2ef2cad
Remove useless/soon to be outdated tests
Mar 16, 2020
bcc36ab
Remove meaningless component readme's
Mar 16, 2020
7bf2318
Update Material UI, styled-components, nextjs and react to latest, re…
Mar 16, 2020
ead6dd4
Update Material UI components
Mar 16, 2020
e6d9f27
Remove useless/soon to be outdated tests #2
Mar 16, 2020
a7effd2
Add Progressive Image from component library and alter ref, as this i…
Mar 16, 2020
6d6954f
Add aliases to next for global imports without babel
Mar 16, 2020
f2d8625
Remove use of decorators
Mar 16, 2020
aba9f9b
Remove unnecessary initialprops for viewport
Mar 27, 2020
0b188de
Add new Apollo Implementation
Mar 28, 2020
69f5506
Add new Apollo Implementation #2
Mar 28, 2020
73156d7
Remove unused tracking
Mar 28, 2020
aad92ca
Use new utiltiy functions for static data fetching
Mar 28, 2020
4f5e4fb
Keep withShop for compatiblity, but instead of fetching pass shop fr…
Mar 28, 2020
59261bf
Remove useless withShop down in the tree
Mar 28, 2020
4dc6691
Remove useless withShop down in the tree #2
Mar 28, 2020
383bb76
Comment out tracking for now, keep reference for later
Mar 28, 2020
052fdea
import classic Apollo Wrappers from new lib, remove use of nested wi…
Mar 28, 2020
deabf42
Use Apollo per page
Mar 28, 2020
3ea8125
Update fetch implementation
Mar 28, 2020
0beef0c
Update node version
Mar 28, 2020
555d1fc
Import withApollo from react-components as this reexports it with wit…
Mar 28, 2020
d89cd47
fix withsHop compatibility layer to use pageprops properly
Mar 28, 2020
388751b
Replace next getConfig pattern with env
Mar 28, 2020
98ced5c
remove console logs
Mar 28, 2020
b93953b
Remov withShop per page
Mar 28, 2020
e423aa3
Implement new static catalog product fetch and use native routing to …
Mar 28, 2020
e753a37
move out of src folder
Mar 28, 2020
8990c20
move out of src folder #2
Mar 28, 2020
3aaa9c0
Move deprecated static dir to public
Mar 28, 2020
cd5b955
Remove usage of next-routes, use dynamic routes
Mar 28, 2020
88a5bf5
Remove custom server
Mar 28, 2020
667b2c0
dont omit env vars during build
Mar 28, 2020
8427e0a
set envalid strict to false
Mar 28, 2020
6f59147
use api routes for existing express routes
Mar 28, 2020
38fa29f
Add static navigation and tag fetch
Mar 28, 2020
32f6374
Turn pdp page to functional component and show loader for fallback re…
Mar 28, 2020
50d3229
lintfix
Mar 28, 2020
b7b798b
Add new Context Stores
Mar 28, 2020
b2cb153
Add hooks to consume context
Mar 28, 2020
3fe8648
Add translations util
Mar 28, 2020
cf3c905
add swr for token fetching
Mar 28, 2020
9e4bb65
Add global Context providers
Mar 28, 2020
29c6c34
Remove obsolete conainers
Mar 28, 2020
317dc5e
Remove mobx usage
Mar 29, 2020
29a21fc
Add Context Providers for Tags and Navigation
Mar 29, 2020
4ec95c7
Remove mobx usage - Fix
Mar 29, 2020
15d2023
remove unused
Mar 29, 2020
17423e3
remove unused use of session storage
Mar 29, 2020
bcd214d
useviewer query
Mar 29, 2020
6c9254b
Wrap both Apollo Providers to support both hooks and query components
Mar 29, 2020
c263176
Use Layout per page as _app is not wrapped by apollo
Mar 29, 2020
6b4f37d
add cart hook and wrap in hoc
Mar 29, 2020
c9a235f
remove pushRoute, use Route
Mar 29, 2020
d2e9efe
adjust import
Mar 29, 2020
3a00524
adjust imports
Mar 29, 2020
42aaf97
adjust imports
Mar 29, 2020
75e82df
Implement addressbook and payments hook, use in hoc
Mar 29, 2020
0017b25
Move shop hook
Mar 29, 2020
99c601f
Move to functional and seperate properly instead of rendering by route
Mar 29, 2020
9294d5b
Rename gql
Mar 29, 2020
cfeccf9
add remaining statc props
Mar 29, 2020
0a047a4
add remaining statc props #2
Mar 29, 2020
8c0cf1b
lintfix
Mar 29, 2020
335135c
fix token checkout
Mar 29, 2020
c432138
Wrap stripe provider on checkout, add static path fallback on checkou…
Mar 29, 2020
18eaa32
use new graphql request wrapper function
Mar 29, 2020
614da89
Add cart reconcilation
Mar 29, 2020
a918eb8
Fix checkout route for autneticated users
Mar 29, 2020
016eba4
Fix proptypr
Mar 29, 2020
6535fda
Move pages into language subpath
Mar 29, 2020
2f8a398
adjust imports
Mar 29, 2020
9609aa0
Implement i18n
Mar 29, 2020
aa1b4e7
Add locale paths for each page
Mar 29, 2020
08b2ec8
Remove logs
Mar 29, 2020
2e41bc4
Fix proptype
Mar 29, 2020
8c3caa8
Implement order/orders as hook, use in withOrder HOC
Mar 29, 2020
695d97c
Pass product url with locale
Mar 29, 2020
29abd91
Add margin
Mar 29, 2020
7067849
styling
Mar 30, 2020
89e7322
Remove duplicate import
Mar 30, 2020
1a1c0ab
Fetch tag data statically per page
Mar 30, 2020
fdbff43
add wrapper for useTranslation
Mar 30, 2020
24bbc72
add example translations
Mar 30, 2020
98c63e4
add example translations #2
Mar 30, 2020
bd695c6
Use Mui Button
Mar 30, 2020
707e861
Remove rui theme
Mar 30, 2020
efcafe3
Remove unsued click handler
Mar 30, 2020
64a15c4
Reimplement Profil menu
Mar 30, 2020
3e7ce7f
Wrap all context store providers in one HOC
Mar 30, 2020
5242bd6
Fork CatalogGridItem t use dynamic pat routing
Mar 30, 2020
e7d6365
lintfix
Mar 30, 2020
fdf327e
only fetch all tags on pages using breadcrumbs - Todo: Get rif of fet…
Mar 30, 2020
b11c426
Change refresh endpoint to return a JSON including hte new token inst…
Apr 12, 2020
bc4a7ed
Silent reffresh: Introduce request queuing to the errorLink - Enqueue…
Apr 12, 2020
a0a58e9
Applly linting rules
Apr 12, 2020
36cf21f
Use Mutationhook for handleRemoveCartItems, export laoding state
Apr 12, 2020
ca56e98
Remove logger - We don't want chalk tbundled on the client
Apr 12, 2020
2ba7439
Upgrade nextjs to 9.3.5
janus-reith Apr 19, 2020
0ae583e
Remove commented-out code and obsolete tracking
janus-reith Apr 19, 2020
e5e01d5
Remove apollo-client
janus-reith Apr 19, 2020
1f2a422
Remove MobX-related documentation
janus-reith Apr 19, 2020
8ed637e
Fix outdated ApolloLink import
janus-reith Apr 19, 2020
dc0b6cb
Add back apollo-client, as @apollo/react-components used in ProfileCo…
janus-reith Apr 19, 2020
912f4a5
Remove ErrorPage and use simple Typography text for now. TODO: replac…
janus-reith Apr 19, 2020
ae56a7c
Remove build-clean script
janus-reith Apr 19, 2020
fff1938
RFix next _document.js according to https://github.com/mui-org/materi…
janus-reith Apr 19, 2020
73f5126
Upgrade nextjs to v9.4.1
janus-reith May 19, 2020
aaf3dc3
Fix the docker build and provide a default env
janus-reith May 20, 2020
0f13c5d
Remove commented out code
janus-reith May 20, 2020
1719683
Introduce new BUILD_GRAPHQL_URL env var
janus-reith May 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 0 additions & 32 deletions .babelrc

This file was deleted.

1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CANONICAL_URL=http://localhost:4000
ENABLE_SPA_ROUTING=true
BUILD_GRAPHQL_URL=http://localhost:3000/graphql
EXTERNAL_GRAPHQL_URL=http://localhost:3000/graphql
INTERNAL_GRAPHQL_URL=http://api.reaction.localhost:3000/graphql
OAUTH2_ADMIN_PORT=4445
Expand Down
21 changes: 21 additions & 0 deletions .env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CANONICAL_URL=http://localhost:4000
ENABLE_SPA_ROUTING=true
BUILD_GRAPHQL_URL=http://localhost:3000/graphql
EXTERNAL_GRAPHQL_URL=http://localhost:3000/graphql
INTERNAL_GRAPHQL_URL=http://api.reaction.localhost:3000/graphql
OAUTH2_ADMIN_PORT=4445
OAUTH2_ADMIN_URL=http://hydra.reaction.localhost:4445
OAUTH2_AUTH_URL=http://localhost:4444/oauth2/auth
OAUTH2_CLIENT_ID=example-storefront
OAUTH2_CLIENT_SECRET=CHANGEME
OAUTH2_PUBLIC_LOGOUT_URL=http://localhost:4444/oauth2/sessions/logout
OAUTH2_HOST=hydra.reaction.localhost
OAUTH2_IDP_PUBLIC_CHANGE_PASSWORD_URL=http://localhost:4100/account/change-password?email=EMAIL&from=FROM
OAUTH2_IDP_HOST_URL=http://identity.reaction.localhost:4100
OAUTH2_TOKEN_URL=http://hydra.reaction.localhost:4444/oauth2/token
PORT=4000
SEGMENT_ANALYTICS_SKIP_MINIMIZE=true
SEGMENT_ANALYTICS_WRITE_KEY=ENTER_KEY_HERE
SESSION_MAX_AGE_MS=2592000000
SESSION_SECRET=CHANGEME
STRIPE_PUBLIC_API_KEY=ENTER_STRIPE_PUBLIC_KEY_HERE
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.c9
.env*
!.env.example*
!.env.prod*
*.csv
*.dat
*.gz
Expand Down
19 changes: 8 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
FROM node:10.16.3-alpine
FROM node:12-alpine

ARG NEXTJS_DOTENV

ENV NEXTJS_DOTENV=$NEXTJS_DOTENV

# hadolint ignore=DL3018
RUN apk --no-cache add bash curl less tini vim make
Expand All @@ -12,27 +16,20 @@ RUN chown node:node .

# Copy specific things so that we can keep the image as small as possible
# without relying on each repo to include a .dockerignore file.
COPY --chown=node:node package.json ./
COPY --chown=node:node yarn.lock ./
COPY --chown=node:node ./src ./src
COPY --chown=node:node LICENSE ./

# Needed only for the build command
COPY --chown=node:node .babelrc ./
COPY --chown=node:node ./ ./

USER node

# Install ALL dependencies. We need devDependencies for the build command.
RUN yarn install --production=false --frozen-lockfile --ignore-scripts --non-interactive --no-cache

ENV BUILD_ENV=production NODE_ENV=production
RUN IS_BUILDING_NEXTJS=1 "$(npm bin)/next" build src
RUN export $(grep -v '^#' .env.${NEXTJS_DOTENV:-prod} | xargs -0) && yarn build

# Install only prod dependencies now that we've built, to make the image smaller
RUN rm -rf node_modules/*
RUN rm ./.babelrc
RUN yarn install --production=true --frozen-lockfile --ignore-scripts --non-interactive

# If any Node flags are needed, they can be set in the NODE_OPTIONS env variable.
CMD ["tini", "--", "node", "."]
CMD ["tini", "--", "yarn", "start"]
LABEL com.reactioncommerce.name="example-storefront"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Reaction comes with a robust set of core commerce capabilities right out of the

Check out the full list of Reaction [features](https://www.reactioncommerce.com/features) and [release history](https://reactioncommerce.com/roadmap) for more info.

This example storefront is built with [Next.js](https://nextjs.org/), [React](https://reactjs.org/), [MobX](https://mobx.js.org/getting-started.html), [GraphQL](https://graphql.org/), and [Apollo Client](https://www.apollographql.com/docs/react/)
This example storefront is built with [Next.js](https://nextjs.org/), [React](https://reactjs.org/), [GraphQL](https://graphql.org/), and [Apollo Client](https://www.apollographql.com/docs/react/)

- Headless ecommerce example storefront built with [Next.js](https://nextjs.org/), [React](https://reactjs.org/), [MobX](https://mobx.js.org/getting-started.html), [GraphQL](https://graphql.org/), [Apollo Client](https://www.apollographql.com/docs/react/)
- Headless ecommerce example storefront built with [Next.js](https://nextjs.org/), [React](https://reactjs.org/), [GraphQL](https://graphql.org/), [Apollo Client](https://www.apollographql.com/docs/react/)
- [Reaction GraphQL API](https://github.com/reactioncommerce/reaction/tree/master/imports/plugins/core/graphql) integration
- Server-side rendering
- Payments with [Stripe](https://stripe.com/)
Expand Down
36 changes: 36 additions & 0 deletions apiUtils/headerLanguage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default function headerLookup(req) {
let found;

if (typeof req !== "undefined") {
const { headers } = req;
if (!headers) return found;

const locales = [];
const acceptLanguage = headers["accept-language"];

if (acceptLanguage) {
const lngs = []; let i; let m;
const rgx = /(([a-z]{2})-?([A-Z]{2})?)\s*;?\s*(q=([0-9.]+))?/gi;

do {
m = rgx.exec(acceptLanguage);
if (m) {
const lng = m[1]; const weight = m[5] || "1"; const q = Number(weight);
if (lng && !isNaN(q)) {
lngs.push({ lng, q });
}
}
} while (m);

lngs.sort((a, b) => b.q - a.q);

for (i = 0; i < lngs.length; i++) {
locales.push(lngs[i].lng);
}

if (locales.length) found = locales;
}
}

return found;
}
51 changes: 51 additions & 0 deletions apiUtils/localeMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import headerLanguage from "./headerLanguage";
import redirect from "./redirect";

export default (req, res) => {
const {
query: { slug },
_parsedUrl
} = req;

const fallback = "de";
const allowedLocales = [
{ name: "de-DE", locale: "de" },
{ name: "de", locale: "de" },
{ name: "en-AU", locale: "en" },
{ name: "en-IN", locale: "en" },
{ name: "en-CA", locale: "en" },
{ name: "en-NZ", locale: "en" },
{ name: "en-US", locale: "en" },
{ name: "en-ZA", locale: "en" },
{ name: "en-GB", locale: "en" },
{ name: "en", locale: "en" }
];

const detections = headerLanguage(req);

let found;

if (detections && detections.length) {
detections.forEach((language) => {
if (found || typeof language !== "string") return;

const lookedUpLocale = allowedLocales.find((allowedLocale) => allowedLocale.name === language);

if (lookedUpLocale) {
found = lookedUpLocale.locale;
}
});
}

if (!found) {
found = fallback;
}

const queryPart = (_parsedUrl && _parsedUrl.query) ? `?${_parsedUrl.query}` : "";

if (slug) {
return redirect(res, 302, `/${found}${slug ? `/${slug.join("/")}` : ""}${queryPart}`);
}

return redirect(res, 302, `/${found}${queryPart}`);
};
53 changes: 53 additions & 0 deletions apiUtils/passportMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import OAuth2Strategy from "passport-oauth2";
import passport from "passport";
import sessions from "client-sessions";
import appConfig from "../config.js";
import redirect from "./redirect";

export { default as passport } from "passport";

let baseUrl = appConfig.CANONICAL_URL;
if (!baseUrl.endsWith("/")) baseUrl = `${baseUrl}/`;

const oauthRedirectUrl = `${baseUrl}callback`;

// This is needed to allow custom parameters (e.g. loginActions) to be included
// when requesting authorization. This is setup to allow only loginAction to pass through
OAuth2Strategy.prototype.authorizationParams = function (options = {}) {
return { loginAction: options.loginAction };
};

passport.use("oauth2", new OAuth2Strategy({
authorizationURL: appConfig.OAUTH2_AUTH_URL,
tokenURL: appConfig.OAUTH2_TOKEN_URL,
clientID: appConfig.OAUTH2_CLIENT_ID,
clientSecret: appConfig.OAUTH2_CLIENT_SECRET,
callbackURL: oauthRedirectUrl,
state: true,
scope: ["offline", "openid"]
}, (accessToken, refreshToken, params, profile, cb) => {
cb(null, { accessToken, refreshToken, idToken: params.id_token });
}));

passport.serializeUser((user, done) => {
done(null, JSON.stringify(user));
});

passport.deserializeUser((user, done) => {
done(null, JSON.parse(user));
});

export default (handler) => (req, res) => {
if (!res.redirect) {
res.redirect = (location) => redirect(res, 302, location);
}

sessions({
cookieName: "session", // This name is required so passport picks it up correctly
secret: appConfig.SESSION_SECRET,
duration: appConfig.SESSION_MAX_AGE_MS
})(req, res, () =>
passport.initialize()(req, res, () =>
passport.session()(req, res, () =>
handler(req, res))));
};
17 changes: 17 additions & 0 deletions apiUtils/redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function redirect(res, statusCode, location) {
if (!res) {
throw new Error("Response object required");
}

if (!statusCode) {
throw new Error("Status code required");
}

if (!location) {
throw new Error("Location required");
}

res.statusCode = statusCode;
res.setHeader("Location", location);
res.end();
}
95 changes: 95 additions & 0 deletions components/AccountDropdown/AccountDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState, Fragment } from "react";
import inject from "hocs/inject";
import { makeStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import Button from "@material-ui/core/Button";
import ButtonBase from "@material-ui/core/ButtonBase";
import AccountIcon from "mdi-material-ui/Account";
import Popover from "@material-ui/core/Popover";
import useViewer from "hooks/viewer/useViewer";
import ViewerInfo from "@reactioncommerce/components/ViewerInfo/v1";
import Link from "components/Link";

const useStyles = makeStyles((theme) => ({
accountDropdown: {
width: 320,
padding: theme.spacing(2)
},
marginBottom: {
marginBottom: theme.spacing(2)
}
}));

const AccountDropdown = () => {
const classes = useStyles();
const [anchorElement, setAnchorElement] = useState(null);
const [viewer, isLoadingViewer] = useViewer();
const isAuthenticated = viewer && viewer._id;

const toggleOpen = (event) => {
setAnchorElement(event.currentTarget);
};

const onClose = () => {
setAnchorElement(null);
};

return (
<Fragment>
{ isAuthenticated ?
<ButtonBase onClick={toggleOpen}>
<ViewerInfo viewer={viewer} />
</ButtonBase>
:
<IconButton color="inherit" onClick={toggleOpen}>
<AccountIcon />
</IconButton>
}

<Popover
anchorEl={anchorElement}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
open={Boolean(anchorElement)}
onClose={onClose}
>
<div className={classes.accountDropdown}>
{isAuthenticated ?
<Fragment>
<div className={classes.marginBottom}>
<Link href="/profile/address">
<Button color="primary" fullWidth>
Profile
</Button>
</Link>
</div>
<div className={classes.marginBottom}>
<Button color="primary" fullWidth href={`/change-password?email=${encodeURIComponent(viewer.emailRecords[0].address)}`}>
Change Password
</Button>
</div>
<Button color="primary" fullWidth href="/logout" variant="contained">
Sign Out
</Button>
</Fragment>
:
<Fragment>
<div className={classes.authContent}>
<Button color="primary" fullWidth href="/signin" variant="contained">
Sign In
</Button>
</div>
<Button color="primary" fullWidth href="/signup">
Create Account
</Button>
</Fragment>
}
</div>
</Popover>
</Fragment>
);
};

export default inject("authStore")(AccountDropdown);
Loading