├── lib ├── config │ ├── locales.ts │ ├── dates.ts │ └── money.ts ├── api │ ├── resources │ │ ├── country.ts │ │ ├── taxon.ts │ │ ├── common.ts │ │ ├── product.ts │ │ ├── customer.ts │ │ └── cart.ts │ ├── urls.ts │ └── client.ts ├── payment │ └── getStripe.ts ├── resource │ ├── product.ts │ ├── taxon.ts │ ├── cart.ts │ └── customer.ts └── router │ └── router.ts ├── .vscode └── settings.json ├── next-env.d.ts ├── types ├── resources │ ├── countryTypes.ts │ ├── imageTypes.ts │ ├── cartTypes.ts │ ├── common.ts │ ├── taxonTypes.ts │ ├── productTypes.ts │ ├── customerTypes.ts │ └── orderTypes.ts └── fetchTypes.ts ├── .env ├── public └── assets │ └── images │ └── sylius.png ├── next.config.js ├── components ├── layout │ ├── Footer.tsx │ ├── Layout.tsx │ ├── MainMenu.tsx │ ├── breadcrumb.tsx │ ├── Search.code-search │ └── Header.tsx ├── LoadingContext.ts ├── CartContext.ts ├── MessageContext.ts ├── features │ ├── LoadingOverlay.tsx │ ├── Newsletter.tsx │ └── SimpleForm.tsx ├── Product │ ├── ProductTabs.tsx │ ├── ProductsList.tsx │ ├── RelatedProducts.tsx │ ├── ProductImages.tsx │ ├── ProductList.tsx │ ├── ProductDetails.tsx │ ├── SingleVariant.tsx │ └── MultipleVariants.tsx ├── account │ └── AccountNav.tsx └── cart │ ├── CartResume.tsx │ └── CartItems.tsx ├── .gitignore ├── tsconfig.json ├── pages ├── _document.tsx ├── 404.tsx ├── _app.tsx ├── 500.tsx ├── cart │ ├── failed.tsx │ ├── confirmation.tsx │ ├── paiement.tsx │ ├── index.tsx │ └── adresses.tsx ├── account │ ├── index.tsx │ ├── login.tsx │ ├── register.tsx │ ├── password.tsx │ └── orders.tsx ├── categories │ └── [categorySlug] │ │ ├── index.tsx │ │ └── [productSlug].tsx ├── index.tsx ├── search.tsx └── forgotten-password.tsx ├── package.json └── README.md /lib/config/locales.ts: -------------------------------------------------------------------------------- 1 | export const getLocale = () => { 2 | return 'fr_FR'; 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /types/resources/countryTypes.ts: -------------------------------------------------------------------------------- 1 | export interface ICountry { 2 | code: string; 3 | provinces: [] 4 | } -------------------------------------------------------------------------------- /types/resources/imageTypes.ts: -------------------------------------------------------------------------------- 1 | export interface IResourceImage{ 2 | id: string; 3 | path: string; 4 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_RESOURCE_BASE_URL= 2 | NEXT_PUBLIC_API_PUBLIC_URL= 3 | 4 | API_HOST_NAME= 5 | 6 | NEXT_PUBLIC_STRIPE_PUBLIC_KEY= 7 | 8 | MAILCHIMP_URL= -------------------------------------------------------------------------------- /public/assets/images/sylius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidroberto/-NOT-MAINTAINED-sylius-next-boilerplate-theme/HEAD/public/assets/images/sylius.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | serverRuntimeConfig: { 3 | PROJECT_ROOT: __dirname 4 | }, 5 | images: { 6 | domains: [process.env.API_HOST_NAME] 7 | } 8 | } -------------------------------------------------------------------------------- /types/resources/cartTypes.ts: -------------------------------------------------------------------------------- 1 | import { IOrder, IOrderAddresses } from "./orderTypes"; 2 | 3 | export interface IAddAddressesToCart { 4 | (adresses: IOrderAddresses & { email: string }): Promise 5 | } -------------------------------------------------------------------------------- /components/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default Footer; -------------------------------------------------------------------------------- /types/resources/common.ts: -------------------------------------------------------------------------------- 1 | export interface IResource { 2 | translations: { 3 | [key: string]: any 4 | } 5 | } 6 | 7 | export interface IMessage { 8 | message: string; 9 | type: string; 10 | } -------------------------------------------------------------------------------- /lib/config/dates.ts: -------------------------------------------------------------------------------- 1 | export const formatDateToLocale = (dateString: string): string => { 2 | const timestamp = Date.parse(dateString); 3 | const dateObject = new Date(timestamp); 4 | return dateObject.toLocaleDateString(); 5 | } -------------------------------------------------------------------------------- /lib/api/resources/country.ts: -------------------------------------------------------------------------------- 1 | import { getResource } from '../client'; 2 | import { ICountry } from '../../../types/resources/countryTypes'; 3 | 4 | export const getCountries = async (): Promise => { 5 | return await getResource('shop/countries'); 6 | } -------------------------------------------------------------------------------- /lib/config/money.ts: -------------------------------------------------------------------------------- 1 | export const formatMoney = (price: number): string => { 2 | 3 | let formatter = new Intl.NumberFormat('fr-FR', { 4 | style: 'currency', 5 | currency: 'EUR', 6 | }); 7 | 8 | return formatter.format(price / 100); 9 | } -------------------------------------------------------------------------------- /components/LoadingContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | interface ILoadingContext { 4 | isLoading: boolean, 5 | setLoading: (loading: boolean ) => void 6 | } 7 | 8 | const LoadingContext = createContext({ 9 | isLoading: false, 10 | setLoading: (loading: boolean ) => {} 11 | }); 12 | 13 | 14 | export default LoadingContext; -------------------------------------------------------------------------------- /lib/payment/getStripe.ts: -------------------------------------------------------------------------------- 1 | import { Stripe, loadStripe } from '@stripe/stripe-js'; 2 | 3 | let stripePromise: Promise; 4 | 5 | const getStripe = () => { 6 | if (!stripePromise) { 7 | stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY as string); 8 | } 9 | return stripePromise; 10 | }; 11 | 12 | export default getStripe; 13 | -------------------------------------------------------------------------------- /components/CartContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { IOrder } from '../types/resources/orderTypes'; 3 | 4 | interface ICartContext { 5 | cart: IOrder | null, 6 | setCart: (order: IOrder | null ) => void 7 | } 8 | 9 | const CartContext = createContext({ 10 | cart: null, 11 | setCart: (order: IOrder | null ) => {} 12 | }); 13 | 14 | 15 | export default CartContext; -------------------------------------------------------------------------------- /components/MessageContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { IMessage } from '../types/resources/common'; 3 | 4 | interface IMessageContext { 5 | message: IMessage | null, 6 | setMessage: (message: IMessage | null ) => void 7 | } 8 | 9 | const MessageContext = createContext({ 10 | message: null, 11 | setMessage: (message: IMessage | null ) => {} 12 | }); 13 | 14 | 15 | export default MessageContext; -------------------------------------------------------------------------------- /lib/api/resources/taxon.ts: -------------------------------------------------------------------------------- 1 | import { getResource } from "../client"; 2 | import { ITaxon } from '../../../types/resources/taxonTypes'; 3 | 4 | export const getAllTaxons = async (): Promise => { 5 | let taxons = await getResource('shop/taxons'); 6 | 7 | taxons = taxons.filter(function(taxon: ITaxon) { 8 | if (taxon.code !== "MENU_CATEGORY") { 9 | return taxon; 10 | } 11 | }); 12 | 13 | return taxons; 14 | } -------------------------------------------------------------------------------- /lib/api/resources/common.ts: -------------------------------------------------------------------------------- 1 | import { IResource } from '../../../types/resources/common'; 2 | import { getLocale } from '../../config/locales'; 3 | 4 | export const getResourceTranslation = (resource: IResource | null) => { 5 | 6 | if (resource) { 7 | if (getLocale() in resource.translations) { 8 | return resource.translations[getLocale()] 9 | } 10 | return resource.translations[Object.keys(resource.translations)[0]]; 11 | } 12 | 13 | return null 14 | } -------------------------------------------------------------------------------- /components/features/LoadingOverlay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ILoadingOverlayProps { 4 | 5 | } 6 | 7 | const LoadingOverlay: React.FC = () => { 8 | return ( 9 |
17 |
18 | ) 19 | } 20 | 21 | export default LoadingOverlay; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /types/resources/taxonTypes.ts: -------------------------------------------------------------------------------- 1 | import { IResource } from './common'; 2 | 3 | export interface ITaxonSlugTranslation { 4 | translations: { 5 | [key: string]: { 6 | slug: string 7 | } 8 | } 9 | } 10 | 11 | export interface ITaxonTranslation { 12 | id: number; 13 | name: string; 14 | slug: string; 15 | description: string; 16 | } 17 | 18 | export interface ITaxon extends IResource { 19 | id: number; 20 | code: string; 21 | } 22 | 23 | export interface IProductTaxon { 24 | id: number; 25 | taxon: string; 26 | } -------------------------------------------------------------------------------- /lib/resource/product.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from '../../types/resources/productTypes'; 2 | import { IResourceImage } from '../../types/resources/imageTypes'; 3 | 4 | export const hasPromo = (product: IProduct): boolean => { 5 | if (product.variants[0].originalPrice && 6 | product.variants[0].originalPrice !== product.variants[0].price 7 | ) { 8 | return true; 9 | } 10 | 11 | return false; 12 | } 13 | 14 | export const sortIProductmagesById = (productImages: IResourceImage[]) => { 15 | return productImages.sort((a: any, b: any) => a.id - b.id); 16 | } -------------------------------------------------------------------------------- /lib/resource/taxon.ts: -------------------------------------------------------------------------------- 1 | import { ITaxon } from "../../types/resources/taxonTypes"; 2 | import { getLocale } from "../config/locales"; 3 | import { getResourceTranslation } from '../api/resources/common'; 4 | 5 | export const getTaxonBySlugFromCollection = (taxons: ITaxon[], taxonTranslatedSlug: string): ITaxon | null => { 6 | let currentTaxon = null; 7 | 8 | taxons.map((taxon: ITaxon) => { 9 | if (getResourceTranslation(taxon).slug === taxonTranslatedSlug) { 10 | currentTaxon = taxon; 11 | } 12 | }); 13 | 14 | return currentTaxon; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /components/Product/ProductTabs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IProduct } from '../../types/resources/productTypes'; 3 | import { getResourceTranslation } from '../../lib/api/resources/common'; 4 | 5 | 6 | interface IProductTabs { 7 | product: IProduct; 8 | } 9 | 10 | const ProductTabs: React.FC = ({ product }) => { 11 | 12 | return ( 13 | <> 14 |

Description

15 |
16 | 17 | ); 18 | } 19 | 20 | export default ProductTabs; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /components/Product/ProductsList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IProduct } from '../../types/resources/productTypes'; 3 | import ProductList from './ProductList'; 4 | 5 | 6 | interface IProductsListProps { 7 | products: IProduct[] | null 8 | } 9 | 10 | const ProductsList: React.FC = ( { products } ) => { 11 | 12 | return ( 13 | <> 14 | {products?.map((product: IProduct) => { 15 | return( 16 |
17 | 18 |
19 | ); 20 | })} 21 | 22 | ); 23 | } 24 | 25 | 26 | export default ProductsList; -------------------------------------------------------------------------------- /components/Product/RelatedProducts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IProduct } from '../../types/resources/productTypes'; 3 | import ProductList from './ProductList'; 4 | 5 | interface IRelatedProductsProps { 6 | products: IProduct[] | null 7 | } 8 | 9 | const RelatedProducts: React.FC = ( { products } ) => { 10 | 11 | return ( 12 | <> 13 | {products?.map((product: IProduct, index) => { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | })} 20 | 21 | ); 22 | } 23 | 24 | 25 | export default RelatedProducts; -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | 22 | export default MyDocument; -------------------------------------------------------------------------------- /components/features/Newsletter.tsx: -------------------------------------------------------------------------------- 1 | import MailchimpSubscribe, { EmailFormFields } from "react-mailchimp-subscribe"; 2 | import SimpleForm from './SimpleForm'; 3 | 4 | interface IProps { 5 | mailchimpUrl: string 6 | } 7 | 8 | const Newsletter: React.FC = ({ mailchimpUrl }) => ( 9 | ( 14 | <> 15 |

Subscribe to the newsletter:

16 | subscribe(formData)} 20 | /> 21 | 22 | )} 23 | /> 24 | ) 25 | 26 | export default Newsletter; -------------------------------------------------------------------------------- /types/resources/productTypes.ts: -------------------------------------------------------------------------------- 1 | import { IResourceImage } from "./imageTypes"; 2 | import { ITaxonSlugTranslation } from "./taxonTypes"; 3 | 4 | interface IProductTranslation { 5 | id: number; 6 | name: string; 7 | slug: string; 8 | description: string; 9 | shortDescription: string; 10 | } 11 | 12 | interface IProductVariant { 13 | id: number; 14 | code: string; 15 | translations: { 16 | [key: string]: { 17 | name: string; 18 | } 19 | } 20 | price: number; 21 | originalPrice: number; 22 | enabled: boolean; 23 | onHand: number; 24 | } 25 | 26 | export interface IProduct { 27 | id: number; 28 | code: string; 29 | mainTaxon: ITaxonSlugTranslation; 30 | translations: { 31 | [key: string]: IProductTranslation 32 | }; 33 | images: IResourceImage[]; 34 | variants: IProductVariant[]; 35 | enabled: boolean; 36 | } -------------------------------------------------------------------------------- /components/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Header from './Header'; 3 | import Footer from './Footer'; 4 | import { ITaxon } from '../../types/resources/taxonTypes'; 5 | import { ReactNode, useContext } from 'react'; 6 | import LoadingOverlay from '../features/LoadingOverlay'; 7 | import LoadingContext from '../LoadingContext'; 8 | 9 | interface LayoutProps { 10 | taxons: ITaxon[], 11 | children: ReactNode 12 | } 13 | 14 | const Layout: React.FC = ({ children, taxons }: LayoutProps) => { 15 | 16 | const { isLoading } = useContext(LoadingContext); 17 | 18 | return ( 19 | <> 20 | {isLoading && 21 | 22 | } 23 |
24 |
25 | {children} 26 |
27 |