├── .gitignore ├── packages ├── container │ ├── src │ │ ├── index.js │ │ ├── bootstrap.js │ │ ├── App.js │ │ └── components │ │ │ ├── MarketingApp.js │ │ │ └── Header.js │ ├── public │ │ └── index.html │ ├── config │ │ ├── webpack.common.js │ │ ├── webpack.dev.js │ │ └── webpack.prod.js │ └── package.json ├── marketing │ ├── src │ │ ├── index.js │ │ ├── App.js │ │ ├── bootstrap.js │ │ └── components │ │ │ ├── Landing.js │ │ │ └── Pricing.js │ ├── public │ │ └── index.html │ ├── config │ │ ├── webpack.common.js │ │ ├── webpack.prod.js │ │ └── webpack.dev.js │ └── package.json ├── auth │ └── package.json └── dashboard │ └── package.json └── .github └── workflows ├── marketing.yml └── container.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store -------------------------------------------------------------------------------- /packages/container/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); -------------------------------------------------------------------------------- /packages/marketing/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); -------------------------------------------------------------------------------- /packages/container/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.querySelector("#root")); -------------------------------------------------------------------------------- /packages/container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Container App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /packages/marketing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Marketing 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /packages/marketing/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.m?js$/, 6 | exclude: /(node_modules)/, 7 | use: { 8 | loader: 'babel-loader', 9 | options: { 10 | presets: ['@babel/preset-react', '@babel/preset-env'], 11 | plugins: ['@babel/plugin-transform-runtime'] 12 | } 13 | } 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /packages/container/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 2 | 3 | 4 | module.exports = { 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.m?js$/, 9 | exclude: /(node_modules)/, 10 | use: { 11 | loader: 'babel-loader', 12 | options: { 13 | presets: ['@babel/preset-react', '@babel/preset-env'], 14 | plugins: ['@babel/plugin-transform-runtime'] 15 | } 16 | } 17 | } 18 | ] 19 | }, 20 | plugins: [ 21 | new HTMLWebpackPlugin({ 22 | template: './public/index.html', 23 | }), 24 | ], 25 | } -------------------------------------------------------------------------------- /packages/container/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter, BrowserRouterProps } from 'react-router-dom'; 3 | import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles'; 4 | import MarketingApp from './components/MarketingApp'; 5 | import Header from './components/Header'; 6 | 7 | const generateClassName = createGenerateClassName({ 8 | productionPrefix: "co", 9 | }) 10 | 11 | export default () => { 12 | return ( 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | ) 22 | } -------------------------------------------------------------------------------- /packages/container/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const commonConfig = require('./webpack.common.js'); 4 | const packageJson = require('../package.json'); 5 | 6 | 7 | const devConfig = { 8 | mode: 'development', 9 | devServer: { 10 | port: 8080, 11 | historyApiFallback: { 12 | index: 'index.html' 13 | } 14 | }, 15 | plugins: [ 16 | new ModuleFederationPlugin({ 17 | name: 'container', 18 | remotes: { 19 | 'marketing': 'marketing@http://localhost:8081/remoteEntry.js' 20 | }, 21 | shared: packageJson.dependencies 22 | }) 23 | ] 24 | } 25 | 26 | module.exports = merge(commonConfig, devConfig); -------------------------------------------------------------------------------- /packages/marketing/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch, Route, Router } from "react-router-dom"; 3 | import { StylesProvider, createGenerateClassName } from "@material-ui/core/styles"; 4 | 5 | import Landing from "./components/Landing"; 6 | import Pricing from "./components/Pricing"; 7 | 8 | const generateClassName = createGenerateClassName({ 9 | productionPrefix: "ma", 10 | }) 11 | 12 | export default ({history}) => { 13 | return
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | } -------------------------------------------------------------------------------- /packages/marketing/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const commonConfig = require('./webpack.common.js'); 4 | const packageJson = require('../package.json'); 5 | 6 | const prodConfig = { 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | publicPath: '/marketing/latest/' 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: 'marketing', 15 | filename: 'remoteEntry.js', 16 | exposes: { 17 | './MarketingApp': './src/bootstrap' 18 | }, 19 | shared: packageJson.dependencies 20 | }) 21 | ] 22 | } 23 | 24 | module.exports = merge(commonConfig, prodConfig) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/container/src/components/MarketingApp.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'marketing/MarketingApp'; 3 | import { useHistory } from 'react-router-dom'; 4 | 5 | export default () => { 6 | // get ref 7 | const ref = React.useRef(null); 8 | const history = useHistory(); 9 | 10 | // use effect 11 | React.useEffect(() => { 12 | const { onParentNavigate } = mount(ref.current, { 13 | onNavigate: ({pathname: nextPathname}) => { 14 | const {pathname} = history.location; 15 | 16 | if( pathname !== nextPathname ) { 17 | history.push(nextPathname); 18 | } 19 | } 20 | }) 21 | 22 | history.listen(onParentNavigate); 23 | 24 | return () => { 25 | history.listen(null); 26 | } 27 | }, []); 28 | 29 | return
; 30 | } -------------------------------------------------------------------------------- /packages/container/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const commonConfig = require('./webpack.common.js'); 4 | const packageJson = require('../package.json'); 5 | 6 | const domain = process.env.PRODUCTION_DOMAIN; 7 | 8 | const prodConfig = { 9 | mode: 'production', 10 | output: { 11 | filename: '[name].[contenthash].js', 12 | publicPath: '/container/latest/' 13 | }, 14 | plugins: [ 15 | new ModuleFederationPlugin({ 16 | name: 'container', 17 | remotes: { 18 | marketing: `marketing@${domain}/marketing/latest/remoteEntry.js`, 19 | }, 20 | shared: packageJson.dependencies 21 | }) 22 | ], 23 | } 24 | 25 | module.exports = merge(commonConfig, prodConfig); -------------------------------------------------------------------------------- /packages/marketing/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 3 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 4 | const commonConfig = require('./webpack.common.js'); 5 | const packageJson = require('../package.json'); 6 | 7 | const devConfig = { 8 | mode: 'development', 9 | devServer: { 10 | port: 8081, 11 | historyApiFallback: { 12 | index: 'index.html' 13 | } 14 | }, 15 | plugins: [ 16 | new ModuleFederationPlugin({ 17 | name: 'marketing', 18 | filename: 'remoteEntry.js', 19 | exposes: { 20 | './MarketingApp': './src/bootstrap' 21 | }, 22 | shared: packageJson.dependencies 23 | }), 24 | new HTMLWebpackPlugin({ 25 | template: './public/index.html', 26 | }) 27 | ] 28 | } 29 | 30 | module.exports = merge(commonConfig, devConfig); -------------------------------------------------------------------------------- /packages/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "test": "echo \"Error: no test specified\" && exit 1" 6 | }, 7 | "dependencies": { 8 | "@material-ui/core": "^4.11.0", 9 | "@material-ui/icons": "^4.9.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-router-dom": "^5.2.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.12.3", 16 | "@babel/plugin-transform-runtime": "^7.12.1", 17 | "@babel/preset-env": "^7.12.1", 18 | "@babel/preset-react": "^7.12.1", 19 | "babel-loader": "^8.1.0", 20 | "clean-webpack-plugin": "^3.0.0", 21 | "css-loader": "^5.0.0", 22 | "html-webpack-plugin": "^4.5.0", 23 | "style-loader": "^2.0.0", 24 | "webpack": "^5.4.0", 25 | "webpack-cli": "^4.1.0", 26 | "webpack-dev-server": "^3.11.0", 27 | "webpack-merge": "^5.2.0" 28 | } 29 | } -------------------------------------------------------------------------------- /packages/container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "@material-ui/core": "^4.11.0", 10 | "@material-ui/icons": "^4.9.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@babel/preset-react": "^7.12.1", 20 | "babel-loader": "^8.1.0", 21 | "clean-webpack-plugin": "^3.0.0", 22 | "css-loader": "^5.0.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "style-loader": "^2.0.0", 25 | "webpack": "^5.4.0", 26 | "webpack-cli": "^4.1.0", 27 | "webpack-dev-server": "^3.11.0", 28 | "webpack-merge": "^5.2.0" 29 | } 30 | } -------------------------------------------------------------------------------- /packages/marketing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marketing", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "@material-ui/core": "^4.11.0", 10 | "@material-ui/icons": "^4.9.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@babel/preset-react": "^7.12.1", 20 | "babel-loader": "^8.1.0", 21 | "clean-webpack-plugin": "^3.0.0", 22 | "css-loader": "^5.0.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "style-loader": "^2.0.0", 25 | "webpack": "^5.4.0", 26 | "webpack-cli": "^4.1.0", 27 | "webpack-dev-server": "^3.11.0", 28 | "webpack-merge": "^5.2.0" 29 | } 30 | } -------------------------------------------------------------------------------- /packages/dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "test": "echo \"Error: no test specified\" && exit 1" 6 | }, 7 | "dependencies": { 8 | "chart.js": "^2.9.4", 9 | "primeflex": "^2.0.0", 10 | "primeicons": "^4.0.0", 11 | "primevue": "3.0.1", 12 | "vue": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.12.3", 16 | "@babel/plugin-transform-runtime": "^7.12.1", 17 | "@babel/preset-env": "^7.12.1", 18 | "@vue/compiler-sfc": "^3.0.2", 19 | "babel-loader": "^8.1.0", 20 | "css-loader": "^5.0.0", 21 | "file-loader": "^6.2.0", 22 | "html-webpack-plugin": "^4.5.0", 23 | "node-sass": "^4.14.1", 24 | "sass-loader": "^10.0.4", 25 | "style-loader": "^2.0.0", 26 | "vue-loader": "^16.0.0-beta.9", 27 | "vue-style-loader": "^4.1.2", 28 | "webpack": "^5.4.0", 29 | "webpack-cli": "^4.1.0", 30 | "webpack-dev-server": "^3.11.0", 31 | "webpack-merge": "^5.2.0" 32 | } 33 | } -------------------------------------------------------------------------------- /packages/marketing/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { createMemoryHistory, createBrowserHistory } from 'history' 4 | import App from './App'; 5 | 6 | // Mount Function 7 | const mount = (el, { onNavigate, defaultHistory }) => { 8 | const history = defaultHistory || createMemoryHistory(); 9 | 10 | if( onNavigate ) { 11 | history.listen(onNavigate) 12 | } 13 | 14 | ReactDOM.render( 15 | , 16 | el 17 | ); 18 | 19 | return { 20 | onParentNavigate({pathname: nextPathname}) { 21 | console.log('Containber just navigated') 22 | const { pathname } = history.location; 23 | if( pathname !== nextPathname ) { 24 | history.push(nextPathname); 25 | } 26 | } 27 | } 28 | } 29 | 30 | 31 | // if we are in development and in isolation, 32 | // call mount immediately 33 | if (process.env.NODE_ENV === 'development') { 34 | const devRoot = document.querySelector('#_marketing-dev-root'); 35 | 36 | if (devRoot) { 37 | mount(devRoot, {defaultHistory: createBrowserHistory()}); 38 | } 39 | } 40 | 41 | export { mount }; -------------------------------------------------------------------------------- /.github/workflows/marketing.yml: -------------------------------------------------------------------------------- 1 | name: deploy-marketing 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'packages/marketing/**' 9 | defaults: 10 | run: 11 | working-directory: packages/marketing 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - run: npm install 19 | - run: npm run build 20 | 21 | - uses: shinyinc/action-aws-cli@v1.2 22 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/marketing/latest 23 | env: 24 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 25 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 26 | AWS_DEFAULT_REGION: ${{ secrets.AWS_S3_BUCKET_REGION }} 27 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --paths "/marketing/latest/remoteEntry.js" 28 | env: 29 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | AWS_DEFAULT_REGION: ${{ secrets.AWS_S3_BUCKET_REGION }} -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | name: deploy-container 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'packages/container/**' 9 | defaults: 10 | run: 11 | working-directory: packages/container 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - run: npm install 19 | - run: npm run build 20 | env: 21 | PRODUCTION_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }} 22 | - uses: shinyinc/action-aws-cli@v1.2 23 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/container/latest 24 | env: 25 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 26 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 27 | AWS_DEFAULT_REGION: ${{ secrets.AWS_S3_BUCKET_REGION }} 28 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --paths "/container/latest/index.html" 29 | env: 30 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 31 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 32 | AWS_DEFAULT_REGION: ${{ secrets.AWS_S3_BUCKET_REGION }} -------------------------------------------------------------------------------- /packages/container/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppBar from '@material-ui/core/AppBar'; 3 | import Button from '@material-ui/core/Button'; 4 | import Toolbar from '@material-ui/core/Toolbar'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import { makeStyles } from '@material-ui/core/styles'; 7 | import { Link as RouterLink } from 'react-router-dom'; 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | '@global': { 11 | ul: { 12 | margin: 0, 13 | padding: 0, 14 | listStyle: 'none', 15 | }, 16 | a: { 17 | textDecoration: 'none', 18 | }, 19 | }, 20 | appBar: { 21 | borderBottom: `1px solid ${theme.palette.divider}`, 22 | }, 23 | toolbar: { 24 | flexWrap: 'wrap', 25 | justifyContent: 'space-between', 26 | }, 27 | link: { 28 | margin: theme.spacing(1, 1.5), 29 | }, 30 | heroContent: { 31 | padding: theme.spacing(8, 0, 6), 32 | }, 33 | cardHeader: { 34 | backgroundColor: 35 | theme.palette.type === 'light' 36 | ? theme.palette.grey[200] 37 | : theme.palette.grey[700], 38 | }, 39 | cardPricing: { 40 | display: 'flex', 41 | justifyContent: 'center', 42 | alignItems: 'baseline', 43 | marginBottom: theme.spacing(2), 44 | }, 45 | footer: { 46 | borderTop: `1px solid ${theme.palette.divider}`, 47 | marginTop: theme.spacing(8), 48 | paddingTop: theme.spacing(3), 49 | paddingBottom: theme.spacing(3), 50 | [theme.breakpoints.up('sm')]: { 51 | paddingTop: theme.spacing(6), 52 | paddingBottom: theme.spacing(6), 53 | }, 54 | }, 55 | })); 56 | 57 | export default function Header({ signedIn, onSignOut }) { 58 | const classes = useStyles(); 59 | 60 | const onClick = () => { 61 | if (signedIn && onSignOut) { 62 | onSignOut(); 63 | } 64 | }; 65 | 66 | return ( 67 | 68 | 74 | 75 | 82 | App 83 | 84 | 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /packages/marketing/src/components/Landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActions from '@material-ui/core/CardActions'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import CardMedia from '@material-ui/core/CardMedia'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import { makeStyles } from '@material-ui/core/styles'; 10 | import Container from '@material-ui/core/Container'; 11 | import MaterialLink from '@material-ui/core/Link'; 12 | import { Link } from 'react-router-dom'; 13 | 14 | function Copyright() { 15 | return ( 16 | 17 | {'Copyright © '} 18 | 19 | Your Website 20 | {' '} 21 | {new Date().getFullYear()} 22 | {'.'} 23 | 24 | ); 25 | } 26 | 27 | const useStyles = makeStyles((theme) => ({ 28 | '@global': { 29 | a: { 30 | textDecoration: 'none', 31 | }, 32 | }, 33 | icon: { 34 | marginRight: theme.spacing(2), 35 | }, 36 | heroContent: { 37 | backgroundColor: theme.palette.background.paper, 38 | padding: theme.spacing(8, 0, 6), 39 | }, 40 | heroButtons: { 41 | marginTop: theme.spacing(4), 42 | }, 43 | cardGrid: { 44 | paddingTop: theme.spacing(8), 45 | paddingBottom: theme.spacing(8), 46 | }, 47 | card: { 48 | height: '100%', 49 | display: 'flex', 50 | flexDirection: 'column', 51 | }, 52 | cardMedia: { 53 | paddingTop: '56.25%', // 16:9 54 | }, 55 | cardContent: { 56 | flexGrow: 1, 57 | }, 58 | footer: { 59 | backgroundColor: theme.palette.background.paper, 60 | padding: theme.spacing(6), 61 | }, 62 | })); 63 | 64 | const cards = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 65 | 66 | export default function Album() { 67 | const classes = useStyles(); 68 | 69 | return ( 70 | 71 |
72 | {/* Hero unit */} 73 |
74 | 75 | 82 | Home Page 83 | 84 | 90 | Something short and leading about the collection below—its 91 | contents, the creator, etc. Make it short and sweet, but not too 92 | short so folks don't simply skip over it entirely. 93 | 94 |
95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 108 | 109 | 110 | 111 |
112 |
113 |
114 | 115 | {/* End hero unit */} 116 | 117 | {cards.map((card) => ( 118 | 119 | 120 | 125 | 126 | 127 | Heading 128 | 129 | 130 | This is a media card. You can use this section to describe 131 | the content. 132 | 133 | 134 | 135 | 138 | 141 | 142 | 143 | 144 | ))} 145 | 146 | 147 |
148 | {/* Footer */} 149 |
150 | 151 | Footer 152 | 153 | 159 | Something here to give the footer a purpose! 160 | 161 | 162 |
163 | {/* End footer */} 164 |
165 | ); 166 | } 167 | -------------------------------------------------------------------------------- /packages/marketing/src/components/Pricing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActions from '@material-ui/core/CardActions'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import CardHeader from '@material-ui/core/CardHeader'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import StarIcon from '@material-ui/icons/StarBorder'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Link from '@material-ui/core/Link'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import Box from '@material-ui/core/Box'; 14 | import { Link as RouterLink } from 'react-router-dom'; 15 | 16 | function Copyright() { 17 | return ( 18 | 19 | {'Copyright © '} 20 | 21 | Your Website 22 | {' '} 23 | {new Date().getFullYear()} 24 | {'.'} 25 | 26 | ); 27 | } 28 | 29 | const useStyles = makeStyles((theme) => ({ 30 | '@global': { 31 | ul: { 32 | margin: 0, 33 | padding: 0, 34 | listStyle: 'none', 35 | }, 36 | }, 37 | toolbar: { 38 | flexWrap: 'wrap', 39 | }, 40 | toolbarTitle: { 41 | flexGrow: 1, 42 | }, 43 | link: { 44 | margin: theme.spacing(1, 1.5), 45 | }, 46 | heroContent: { 47 | padding: theme.spacing(8, 0, 6), 48 | }, 49 | cardHeader: { 50 | backgroundColor: 51 | theme.palette.type === 'light' 52 | ? theme.palette.grey[200] 53 | : theme.palette.grey[700], 54 | }, 55 | cardPricing: { 56 | display: 'flex', 57 | justifyContent: 'center', 58 | alignItems: 'baseline', 59 | marginBottom: theme.spacing(2), 60 | }, 61 | footer: { 62 | borderTop: `1px solid ${theme.palette.divider}`, 63 | marginTop: theme.spacing(8), 64 | paddingTop: theme.spacing(3), 65 | paddingBottom: theme.spacing(3), 66 | [theme.breakpoints.up('sm')]: { 67 | paddingTop: theme.spacing(6), 68 | paddingBottom: theme.spacing(6), 69 | }, 70 | }, 71 | })); 72 | 73 | const tiers = [ 74 | { 75 | title: 'Free', 76 | price: '0', 77 | description: [ 78 | '10 users included', 79 | '2 GB of storage', 80 | 'Help center access', 81 | 'Email support', 82 | ], 83 | buttonText: 'Sign up for free', 84 | buttonVariant: 'outlined', 85 | }, 86 | { 87 | title: 'Pro', 88 | subheader: 'Most popular', 89 | price: '15', 90 | description: [ 91 | '20 users included', 92 | '10 GB of storage', 93 | 'Help center access', 94 | 'Priority email support', 95 | ], 96 | buttonText: 'Get started', 97 | buttonVariant: 'contained', 98 | }, 99 | { 100 | title: 'Enterprise', 101 | price: '30', 102 | description: [ 103 | '50 users included', 104 | '30 GB of storage', 105 | 'Help center access', 106 | 'Phone & email support', 107 | ], 108 | buttonText: 'Contact us', 109 | buttonVariant: 'outlined', 110 | }, 111 | ]; 112 | const footers = [ 113 | { 114 | title: 'Company', 115 | description: ['Team', 'History', 'Contact us', 'Locations'], 116 | }, 117 | { 118 | title: 'Features', 119 | description: [ 120 | 'Cool stuff', 121 | 'Random feature', 122 | 'Team feature', 123 | 'Developer stuff', 124 | 'Another one', 125 | ], 126 | }, 127 | { 128 | title: 'Resources', 129 | description: [ 130 | 'Resource', 131 | 'Resource name', 132 | 'Another resource', 133 | 'Final resource', 134 | ], 135 | }, 136 | { 137 | title: 'Legal', 138 | description: ['Privacy policy', 'Terms of use'], 139 | }, 140 | ]; 141 | 142 | export default function Pricing() { 143 | const classes = useStyles(); 144 | 145 | return ( 146 | 147 | {/* Hero unit */} 148 | 149 | 156 | Pricing 157 | 158 | 164 | Quickly build an effective pricing table for your potential customers 165 | with this layout. It's built with default Material-UI components 166 | with little customization. 167 | 168 | 169 | {/* End hero unit */} 170 | 171 | 172 | {tiers.map((tier) => ( 173 | // Enterprise card is full width at sm breakpoint 174 | 181 | 182 | : null} 188 | className={classes.cardHeader} 189 | /> 190 | 191 |
192 | 193 | ${tier.price} 194 | 195 | 196 | /mo 197 | 198 |
199 |
    200 | {tier.description.map((line) => ( 201 | 207 | {line} 208 | 209 | ))} 210 |
211 |
212 | 213 | 221 | 222 |
223 |
224 | ))} 225 |
226 |
227 | {/* Footer */} 228 | 229 | 230 | {footers.map((footer) => ( 231 | 232 | 233 | {footer.title} 234 | 235 |
    236 | {footer.description.map((item) => ( 237 |
  • 238 | 239 | {item} 240 | 241 |
  • 242 | ))} 243 |
244 |
245 | ))} 246 |
247 | 248 | 249 | 250 |
251 | {/* End footer */} 252 |
253 | ); 254 | } 255 | --------------------------------------------------------------------------------