├── .babelrc ├── .circleci └── config.yml ├── .env-example ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .serverless ├── Template.json ├── Template.myNextApp.AwsS3.json ├── Template.myNextApp.CloudFront.json ├── Template.myNextApp.Domain.json ├── Template.myNextApp.defaultEdgeLambda.AwsIamRole.json ├── Template.myNextApp.defaultEdgeLambda.json └── _.json ├── .storybook ├── addons.js ├── config.js ├── preview-head.html └── webpack.config.js ├── README.md ├── build_aws_profile.js ├── components ├── AccountConfirmationForm │ ├── AccountConfirmationForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── AccountDropdown │ ├── AccountDropdown.tsx │ ├── index.stories.tsx │ └── index.tsx ├── App │ ├── App.tsx │ ├── AppContainer.tsx │ ├── ProvidedApp.tsx │ └── index.tsx ├── Banner │ ├── Banner.stories.tsx │ ├── Banner.tsx │ └── index.tsx ├── BookForm │ ├── BookForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── CommentBlock │ ├── CommentBlock.tsx │ ├── index.stories.tsx │ └── index.tsx ├── CookForm │ ├── CookForm.tsx │ └── index.tsx ├── Cover │ ├── Cover.stories.tsx │ ├── Cover.tsx │ └── index.tsx ├── FAQ │ ├── FAQ.tsx │ └── index.tsx ├── FlipCard │ ├── FlipCard.stories.tsx │ ├── FlipCard.tsx │ └── index.tsx ├── FlipCardList │ ├── FlipCardList.stories.tsx │ ├── FlipCardList.tsx │ └── index.tsx ├── Footer │ ├── Footer.stories.tsx │ ├── Footer.tsx │ └── index.tsx ├── Gallery │ ├── Gallery.tsx │ ├── index.stories.tsx │ └── index.tsx ├── Header │ ├── Header.stories.tsx │ ├── Header.tsx │ ├── HeaderContainer.tsx │ └── index.tsx ├── Hero │ ├── Hero.tsx │ ├── index.stories.tsx │ └── index.tsx ├── Layout │ ├── Layout.tsx │ └── index.tsx ├── Link │ ├── Link.tsx │ ├── NextLink.tsx │ └── index.tsx ├── Loading │ ├── Loading.tsx │ ├── index.stories.tsx │ └── index.tsx ├── LoginForm │ ├── LoginForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── Logo │ ├── Logo.tsx │ └── index.tsx ├── MailchimpForm │ ├── MailchimpForm.tsx │ └── index.tsx ├── PricingCardList │ ├── PricingCardList.tsx │ └── index.tsx ├── ProfileImage │ ├── ProfileImage.tsx │ └── index.tsx ├── ProfileImageUploader │ ├── ProfileImageUploader.tsx │ ├── ProfileImageUploaderContainer.tsx │ ├── index.stories.tsx │ └── index.tsx ├── ProgressBar │ ├── ProgressBar.tsx │ └── index.tsx ├── ResetPasswordForm │ ├── ResetPasswordForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── ResetPasswordRequestForm │ ├── ResetPasswordRequestForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── S3Image │ ├── S3Image.tsx │ └── index.tsx ├── SearchForm │ ├── SearchForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── Seo │ ├── Seo.tsx │ └── index.tsx ├── SignUpForm │ ├── SignUpForm.tsx │ ├── index.stories.tsx │ └── index.tsx ├── Snackbar │ ├── Snackbar.tsx │ ├── index.stories.tsx │ └── index.tsx ├── StarRating │ ├── StarRating.tsx │ ├── index.stories.tsx │ └── index.tsx ├── StaticSteper │ ├── StaticSteper.tsx │ └── index.tsx ├── Steps │ ├── Steps.tsx │ └── index.tsx ├── WizardForm │ ├── WizardForm.tsx │ └── index.tsx ├── WorkshopCard │ ├── WorkshopCard.tsx │ ├── index.stories.tsx │ └── index.tsx ├── WorkshopCardList │ ├── WorkshopCardList.tsx │ ├── index.stories.tsx │ └── index.tsx ├── WorkshopForm │ ├── WorkshopForm.tsx │ └── index.tsx └── WorkshopListItem │ ├── WorkshopListItem.tsx │ ├── index.stories.tsx │ └── index.tsx ├── content ├── charter.mdx ├── city │ └── brest.mdx ├── components.tsx ├── gift.mdx ├── invite-business.mdx ├── join.mdx ├── mission.mdx ├── organize.mdx ├── presskit.mdx ├── team.mdx ├── terms-pro.mdx └── terms.mdx ├── decorators ├── Redirect │ ├── RedirectDecorator.tsx │ └── index.tsx ├── WithAuth │ ├── WithAuth.tsx │ ├── WithAuthDecorator.tsx │ └── index.tsx ├── WithData │ ├── WithDataDecorator.tsx │ ├── index.tsx │ └── withData.js ├── index.tsx ├── initApollo.js └── theme.js ├── generate-sitemap.js ├── handler.js ├── loadenv.js ├── next-env.d.ts ├── next.config.js ├── notcomponents ├── AccountForm │ ├── AccountForm.tsx │ ├── AccountFormContainer.tsx │ ├── index.stories.tsx │ └── index.tsx ├── BecomeCookForm │ ├── BecomeCookForm.tsx │ ├── BecomeCookFormContainer.tsx │ ├── index.stories.tsx │ └── index.tsx ├── PaymentCardForm │ ├── PaymentCardForm.tsx │ ├── index.stories.tsx │ └── index.tsx └── WorkshopEditForm │ ├── WorkshopEditForm.tsx │ ├── WorkshopEditFormContainer.tsx │ └── index.tsx ├── notpages ├── account-confirmation.tsx ├── account.tsx ├── become-cook.tsx ├── become-partner.tsx ├── cocktail.tsx ├── cook.tsx ├── login.tsx ├── payment.tsx ├── places.tsx ├── profile.tsx ├── reset-password-request.tsx ├── reset-password.tsx ├── search.tsx ├── sign-up.tsx ├── work-council.tsx ├── workshop-edit.tsx ├── workshop-new.tsx └── workshop.tsx ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── _error.tsx ├── breakfast.tsx ├── cater.tsx ├── challenge.tsx ├── charter.tsx ├── cocktail.tsx ├── current-offer.tsx ├── event.tsx ├── faq.tsx ├── index.tsx ├── individual.tsx ├── invite.tsx ├── join.tsx ├── lunch.tsx ├── our-cooks.tsx ├── our-places.tsx ├── pop-up-station.tsx ├── presskit.tsx ├── recurring-cohesion.tsx ├── seminar.tsx ├── taylored-event.tsx ├── team-and-mission.tsx ├── teambuilding.tsx ├── terms.tsx └── testimony.tsx ├── public ├── browserconfig.xml ├── manifest.json └── robots.txt ├── queries ├── CreateCook.ts ├── CreateWorkshop.ts ├── DeleteWorkshop.ts ├── GetCook.ts ├── GetCurrentGourmet.ts ├── GetCurrentGourmetImage.ts ├── GetKitchens.ts ├── GetWorkshop.ts ├── GetWorkshops.ts ├── UpdateCook.ts ├── UpdateGourmet.ts ├── UpdateWorkshop.ts └── index.js ├── serverless.yml ├── shared ├── analytics.ts ├── auth.ts ├── config.ts ├── constants.ts ├── date-utils.ts ├── events.js ├── faq.js ├── seo.js ├── util.ts └── validations.ts ├── tsconfig.json ├── typings └── index.d.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": [ 5 | "next/babel" 6 | ] 7 | }, 8 | "production": { 9 | "presets": [ 10 | "next/babel" 11 | ] 12 | }, 13 | "test": { 14 | "presets": [ 15 | [ 16 | "next/babel", 17 | { 18 | "preset-env": { 19 | "modules": "commonjs" 20 | } 21 | } 22 | ] 23 | ] 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | deploy-front-prod: 4 | docker: 5 | - image: circleci/node:latest 6 | environment: 7 | AWS_CONFIG_PATH: /home/circleci/.aws 8 | steps: 9 | - checkout 10 | - restore_cache: &restore_front_cache 11 | key: dependency-cache-{{ checksum "./yarn.lock" }} 12 | - run: &build_aws_config 13 | name: Build AWS config 14 | command: | 15 | sudo mkdir $AWS_CONFIG_PATH 16 | sudo AWS_CONFIG_PATH=$AWS_CONFIG_PATH aws_access_key_id_prod=$aws_access_key_id_prod aws_secret_access_key_prod=$aws_secret_access_key_prod node build_aws_profile 17 | - run: &install_front_dependencies 18 | name: Install dependencies 19 | command: | 20 | yarn install 21 | - save_cache: &save_front_cache 22 | key: dependency-cache-{{ checksum "./yarn.lock" }} 23 | paths: 24 | - ./node_modules 25 | - run: &build_env 26 | name: Build env 27 | command: | 28 | yarn create-env 29 | - run: &create_sitemap 30 | name: Create sitemap 31 | command: | 32 | yarn sitemap 33 | - run: &deploy 34 | name: Deploy frontend 35 | command: | 36 | yarn deploy 37 | 38 | deploy-front-stage: 39 | docker: 40 | - image: circleci/node:latest 41 | environment: 42 | AWS_CONFIG_PATH: /home/circleci/.aws 43 | steps: 44 | - checkout 45 | - restore_cache: *restore_front_cache 46 | - run: *build_aws_config 47 | - run: *install_front_dependencies 48 | - save_cache: *save_front_cache 49 | - run: *build_env 50 | - run: *create_sitemap 51 | - run: *deploy 52 | 53 | workflows: 54 | version: 2 55 | deploy: 56 | jobs: 57 | - deploy-front-prod: 58 | context: production 59 | filters: 60 | branches: 61 | only: 62 | - master 63 | - deploy-front-stage: 64 | context: staging 65 | filters: 66 | branches: 67 | only: /release-.+/ 68 | -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- 1 | STAGE= 2 | AWS_REGION_IRELAND= 3 | AWS_USERPOOL_ID= 4 | AWS_IDENTITY_POOL_ID= 5 | AWS_STORE_BUCKET= 6 | AWS_APP_CLIENT_ID= 7 | GRAPHQL_API_URL= 8 | GUEST_USERNAME= 9 | GUEST_PASSWORD= 10 | ALGOLIASEARCH_SEARCH_APP_ID= 11 | ALGOLIASEARCH_SEARCH_KEY= 12 | ALGOLIASEARCH_PLACES_APP_ID= 13 | ALGOLIASEARCH_PLACES_KEY= 14 | SENTRY_DSN= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: ['airbnb-typescript', 'prettier/react', 'prettier/@typescript-eslint'], 6 | parserOptions: { 7 | project: './tsconfig.json', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | build 3 | *.log 4 | 5 | .DS_Store 6 | 7 | .next 8 | .serverless_nextjs 9 | .env 10 | .env.local 11 | .env.development* 12 | .env.test* 13 | .env.production* 14 | 15 | node_modules/ 16 | npm-debug.log 17 | yarn-error.log 18 | sitemap.xml 19 | 20 | .vscode 21 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | singleQuote: true, 4 | useTabs: true, 5 | printWidth: 170, 6 | }; 7 | -------------------------------------------------------------------------------- /.serverless/Template.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "myNextApp": "C:\\Users\\Romain\\.serverless\\components\\registry\\npm\\serverless-next.js@1.6.1\\node_modules\\serverless-next.js" 4 | } 5 | } -------------------------------------------------------------------------------- /.serverless/Template.myNextApp.AwsS3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cuistotducoin-front", 3 | "region": "us-east-1", 4 | "accelerated": true, 5 | "url": "https://cuistotducoin-front.s3.amazonaws.com" 6 | } -------------------------------------------------------------------------------- /.serverless/Template.myNextApp.CloudFront.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "EL6H1YPAQF3DD", 3 | "arn": "arn:aws:cloudfront::899832958734:distribution/EL6H1YPAQF3DD", 4 | "url": "https://d2hwjlw3auyhnf.cloudfront.net", 5 | "region": "us-east-1", 6 | "origins": [ 7 | { 8 | "url": "http://cuistotducoin-front.s3.amazonaws.com", 9 | "private": true, 10 | "pathPatterns": { 11 | "_next/*": { 12 | "ttl": 86400 13 | }, 14 | "static/*": { 15 | "ttl": 86400 16 | } 17 | } 18 | } 19 | ], 20 | "defaults": { 21 | "ttl": 5, 22 | "allowedHttpMethods": [ 23 | "HEAD", 24 | "GET" 25 | ], 26 | "forward": { 27 | "cookies": "all", 28 | "queryString": true 29 | }, 30 | "lambda@edge": { 31 | "origin-request": "arn:aws:lambda:us-east-1:899832958734:function:rc8ti2m-2ls59pk:26" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /.serverless/Template.myNextApp.Domain.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "privateZone": false, 4 | "domain": "cuistotducoin.com", 5 | "subdomains": [ 6 | { 7 | "domain": "www.cuistotducoin.com", 8 | "distributionId": "EL6H1YPAQF3DD", 9 | "url": "https://d2hwjlw3auyhnf.cloudfront.net", 10 | "type": "awsCloudFront" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.serverless/Template.myNextApp.defaultEdgeLambda.AwsIamRole.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rc8ti2m-sxdgfeh", 3 | "arn": "arn:aws:iam::899832958734:role/rc8ti2m-sxdgfeh", 4 | "service": [ 5 | "edgelambda.amazonaws.com", 6 | "lambda.amazonaws.com" 7 | ], 8 | "policy": { 9 | "arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 10 | }, 11 | "region": "us-east-1" 12 | } -------------------------------------------------------------------------------- /.serverless/Template.myNextApp.defaultEdgeLambda.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rc8ti2m-2ls59pk", 3 | "hash": "XcLle5rJaHq9WwNIxZwOrxGIQbC9fON3xksLF5EWukg=", 4 | "description": "Default Lambda@Edge for Next CloudFront distribution", 5 | "memory": 512, 6 | "timeout": 10, 7 | "code": "C:\\Users\\Romain\\Documents\\GitHub\\front\\.serverless_nextjs\\default-lambda", 8 | "shims": [], 9 | "handler": "index.handler", 10 | "runtime": "nodejs10.x", 11 | "env": {}, 12 | "role": { 13 | "arn": "arn:aws:iam::899832958734:role/rc8ti2m-sxdgfeh" 14 | }, 15 | "arn": "arn:aws:lambda:us-east-1:899832958734:function:rc8ti2m-2ls59pk", 16 | "region": "us-east-1" 17 | } -------------------------------------------------------------------------------- /.serverless/_.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "rc8ti2m" 3 | } -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import "@storybook/addon-actions/register"; 2 | import "@storybook/addon-knobs/register"; 3 | import "@storybook/addon-options/register"; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { configure, addDecorator } from "@storybook/react"; 3 | import { withKnobs } from "@storybook/addon-knobs"; 4 | import { setOptions } from "@storybook/addon-options"; 5 | import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; 6 | import theme from '../decorators/theme'; 7 | import "slick-carousel/slick/slick-theme.css"; 8 | import "slick-carousel/slick/slick.css"; 9 | 10 | const reqComps = require.context("../components", true, /.stories.tsx$/); 11 | const reqPages = require.context("../pages", true, /.stories.tsx$/); 12 | 13 | const load = () => { 14 | reqComps.keys().forEach(reqComps); 15 | reqPages.keys().forEach(reqPages); 16 | }; 17 | 18 | addDecorator(story => {story()}); 19 | addDecorator(withKnobs); 20 | 21 | setOptions({ 22 | name: "Cuistot du Coin", 23 | url: "https://www.cuistotducoin.com", 24 | showDownPanel: true, 25 | downPanelInRight: true 26 | }); 27 | 28 | configure(load, module); 29 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TSDocgenPlugin = require('react-docgen-typescript-webpack-plugin'); 3 | 4 | const COMPONENTS_PATH = path.join(__dirname, '../'); 5 | 6 | module.exports = (baseConfig, env, config) => { 7 | // const config = genDefaultConfig(baseConfig, env); 8 | // Extend it as you need. 9 | // For example, add typescript loader: 10 | config.module.rules.push({ 11 | test: /\.(ts|tsx)$/, 12 | include: [COMPONENTS_PATH], 13 | use: [ 14 | { 15 | loader: require.resolve('awesome-typescript-loader'), 16 | } 17 | ] 18 | }); 19 | 20 | config.plugins.push(new TSDocgenPlugin()); 21 | config.resolve.extensions.push('.ts', '.tsx'); 22 | config.resolve.modules.push('./src'); 23 | config.node = { 24 | __dirname: true 25 | }; 26 | 27 | return config; 28 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Cuistot du Coin](https://www.cuistotducoin.com) : 2 | 3 | ## EDITOR SETUP: 4 | - Use TSLint 5 | 6 | ## SETUP: 7 | - Copy the provided secrets file to the root (look at .env-example) 8 | - `yarn` to install dependencies 9 | - `yarn dev` for development server 10 | - `yarn build` for production server (pages) 11 | - `yarn lambda` for development serverless (use build-phase before lambda) 12 | - `yarn deploy` for deploying on AWS (circleCI will handle that) 13 | 14 | ## TECHNOLOGIES: 15 | ### Libraries & Packages 16 | * Uses TypeScript for types with Javascript 17 | * Uses MaterialUI for Generic Components 18 | * Uses Formik for form validation 19 | * Uses Next for universal server-rendering 20 | * Uses Apollo, aws-amplify, aws-appsync for providing API calls directly to components. 21 | * Uses Algolia for search -------------------------------------------------------------------------------- /build_aws_profile.js: -------------------------------------------------------------------------------- 1 | // Used by CircleCI job and assume-role tool to create necessary aws config files 2 | 3 | const fs = require('fs'); 4 | 5 | content = `\ 6 | [default] 7 | aws_access_key_id = ${process.env.aws_access_key_id_prod} 8 | aws_secret_access_key = ${process.env.aws_secret_access_key_prod} 9 | `; 10 | 11 | fs.writeFile(`${process.env.AWS_CONFIG_PATH}/credentials`, content, (err) => { 12 | if (err) throw err; 13 | console.log('aws credentials saved...'); 14 | }); -------------------------------------------------------------------------------- /components/AccountConfirmationForm/AccountConfirmationForm.tsx: -------------------------------------------------------------------------------- 1 | import Auth from '@aws-amplify/auth'; 2 | import Button from '@material-ui/core/Button'; 3 | import Grid from '@material-ui/core/Grid'; 4 | import { Theme, withStyles } from '@material-ui/core/styles'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import { TextField } from 'formik-material-ui'; 7 | import Router, { withRouter } from 'next/router'; 8 | import React from 'react'; 9 | import { compose } from 'recompose'; 10 | import { Subscribe } from 'unstated'; 11 | import * as Yup from 'yup'; 12 | import { AppContainer } from '../../components/App'; 13 | import { SNACKBAR_MESSAGES } from '../../shared/constants'; 14 | 15 | const styles = (theme: Theme) => ({ 16 | grid: { 17 | margin: '0px auto', 18 | maxWidth: 540, 19 | padding: theme.spacing(3), 20 | paddingTop: 0, 21 | }, 22 | textField: { 23 | width: '100%', 24 | }, 25 | submitButton: { 26 | marginTop: 16, 27 | }, 28 | }); 29 | 30 | interface IAccountConfirmationFormProps { 31 | classes?: any; 32 | location: any; 33 | router: any; 34 | } 35 | 36 | interface IAccountConfirmationFormValues { 37 | username: string; 38 | code: string; 39 | } 40 | 41 | export class AccountConfirmationForm extends React.Component { 42 | public constructor(props) { 43 | super(props); 44 | this.onSubmit = this.onSubmit.bind(this); 45 | this.initialValues = this.initialValues.bind(this); 46 | } 47 | 48 | public initialValues() { 49 | return { 50 | username: this.props.router.query.username || '', 51 | code: '', 52 | }; 53 | } 54 | 55 | public render() { 56 | const { classes } = this.props; 57 | 58 | const validationSchema = Yup.object().shape({ 59 | username: Yup.string().required("Un nom d'utilisateur est obligatoire"), 60 | code: Yup.number().required('Veuillez entrer le code reçu par email'), 61 | }); 62 | 63 | const accountConfirmationFormComponent = () => ( 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 |
79 | ); 80 | 81 | return ( 82 | 83 | {(app: any) => ( 84 | 92 | )} 93 | 94 | ); 95 | } 96 | 97 | public onSubmit(openSnackbar) { 98 | return (values: IAccountConfirmationFormValues, { setSubmitting, setErrors, setStatus, resetForm }) => { 99 | Auth.confirmSignUp(values.username, values.code, { 100 | forceAliasCreation: false, 101 | }) 102 | .then(() => { 103 | setStatus({ success: true }); 104 | resetForm(this.initialValues()); 105 | openSnackbar("Merci d'avoir confirmé votre compte ! Vous pouvez désormais vous connecter", 'success'); 106 | Router.replace(`/login`); 107 | }) 108 | .catch(err => { 109 | openSnackbar(SNACKBAR_MESSAGES[err.code] || 'Erreur', 'error'); 110 | setStatus({ success: false }); 111 | setSubmitting(false); 112 | setErrors({ submit: err.message }); 113 | }); 114 | }; 115 | } 116 | } 117 | 118 | const enhance = compose(withStyles(styles as any), withRouter); 119 | 120 | export default enhance(AccountConfirmationForm); 121 | -------------------------------------------------------------------------------- /components/AccountConfirmationForm/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { withKnobs } from "@storybook/addon-knobs"; 2 | import { storiesOf } from "@storybook/react"; 3 | import React from "react"; 4 | import AccountConfirmationForm from "./AccountConfirmationForm"; 5 | 6 | storiesOf("Cuistot/components/AccountConfirmationForm", module).add( 7 | "default", 8 | () => 9 | ); 10 | -------------------------------------------------------------------------------- /components/AccountConfirmationForm/index.tsx: -------------------------------------------------------------------------------- 1 | import AccountConfirmationForm from "./AccountConfirmationForm"; 2 | export default AccountConfirmationForm; 3 | -------------------------------------------------------------------------------- /components/AccountDropdown/AccountDropdown.tsx: -------------------------------------------------------------------------------- 1 | import Button from "@material-ui/core/Button"; 2 | import ClickAwayListener from "@material-ui/core/ClickAwayListener"; 3 | import ListItemIcon from "@material-ui/core/ListItemIcon"; 4 | import ListItemText from "@material-ui/core/ListItemText"; 5 | import MenuItem from "@material-ui/core/MenuItem"; 6 | import MenuList from "@material-ui/core/MenuList"; 7 | import Paper from "@material-ui/core/Paper"; 8 | import Popper from "@material-ui/core/Popper"; 9 | import PersonIcon from "mdi-material-ui/Account"; 10 | import LogoutIcon from "mdi-material-ui/Logout"; 11 | import MenuIcon from "mdi-material-ui/Menu"; 12 | import Router from "next/router"; 13 | import React from "react"; 14 | import { Subscribe } from "unstated"; 15 | import { AppContainer } from "../../components/App"; 16 | 17 | interface IAccountDropdownState { 18 | open: boolean; 19 | anchorEl: any; 20 | } 21 | 22 | class AccountDropdown extends React.Component<{}, IAccountDropdownState> { 23 | constructor(props) { 24 | super(props); 25 | this.state = { anchorEl: null, open: false }; 26 | this.handleToggle = this.handleToggle.bind(this); 27 | this.handleClose = this.handleClose.bind(this); 28 | this.goToAccount = this.goToAccount.bind(this); 29 | } 30 | 31 | public handleToggle() { 32 | this.setState(state => ({ open: !state.open })); 33 | } 34 | 35 | public handleClose(event) { 36 | if (this.state.anchorEl.contains(event.target)) { 37 | return; 38 | } 39 | this.setState({ open: false }); 40 | } 41 | 42 | public goToAccount(event) { 43 | this.handleClose(event); 44 | Router.push("/account"); 45 | } 46 | 47 | public render() { 48 | const { open, anchorEl } = this.state; 49 | 50 | return ( 51 |
52 | 59 | 60 | {(app: any) => ( 61 | 68 | {() => ( 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | )} 88 | 89 | )} 90 | 91 |
92 | ); 93 | } 94 | } 95 | 96 | export default AccountDropdown; 97 | -------------------------------------------------------------------------------- /components/AccountDropdown/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { withKnobs } from "@storybook/addon-knobs"; 2 | import { storiesOf } from "@storybook/react"; 3 | import React from "react"; 4 | import AccountDropdown from "./AccountDropdown"; 5 | 6 | storiesOf("Cuistot/components/AccountDropdown", module).add("default", () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /components/AccountDropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import AccountDropdown from "./AccountDropdown"; 2 | export default AccountDropdown; 3 | -------------------------------------------------------------------------------- /components/App/AppContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from "unstated"; 2 | import initApollo from "../../decorators/initApollo"; 3 | import { GetCurrentGourmetImage } from "../../queries"; 4 | import { apolloConfig } from "../../shared/config"; 5 | 6 | interface IAppState { 7 | referer?: string; 8 | isLoggedIn: boolean; 9 | snackbarOpened: boolean; 10 | snackbarMessage?: string; 11 | snackbarVariant?: string; 12 | currentGourmet?: object; 13 | } 14 | 15 | class AppContainer extends Container { 16 | public state = { 17 | snackbarOpened: false, 18 | isLoggedIn: false, 19 | referer: "/" 20 | }; 21 | 22 | public setReferer = url => { 23 | this.setState({ referer: url }); 24 | }; 25 | 26 | public logIn = () => { 27 | this.setState({ isLoggedIn: true }); 28 | }; 29 | 30 | public logOut = () => { 31 | this.setState({ isLoggedIn: false, referer: "/" }); 32 | }; 33 | 34 | public openSnackbar = (message, variant = "info") => { 35 | this.setState({ 36 | snackbarOpened: true, 37 | snackbarMessage: message, 38 | snackbarVariant: variant 39 | }); 40 | }; 41 | 42 | public closeSnackbar = () => { 43 | this.setState({ 44 | snackbarOpened: false 45 | }); 46 | }; 47 | 48 | public setCurrentGourmet = gourmet => { 49 | this.setState({ currentGourmet: gourmet }); 50 | }; 51 | 52 | public updateCurrentGourmetImage = () => { 53 | const client = initApollo({}, apolloConfig); 54 | client 55 | .query({ query: GetCurrentGourmetImage, fetchPolicy: "no-cache" }) 56 | .then(result => { 57 | if (result.data.getCurrentGourmet.message === "success") { 58 | const gourmet = result.data.getCurrentGourmet.gourmet; 59 | this.setState(prevState => 60 | Object.assign({}, prevState, { 61 | currentGourmet: { 62 | ...prevState.currentGourmet, 63 | image: gourmet.image 64 | } 65 | }) 66 | ); 67 | } 68 | }); 69 | }; 70 | } 71 | 72 | export default AppContainer; 73 | -------------------------------------------------------------------------------- /components/App/ProvidedApp.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider, Subscribe } from "unstated"; 3 | import UNSTATED from "unstated-debug"; 4 | import { App, AppContainer } from "."; 5 | import "../../shared/auth"; 6 | 7 | if (process.env.NODE_ENV === "development" && typeof window !== "undefined") { 8 | UNSTATED.logStateChanges = true; 9 | // @ts-ignore 10 | window.LOG_LEVEL = "DEBUG"; 11 | } 12 | 13 | export class ProvidedApp extends React.Component { 14 | public appContainer: any; 15 | 16 | public constructor(props) { 17 | super(props); 18 | this.appContainer = new AppContainer(); 19 | } 20 | 21 | public render() { 22 | const { children } = this.props; 23 | return ( 24 | 25 | 26 | {(app: any) => ( 27 | // @ts-ignore 28 | 38 | {children} 39 | 40 | )} 41 | 42 | 43 | ); 44 | } 45 | } 46 | 47 | export default ProvidedApp as any; 48 | -------------------------------------------------------------------------------- /components/App/index.tsx: -------------------------------------------------------------------------------- 1 | import App from "./App"; 2 | import AppContainer from "./AppContainer"; 3 | import ProvidedApp from "./ProvidedApp"; 4 | export default ProvidedApp; 5 | export { App, AppContainer }; 6 | -------------------------------------------------------------------------------- /components/Banner/Banner.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import Banner from "./Banner"; 4 | 5 | storiesOf("Cuistot/components/Banner", module) 6 | .addDecorator(story => ( 7 | 8 | )) 9 | .add("default", () => ); 10 | -------------------------------------------------------------------------------- /components/Banner/Banner.tsx: -------------------------------------------------------------------------------- 1 | import Grid from "@material-ui/core/Grid"; 2 | import { Theme, withStyles } from "@material-ui/core/styles"; 3 | import Typography from "@material-ui/core/Typography"; 4 | import React from "react"; 5 | 6 | const styles = (theme: Theme) => ({ 7 | banner: { 8 | background: "#f6f6f6", 9 | height: "80px", 10 | margin: theme.spacing(2), 11 | overflow: "hidden", 12 | width: "100%" 13 | }, 14 | overflow: { 15 | content: "", 16 | overflow: "hidden", 17 | "&:before": { 18 | content: '""', 19 | position: "absolute", 20 | borderTop: 0, 21 | borderBottom: "80px solid transparent", 22 | borderLeft: "60px solid #f6f6f6" 23 | } 24 | }, 25 | cover: { 26 | height: "100%", 27 | width: "400px" 28 | }, 29 | text: { 30 | padding: theme.spacing(2), 31 | marginLeft: 50 32 | } 33 | }); 34 | 35 | export interface IBannerProps { 36 | classes?: any; 37 | title: string; 38 | subtitle: string; 39 | imageURL?: string; 40 | } 41 | 42 | export class Banner extends React.Component { 43 | private innerBackground = { 44 | backgroundImage: `url(${this.props.imageURL})` 45 | }; 46 | 47 | public render() { 48 | const { classes, title, subtitle } = this.props; 49 | 50 | return ( 51 | 52 | 53 | 54 | {title} 55 | 56 | 57 | {subtitle} 58 | 59 | 60 | {this.props.imageURL && ( 61 | 62 | 63 | 64 | )} 65 | 66 | ); 67 | } 68 | } 69 | 70 | export default withStyles(styles as any)(Banner as any) as any; 71 | -------------------------------------------------------------------------------- /components/Banner/index.tsx: -------------------------------------------------------------------------------- 1 | import Banner from "./Banner"; 2 | export default Banner; 3 | -------------------------------------------------------------------------------- /components/BookForm/BookForm.tsx: -------------------------------------------------------------------------------- 1 | import { FormControl, MenuItem } from "@material-ui/core"; 2 | import Button from "@material-ui/core/Button"; 3 | import InputLabel from "@material-ui/core/InputLabel"; 4 | import { Theme, withStyles } from "@material-ui/core/styles"; 5 | import Typography from "@material-ui/core/Typography"; 6 | import { Field, Form, Formik } from "formik"; 7 | import { Select } from "formik-material-ui"; 8 | import React from "react"; 9 | 10 | const styles = (theme: Theme) => ({ 11 | formControl: { 12 | marginTop: theme.spacing(1), 13 | width: "100%" 14 | }, 15 | grid: { 16 | margin: "0px auto", 17 | maxWidth: 540, 18 | padding: theme.spacing(3) 19 | }, 20 | textField: { 21 | width: "100%" 22 | } 23 | }); 24 | 25 | interface IBookForm { 26 | classes?: any; 27 | price: number; 28 | availableSeat: number; 29 | } 30 | 31 | export class BookForm extends React.Component { 32 | public render() { 33 | const { classes } = this.props; 34 | 35 | const onSubmit = async () => { 36 | try { 37 | // await Auth.signIn(values.email, values.password); 38 | alert("Logged in"); 39 | } catch (e) { 40 | alert(e.message); 41 | } 42 | }; 43 | 44 | const initialValues = { 45 | nbSeat: 1 46 | }; 47 | 48 | const bookFormComponent = () => ( 49 | <> 50 | 51 | {this.props.price}€ par personne 52 | 53 | 54 | Il reste {this.props.availableSeat} places pour cet atelier 55 | 56 |
57 | 58 | Nombre d'invités 59 | 68 | // @ts-ignore 69 | {[...Array(this.props.availableSeat)].map((e, i) => { 70 | return ( 71 | 72 | {i + 1} 73 | 74 | ); 75 | })} 76 | 77 | 78 |
79 | 86 | 87 | Vous ne serez débité que si vous confirmez 88 | 89 |
90 |
91 | 92 | ); 93 | 94 | return ( 95 | 100 | ); 101 | } 102 | } 103 | 104 | export default withStyles(styles as any)(BookForm as any) as any; 105 | -------------------------------------------------------------------------------- /components/BookForm/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import BookForm from "./BookForm"; 4 | 5 | storiesOf("Cuistot/components/BookForm", module).add("default", () => ( 6 | 7 | )); 8 | -------------------------------------------------------------------------------- /components/BookForm/index.tsx: -------------------------------------------------------------------------------- 1 | import BookForm from "./BookForm"; 2 | export default BookForm; 3 | -------------------------------------------------------------------------------- /components/CommentBlock/CommentBlock.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Typography } from '@material-ui/core'; 2 | import React from 'react'; 3 | import ProfileImage from '../../components/ProfileImage'; 4 | import { distanceInWordsToNow } from '../../shared/date-utils'; 5 | import { StarRating } from '../StarRating/StarRating'; 6 | 7 | interface ICommentBlock { 8 | classes?: any; 9 | authorIdentityId: string; 10 | authorImageKey?: string; 11 | name: string; 12 | comment: string; 13 | rating: number; 14 | date: string; 15 | } 16 | 17 | export class CommentBlock extends React.Component { 18 | public render() { 19 | const { authorIdentityId, authorImageKey, name, rating, comment, date } = this.props; 20 | return ( 21 | <> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {name} 32 | 33 | 34 | 35 | 36 | {distanceInWordsToNow(date)} 37 | 38 | 39 | 40 | 41 | {comment} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | 54 | export default CommentBlock; 55 | -------------------------------------------------------------------------------- /components/CommentBlock/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import CommentBlock from "./CommentBlock"; 4 | 5 | storiesOf("Cuistot/components/CommentBlock", module).add("default", () => ( 6 | 13 | )); 14 | -------------------------------------------------------------------------------- /components/CommentBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import CommentBlock from "./CommentBlock"; 2 | export default CommentBlock; 3 | -------------------------------------------------------------------------------- /components/CookForm/CookForm.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@material-ui/core/Button'; 2 | import Grid from '@material-ui/core/Grid'; 3 | import { Theme, withStyles } from '@material-ui/core/styles'; 4 | import { Field, Form } from 'formik'; 5 | import { Switch, TextField } from 'formik-material-ui'; 6 | import React from 'react'; 7 | 8 | const styles = (theme: Theme) => ({ 9 | grid: { 10 | margin: '0px auto', 11 | maxWidth: 540, 12 | padding: theme.spacing(3), 13 | }, 14 | textField: { 15 | width: '100%', 16 | }, 17 | submitButton: { 18 | marginTop: '30px', 19 | }, 20 | isPro: { 21 | display: 'flex', 22 | alignItems: 'center', 23 | justifyContent: 'center', 24 | }, 25 | isProLabel: { 26 | color: theme.palette.text.secondary, 27 | }, 28 | }); 29 | 30 | interface ICookFormProps { 31 | classes: any; 32 | values: any; 33 | action: string; 34 | } 35 | 36 | const BUTTON_TITLES = { 37 | create: 'Devenir Cuistot', 38 | update: 'Mettre à jour mes infos Cuistot', 39 | }; 40 | 41 | // tslint:disable-next-line 42 | const CookForm: React.SFC = ({ classes, values, action }) => ( 43 |
44 | 45 | 46 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | Vous êtes un(e) professionnel(le) ? 64 |
65 |
66 | {values.is_pro && ( 67 | <> 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 89 | 90 | 91 | 100 | 101 | 102 | 103 | 104 | 116 | 117 | 118 | )} 119 | 120 | 123 | 124 |
125 |
126 | ); 127 | 128 | export default withStyles(styles as any)(CookForm); 129 | -------------------------------------------------------------------------------- /components/CookForm/index.tsx: -------------------------------------------------------------------------------- 1 | import CookForm from './CookForm'; 2 | export default CookForm; -------------------------------------------------------------------------------- /components/Cover/Cover.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import Cover from "./Cover"; 4 | 5 | storiesOf("Cuistot/components/Cover", module) 6 | .addDecorator(story => ( 7 | 8 | )) 9 | .add("default", () => ); 10 | -------------------------------------------------------------------------------- /components/Cover/Cover.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from "@material-ui/core/styles"; 2 | import React from "react"; 3 | 4 | const styles = () => ({ 5 | cover: { 6 | backgroundPosition: "50% 50%", 7 | backgroundRepeat: "no-repeat", 8 | backgroundSize: "cover", 9 | height: 400 10 | } 11 | }); 12 | 13 | export interface ICoverProps { 14 | classes?: any; 15 | imageURL: string; 16 | } 17 | 18 | export class Cover extends React.Component { 19 | private innerBackground = { 20 | backgroundImage: `url(${this.props.imageURL})` 21 | }; 22 | 23 | public render() { 24 | const { classes } = this.props; 25 | 26 | return
; 27 | } 28 | } 29 | 30 | export default withStyles(styles as any)(Cover as any) as any; 31 | -------------------------------------------------------------------------------- /components/Cover/index.tsx: -------------------------------------------------------------------------------- 1 | import Cover from "./Cover"; 2 | export default Cover; 3 | -------------------------------------------------------------------------------- /components/FAQ/FAQ.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; 2 | import { Grid, Typography, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails } from '@material-ui/core'; 3 | import React from 'react'; 4 | import ExpandMoreIcon from 'mdi-material-ui/ChevronDown'; 5 | 6 | const useStyles = makeStyles((theme: Theme) => 7 | createStyles({ 8 | container: { 9 | padding: theme.spacing(3), 10 | }, 11 | heading: { 12 | fontSize: theme.typography.pxToRem(15), 13 | fontWeight: 'bold', 14 | flexShrink: 0, 15 | }, 16 | link: { 17 | textDecorationColor: 'black', 18 | textDecoration: 'none', 19 | }, 20 | }), 21 | ); 22 | 23 | type FAQProps = { 24 | faqList: any; 25 | }; 26 | 27 | export default function FAQ(props: FAQProps) { 28 | const { faqList } = props; 29 | const classes = useStyles(); 30 | const [expanded, setExpanded] = React.useState(false); 31 | 32 | const handleChange = (panel: string) => (_event: React.ChangeEvent<{}>, isExpanded: boolean) => { 33 | setExpanded(isExpanded ? panel : false); 34 | }; 35 | 36 | return ( 37 | 38 | {Object.keys(faqList).map((faqItemList, index) => ( 39 | 40 | 41 | {faqItemList} 42 | 43 | 44 | {faqList[faqItemList].map((faq1, _index) => ( 45 | 46 | }> 47 | 48 | {faq1.question} 49 | 50 | 51 | 52 |
53 | 54 | 55 | ))} 56 | 57 | 58 | ))} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /components/FAQ/index.tsx: -------------------------------------------------------------------------------- 1 | import FAQ from './FAQ'; 2 | export default FAQ; 3 | -------------------------------------------------------------------------------- /components/FlipCard/FlipCard.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import FlipCard from "./FlipCard"; 4 | 5 | storiesOf("Cuistot/components/FlipCard", module).add("default", () => ); 6 | -------------------------------------------------------------------------------- /components/FlipCard/FlipCard.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles'; 2 | import React from 'react'; 3 | import cx from 'classnames'; 4 | 5 | // https://codepen.io/ArashRasteh/pen/QmgeLL 6 | // https://codepen.io/nicolamihaita/pen/zKXAOW 7 | // TODO : La carte "tremble" lorsqu'on bouge le curseur sur le contenu de la carte. 8 | const styles = () => ({ 9 | container: { 10 | backgroundColor: 'transparent', 11 | width: 360, 12 | height: 280, 13 | perspective: '1000px', 14 | }, 15 | flipper: { 16 | width: '100%', 17 | height: '100%', 18 | transition: 'transform 0.8s', 19 | transformStyle: 'preserve-3d', 20 | '&:hover': { 21 | transform: 'rotateY(180deg)', 22 | }, 23 | }, 24 | front: { 25 | transformStyle: 'preserve-3d', 26 | backfaceVisibility: 'hidden', 27 | position: 'absolute', 28 | width: '100%', 29 | height: '100%', 30 | }, 31 | back: { 32 | transformStyle: 'preserve-3d', 33 | backfaceVisibility: 'hidden', 34 | position: 'absolute', 35 | transform: 'rotateY(-180deg)', 36 | width: '100%', 37 | height: '100%', 38 | }, 39 | }); 40 | 41 | export interface IFlipCardProps { 42 | classes?: any; 43 | className?: string; 44 | front: any; 45 | back: any; 46 | } 47 | 48 | export class FlipCard extends React.Component { 49 | public render() { 50 | const { classes, className, front, back } = this.props; 51 | 52 | return ( 53 |
54 |
55 |
{front}
56 |
{back}
57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | export default withStyles(styles as any)(FlipCard as any) as any; 64 | -------------------------------------------------------------------------------- /components/FlipCard/index.tsx: -------------------------------------------------------------------------------- 1 | import FlipCard from "./FlipCard"; 2 | export default FlipCard; 3 | -------------------------------------------------------------------------------- /components/FlipCardList/FlipCardList.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import FlipCard from "./FlipCardList"; 4 | 5 | storiesOf("Cuistot/components/FlipCardList", module).add("default", () => ); 6 | -------------------------------------------------------------------------------- /components/FlipCardList/FlipCardList.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@material-ui/core/Button'; 2 | import teal from '@material-ui/core/colors/teal'; 3 | import Grid from '@material-ui/core/Grid'; 4 | import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import React from 'react'; 7 | import FlipCard from '../FlipCard'; 8 | import Link from '../Link'; 9 | 10 | const useStyles = makeStyles((theme: Theme) => 11 | createStyles({ 12 | backgroundImageFront: { 13 | bottom: 0, 14 | left: 0, 15 | position: 'absolute', 16 | right: 0, 17 | top: 0, 18 | backgroundPosition: '50%', 19 | backgroundSize: 'cover', 20 | zIndex: -1, 21 | }, 22 | backgroundImageBack: { 23 | backgroundColor: teal[800], 24 | height: '100%', 25 | width: '100%', 26 | }, 27 | flipcard: { 28 | margin: theme.spacing(1), 29 | }, 30 | flipBoard3DEffect: { 31 | transform: 'translateZ(90px) scale(.91)', 32 | margin: theme.spacing(1), 33 | }, 34 | gridFlipBox: { 35 | height: '100%', 36 | }, 37 | typoFlipBoardTitle: { 38 | color: 'white', 39 | textShadow: '1px 1px #585A5A', 40 | textTransform: 'uppercase', 41 | }, 42 | typoFlipBoardSubtitle: { 43 | color: 'white', 44 | }, 45 | }), 46 | ); 47 | 48 | interface ICardProps { 49 | title: string; 50 | content: string; 51 | image: string; 52 | link: string; 53 | } 54 | 55 | interface IFlipCardListProps { 56 | listCard: ICardProps[]; 57 | } 58 | 59 | export default function flipCardList({ listCard }: IFlipCardListProps) { 60 | const classes = useStyles(); 61 | 62 | return ( 63 | <> 64 | {listCard.map((card, key) => ( 65 | 66 | 67 | 71 | 72 | 73 | 74 | {card.title} 75 | 76 | 77 | 78 |
79 | } 80 | back={ 81 |
82 | 83 | 84 | 85 | {card.title} 86 | 87 | 88 | {card.content} 89 | 90 | 91 | 94 | 95 | 96 | 97 |
98 | } 99 | /> 100 | 101 |
102 | ))} 103 | 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /components/FlipCardList/index.tsx: -------------------------------------------------------------------------------- 1 | import FlipCardList from "./FlipCardList"; 2 | export default FlipCardList; 3 | -------------------------------------------------------------------------------- /components/Footer/Footer.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | import React from "react"; 3 | import Footer from "./Footer"; 4 | 5 | storiesOf("Cuistot/components/Footer", module).add("default", () =>