├── .gitignore ├── packages ├── auth │ ├── src │ │ ├── index.js │ │ ├── App.js │ │ ├── bootstrap.js │ │ └── components │ │ │ ├── Signin.js │ │ │ └── Signup.js │ ├── public │ │ └── index.html │ ├── config │ │ ├── webpack.common.js │ │ ├── webpack.prod.js │ │ └── webpack.dev.js │ └── package.json ├── container │ ├── src │ │ ├── index.js │ │ ├── bootstrap.js │ │ ├── components │ │ │ ├── DashboardApp.js │ │ │ ├── Progress.js │ │ │ ├── MarketingApp.js │ │ │ ├── AuthApp.js │ │ │ └── Header.js │ │ └── App.js │ ├── public │ │ └── index.html │ ├── config │ │ ├── webpack.common.js │ │ ├── webpack.prod.js │ │ └── webpack.dev.js │ └── package.json ├── dashboard │ ├── src │ │ ├── index.js │ │ ├── bootstrap.js │ │ └── components │ │ │ └── Dashboard.vue │ ├── public │ │ └── index.html │ ├── config │ │ ├── webpack.prod.js │ │ ├── webpack.common.js │ │ └── webpack.dev.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 └── .github └── workflows ├── auth.yml ├── dashboard.yml ├── marketing.yml └── container.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store -------------------------------------------------------------------------------- /packages/auth/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); 2 | -------------------------------------------------------------------------------- /packages/container/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); 2 | -------------------------------------------------------------------------------- /packages/dashboard/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); 2 | -------------------------------------------------------------------------------- /packages/marketing/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); 2 | -------------------------------------------------------------------------------- /packages/container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /packages/auth/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /packages/dashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /packages/marketing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /packages/container/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.querySelector('#root')); 6 | -------------------------------------------------------------------------------- /packages/container/src/components/DashboardApp.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'dashboard/DashboardApp'; 2 | import React, { useRef, useEffect } from 'react'; 3 | 4 | export default () => { 5 | const ref = useRef(null); 6 | 7 | useEffect(() => { 8 | mount(ref.current); 9 | }, []); 10 | 11 | return
; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/auth/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 | }; 18 | -------------------------------------------------------------------------------- /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 | }; 18 | -------------------------------------------------------------------------------- /packages/container/src/components/Progress.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles, createStyles } from '@material-ui/core/styles'; 3 | import LinearProgress from '@material-ui/core/LinearProgress'; 4 | 5 | const useStyles = makeStyles((theme) => { 6 | return createStyles({ 7 | bar: { 8 | width: '100%', 9 | '& > * + *': { 10 | marginTop: theme.spacing(2), 11 | }, 12 | }, 13 | }); 14 | }); 15 | 16 | export default () => { 17 | const classes = useStyles(); 18 | 19 | return ( 20 |
21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/container/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | 3 | module.exports = { 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.m?js$/, 8 | exclude: /node_modules/, 9 | use: { 10 | loader: 'babel-loader', 11 | options: { 12 | presets: ['@babel/preset-react', '@babel/preset-env'], 13 | plugins: ['@babel/plugin-transform-runtime'], 14 | }, 15 | }, 16 | }, 17 | ], 18 | }, 19 | plugins: [ 20 | new HtmlWebpackPlugin({ 21 | template: './public/index.html', 22 | }), 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /packages/dashboard/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import Dashboard from './components/Dashboard.vue'; 3 | 4 | // Mount function to start up the app 5 | const mount = (el) => { 6 | const app = createApp(Dashboard); 7 | app.mount(el); 8 | }; 9 | 10 | // If we are in development and in isolation, 11 | // call mount immediately 12 | if (process.env.NODE_ENV === 'development') { 13 | const devRoot = document.querySelector('#_dashboard-dev-root'); 14 | 15 | if (devRoot) { 16 | mount(devRoot); 17 | } 18 | } 19 | 20 | // We are running through container 21 | // and we should export the mount function 22 | export { mount }; 23 | -------------------------------------------------------------------------------- /packages/auth/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const packageJson = require('../package.json'); 4 | const commonConfig = require('./webpack.common'); 5 | 6 | const prodConfig = { 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | publicPath: '/auth/latest/', 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: 'auth', 15 | filename: 'remoteEntry.js', 16 | exposes: { 17 | './AuthApp': './src/bootstrap', 18 | }, 19 | shared: packageJson.dependencies, 20 | }), 21 | ], 22 | }; 23 | 24 | module.exports = merge(commonConfig, prodConfig); 25 | -------------------------------------------------------------------------------- /packages/container/src/components/MarketingApp.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'marketing/MarketingApp'; 2 | import React, { useRef, useEffect } from 'react'; 3 | import { useHistory } from 'react-router-dom'; 4 | 5 | export default () => { 6 | const ref = useRef(null); 7 | const history = useHistory(); 8 | 9 | useEffect(() => { 10 | const { onParentNavigate } = mount(ref.current, { 11 | initialPath: history.location.pathname, 12 | onNavigate: ({ pathname: nextPathname }) => { 13 | const { pathname } = history.location; 14 | 15 | if (pathname !== nextPathname) { 16 | history.push(nextPathname); 17 | } 18 | }, 19 | }); 20 | 21 | history.listen(onParentNavigate); 22 | }, []); 23 | 24 | return
; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/dashboard/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const packageJson = require('../package.json'); 4 | const commonConfig = require('./webpack.common'); 5 | 6 | const prodConfig = { 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | publicPath: '/dashboard/latest/', 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: 'dashboard', 15 | filename: 'remoteEntry.js', 16 | exposes: { 17 | './DashboardApp': './src/bootstrap', 18 | }, 19 | shared: packageJson.dependencies, 20 | }), 21 | ], 22 | }; 23 | 24 | module.exports = merge(commonConfig, prodConfig); 25 | -------------------------------------------------------------------------------- /packages/marketing/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); 3 | const packageJson = require('../package.json'); 4 | const commonConfig = require('./webpack.common'); 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 | -------------------------------------------------------------------------------- /packages/container/src/components/AuthApp.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'auth/AuthApp'; 2 | import React, { useRef, useEffect } from 'react'; 3 | import { useHistory } from 'react-router-dom'; 4 | 5 | export default ({ onSignIn }) => { 6 | const ref = useRef(null); 7 | const history = useHistory(); 8 | 9 | useEffect(() => { 10 | const { onParentNavigate } = mount(ref.current, { 11 | initialPath: history.location.pathname, 12 | onNavigate: ({ pathname: nextPathname }) => { 13 | const { pathname } = history.location; 14 | 15 | if (pathname !== nextPathname) { 16 | history.push(nextPathname); 17 | } 18 | }, 19 | onSignIn, 20 | }); 21 | 22 | history.listen(onParentNavigate); 23 | }, []); 24 | 25 | return
; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/marketing/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route, Router } from 'react-router-dom'; 3 | import { 4 | StylesProvider, 5 | createGenerateClassName, 6 | } from '@material-ui/core/styles'; 7 | 8 | import Landing from './components/Landing'; 9 | import Pricing from './components/Pricing'; 10 | 11 | const generateClassName = createGenerateClassName({ 12 | productionPrefix: 'ma', 13 | }); 14 | 15 | export default ({ history }) => { 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 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/auth/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route, Router } from 'react-router-dom'; 3 | import { 4 | StylesProvider, 5 | createGenerateClassName, 6 | } from '@material-ui/core/styles'; 7 | 8 | import Signin from './components/Signin'; 9 | import Signup from './components/Signup'; 10 | 11 | const generateClassName = createGenerateClassName({ 12 | productionPrefix: 'au', 13 | }); 14 | 15 | export default ({ history, onSignIn }) => { 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /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'); 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 | auth: `auth@${domain}/auth/latest/remoteEntry.js`, 20 | dashboard: `dashboard@${domain}/dashboard/latest/remoteEntry.js`, 21 | }, 22 | shared: packageJson.dependencies, 23 | }), 24 | ], 25 | }; 26 | 27 | module.exports = merge(commonConfig, prodConfig); 28 | -------------------------------------------------------------------------------- /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/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'); 4 | const packageJson = require('../package.json'); 5 | 6 | const devConfig = { 7 | mode: 'development', 8 | output: { 9 | publicPath: 'http://localhost:8080/', 10 | }, 11 | devServer: { 12 | port: 8080, 13 | historyApiFallback: { 14 | index: 'index.html', 15 | }, 16 | }, 17 | plugins: [ 18 | new ModuleFederationPlugin({ 19 | name: 'container', 20 | remotes: { 21 | marketing: 'marketing@http://localhost:8081/remoteEntry.js', 22 | auth: 'auth@http://localhost:8082/remoteEntry.js', 23 | dashboard: 'dashboard@http://localhost:8083/remoteEntry.js', 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | ], 28 | }; 29 | 30 | module.exports = merge(commonConfig, devConfig); 31 | -------------------------------------------------------------------------------- /packages/auth/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'); 5 | const packageJson = require('../package.json'); 6 | 7 | const devConfig = { 8 | mode: 'development', 9 | output: { 10 | publicPath: 'http://localhost:8082/', 11 | }, 12 | devServer: { 13 | port: 8082, 14 | historyApiFallback: { 15 | index: 'index.html', 16 | }, 17 | }, 18 | plugins: [ 19 | new ModuleFederationPlugin({ 20 | name: 'auth', 21 | filename: 'remoteEntry.js', 22 | exposes: { 23 | './AuthApp': './src/bootstrap', 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | new HtmlWebpackPlugin({ 28 | template: './public/index.html', 29 | }), 30 | ], 31 | }; 32 | 33 | module.exports = merge(commonConfig, devConfig); 34 | -------------------------------------------------------------------------------- /.github/workflows/auth.yml: -------------------------------------------------------------------------------- 1 | name: deploy-auth 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'packages/auth/**' 9 | 10 | defaults: 11 | run: 12 | working-directory: packages/auth 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - uses: chrislennon/action-aws-cli@v1.1 24 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/auth/latest 25 | env: 26 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 27 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 28 | 29 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/auth/latest/remoteEntry.js" 30 | env: 31 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 32 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 33 | -------------------------------------------------------------------------------- /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'); 5 | const packageJson = require('../package.json'); 6 | 7 | const devConfig = { 8 | mode: 'development', 9 | output: { 10 | publicPath: 'http://localhost:8081/', 11 | }, 12 | devServer: { 13 | port: 8081, 14 | historyApiFallback: { 15 | index: 'index.html', 16 | }, 17 | }, 18 | plugins: [ 19 | new ModuleFederationPlugin({ 20 | name: 'marketing', 21 | filename: 'remoteEntry.js', 22 | exposes: { 23 | './MarketingApp': './src/bootstrap', 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | new HtmlWebpackPlugin({ 28 | template: './public/index.html', 29 | }), 30 | ], 31 | }; 32 | 33 | module.exports = merge(commonConfig, devConfig); 34 | -------------------------------------------------------------------------------- /.github/workflows/dashboard.yml: -------------------------------------------------------------------------------- 1 | name: deploy-dashboard 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'packages/dashboard/**' 9 | 10 | defaults: 11 | run: 12 | working-directory: packages/dashboard 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - uses: chrislennon/action-aws-cli@v1.1 24 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/dashboard/latest 25 | env: 26 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 27 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 28 | 29 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/dashboard/latest/remoteEntry.js" 30 | env: 31 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 32 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 33 | -------------------------------------------------------------------------------- /.github/workflows/marketing.yml: -------------------------------------------------------------------------------- 1 | name: deploy-marketing 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'packages/marketing/**' 9 | 10 | defaults: 11 | run: 12 | working-directory: packages/marketing 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - uses: chrislennon/action-aws-cli@v1.1 24 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/marketing/latest 25 | env: 26 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 27 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 28 | 29 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/marketing/latest/remoteEntry.js" 30 | env: 31 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 32 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 33 | -------------------------------------------------------------------------------- /packages/dashboard/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const { VueLoaderPlugin } = require('vue-loader'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | output: { 6 | filename: '[name].[contenthash].js', 7 | }, 8 | resolve: { 9 | extensions: ['.js', '.vue'], 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)$/i, 15 | use: [{ loader: 'file-loader' }], 16 | }, 17 | { 18 | test: /\.vue$/, 19 | use: 'vue-loader', 20 | }, 21 | { 22 | test: /\.scss|\.css$/, 23 | use: ['vue-style-loader', 'style-loader', 'css-loader', 'sass-loader'], 24 | }, 25 | { 26 | test: /\.m?js$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['@babel/preset-env'], 32 | plugins: ['@babel/plugin-transform-runtime'], 33 | }, 34 | }, 35 | }, 36 | ], 37 | }, 38 | plugins: [new VueLoaderPlugin()], 39 | }; 40 | -------------------------------------------------------------------------------- /packages/dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 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 | "chart.js": "^2.9.4", 10 | "primeflex": "^2.0.0", 11 | "primeicons": "^4.0.0", 12 | "primevue": "^3.0.1", 13 | "vue": "^3.0.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 | "@vue/compiler-sfc": "^3.0.2", 20 | "babel-loader": "^8.1.0", 21 | "css-loader": "^5.0.0", 22 | "file-loader": "^6.2.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "node-sass": "^4.14.1", 25 | "sass-loader": "^10.0.4", 26 | "style-loader": "^2.0.0", 27 | "vue-loader": "^16.0.0-beta.9", 28 | "vue-style-loader": "^4.1.2", 29 | "webpack": "^5.4.0", 30 | "webpack-cli": "^4.1.0", 31 | "webpack-dev-server": "^3.11.0", 32 | "webpack-merge": "^5.2.0" 33 | } 34 | } -------------------------------------------------------------------------------- /packages/dashboard/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'); 5 | const packageJson = require('../package.json'); 6 | 7 | const devConfig = { 8 | mode: 'development', 9 | output: { 10 | publicPath: 'http://localhost:8083/', 11 | }, 12 | devServer: { 13 | port: 8083, 14 | historyApiFallback: { 15 | index: 'index.html', 16 | }, 17 | headers: { 18 | 'Access-Control-Allow-Origin': '*', 19 | }, 20 | }, 21 | plugins: [ 22 | new ModuleFederationPlugin({ 23 | name: 'dashboard', 24 | filename: 'remoteEntry.js', 25 | exposes: { 26 | './DashboardApp': './src/bootstrap', 27 | }, 28 | shared: packageJson.dependencies, 29 | }), 30 | new HtmlWebpackPlugin({ 31 | template: './public/index.html', 32 | }), 33 | ], 34 | }; 35 | 36 | module.exports = merge(commonConfig, devConfig); 37 | -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | name: deploy-container 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'packages/container/**' 9 | 10 | defaults: 11 | run: 12 | working-directory: packages/container 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | env: 23 | PRODUCTION_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }} 24 | 25 | - uses: shinyinc/action-aws-cli@v1.2 26 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/container/latest 27 | env: 28 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 29 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 30 | 31 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/container/latest/index.html" 32 | env: 33 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 34 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 35 | -------------------------------------------------------------------------------- /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 to start up the app 7 | const mount = (el, { onNavigate, defaultHistory, initialPath }) => { 8 | const history = 9 | defaultHistory || 10 | createMemoryHistory({ 11 | initialEntries: [initialPath], 12 | }); 13 | 14 | if (onNavigate) { 15 | history.listen(onNavigate); 16 | } 17 | 18 | ReactDOM.render(, el); 19 | 20 | return { 21 | onParentNavigate({ pathname: nextPathname }) { 22 | const { pathname } = history.location; 23 | 24 | if (pathname !== nextPathname) { 25 | history.push(nextPathname); 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 | // We are running through container 42 | // and we should export the mount function 43 | export { mount }; 44 | -------------------------------------------------------------------------------- /packages/auth/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 to start up the app 7 | const mount = (el, { onSignIn, onNavigate, defaultHistory, initialPath }) => { 8 | const history = 9 | defaultHistory || 10 | createMemoryHistory({ 11 | initialEntries: [initialPath], 12 | }); 13 | 14 | if (onNavigate) { 15 | history.listen(onNavigate); 16 | } 17 | 18 | ReactDOM.render(, el); 19 | 20 | return { 21 | onParentNavigate({ pathname: nextPathname }) { 22 | const { pathname } = history.location; 23 | 24 | if (pathname !== nextPathname) { 25 | history.push(nextPathname); 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('#_auth-dev-root'); 35 | 36 | if (devRoot) { 37 | mount(devRoot, { defaultHistory: createBrowserHistory() }); 38 | } 39 | } 40 | 41 | // We are running through container 42 | // and we should export the mount function 43 | export { mount }; 44 | -------------------------------------------------------------------------------- /packages/container/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense, useState, useEffect } from 'react'; 2 | import { Router, Route, Switch, Redirect } from 'react-router-dom'; 3 | import { 4 | StylesProvider, 5 | createGenerateClassName, 6 | } from '@material-ui/core/styles'; 7 | import { createBrowserHistory } from 'history'; 8 | 9 | import Progress from './components/Progress'; 10 | import Header from './components/Header'; 11 | 12 | const MarketingLazy = lazy(() => import('./components/MarketingApp')); 13 | const AuthLazy = lazy(() => import('./components/AuthApp')); 14 | const DashboardLazy = lazy(() => import('./components/DashboardApp')); 15 | 16 | const generateClassName = createGenerateClassName({ 17 | productionPrefix: 'co', 18 | }); 19 | 20 | const history = createBrowserHistory(); 21 | 22 | export default () => { 23 | const [isSignedIn, setIsSignedIn] = useState(false); 24 | 25 | useEffect(() => { 26 | if (isSignedIn) { 27 | history.push('/dashboard'); 28 | } 29 | }, [isSignedIn]); 30 | 31 | return ( 32 | 33 | 34 |
35 |
setIsSignedIn(false)} 37 | isSignedIn={isSignedIn} 38 | /> 39 | }> 40 | 41 | 42 | setIsSignedIn(true)} /> 43 | 44 | 45 | {!isSignedIn && } 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /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({ isSignedIn, onSignOut }) { 58 | const classes = useStyles(); 59 | 60 | const onClick = () => { 61 | if (isSignedIn && onSignOut) { 62 | onSignOut(); 63 | } 64 | }; 65 | 66 | return ( 67 | 68 | 74 | 75 | 82 | App 83 | 84 | 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /packages/auth/src/components/Signin.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from '@material-ui/core/Avatar'; 3 | import Button from '@material-ui/core/Button'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Box from '@material-ui/core/Box'; 9 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import { Link } from 'react-router-dom'; 14 | 15 | function Copyright() { 16 | return ( 17 | 18 | {'Copyright © '} 19 | 20 | Your Website 21 | {' '} 22 | {new Date().getFullYear()} 23 | {'.'} 24 | 25 | ); 26 | } 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | '@global': { 30 | a: { 31 | textDecoration: 'none', 32 | }, 33 | }, 34 | paper: { 35 | marginTop: theme.spacing(8), 36 | display: 'flex', 37 | flexDirection: 'column', 38 | alignItems: 'center', 39 | }, 40 | avatar: { 41 | margin: theme.spacing(1), 42 | backgroundColor: theme.palette.secondary.main, 43 | }, 44 | form: { 45 | width: '100%', 46 | marginTop: theme.spacing(1), 47 | }, 48 | submit: { 49 | margin: theme.spacing(3, 0, 2), 50 | }, 51 | })); 52 | 53 | export default function SignIn({ onSignIn }) { 54 | const classes = useStyles(); 55 | 56 | return ( 57 | 58 |
59 | 60 | 61 | 62 | 63 | Sign in 64 | 65 |
e.preventDefault()} 67 | className={classes.form} 68 | noValidate 69 | > 70 | 81 | 92 | } 94 | label="Remember me" 95 | /> 96 | 106 | 107 | 108 | {"Don't have an account? Sign Up"} 109 | 110 | 111 | 112 |
113 | 114 | 115 | 116 |
117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /packages/auth/src/components/Signup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from '@material-ui/core/Avatar'; 3 | import Button from '@material-ui/core/Button'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Box from '@material-ui/core/Box'; 9 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import { Link } from 'react-router-dom'; 14 | 15 | function Copyright() { 16 | return ( 17 | 18 | {'Copyright © '} 19 | Your Website {new Date().getFullYear()} 20 | {'.'} 21 | 22 | ); 23 | } 24 | 25 | const useStyles = makeStyles((theme) => ({ 26 | '@global': { 27 | a: { 28 | textDecoration: 'none', 29 | }, 30 | }, 31 | paper: { 32 | marginTop: theme.spacing(8), 33 | display: 'flex', 34 | flexDirection: 'column', 35 | alignItems: 'center', 36 | }, 37 | avatar: { 38 | margin: theme.spacing(1), 39 | backgroundColor: theme.palette.secondary.main, 40 | }, 41 | form: { 42 | width: '100%', 43 | marginTop: theme.spacing(3), 44 | }, 45 | submit: { 46 | margin: theme.spacing(3, 0, 2), 47 | }, 48 | })); 49 | 50 | export default function SignUp({ onSignIn }) { 51 | const classes = useStyles(); 52 | 53 | return ( 54 | 55 |
56 | 57 | 58 | 59 | 60 | Sign up 61 | 62 |
e.preventDefault()} 64 | className={classes.form} 65 | noValidate 66 | > 67 | 68 | 69 | 79 | 80 | 81 | 90 | 91 | 92 | 101 | 102 | 103 | 113 | 114 | 115 | } 117 | label="I want to receive inspiration, marketing promotions and updates via email." 118 | /> 119 | 120 | 121 | 131 | 132 | 133 | Already have an account? Sign in 134 | 135 | 136 |
137 |
138 | 139 | 140 | 141 |
142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/dashboard/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 254 | 255 | 345 | 346 | 753 | --------------------------------------------------------------------------------