[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\", \"@babel/preset-react\"],\n  \"plugins\": [\"@babel/plugin-syntax-dynamic-import\"]\n}\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": [\"eslint:recommended\", \"plugin:react/recommended\"],\n  \"env\": {\n    \"browser\": true,\n    \"node\": true,\n    \"es6\": true\n  },\n  \"rules\": {\n    \"react/prop-types\": \"off\",\n    \"no-console\": \"off\",\n    \"react/no-children-prop\": \"off\",\n    \"jsx-quotes\": [\"error\", \"prefer-double\"],\n    \"react/no-unescaped-entities\": \"off\",\n    \"no-unused-vars\": \"off\"\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\n.env\n.env.production\nnode_modules\nbuild\nnpm-debug.log\n"
  },
  {
    "path": ".prettierrc",
    "content": "tabWidth: 2\nsemi: false\nsingleQuote: true\nprintWidth: 120\ntrailingComma: true\njsxBracketSameLine: true"
  },
  {
    "path": "README.md",
    "content": "# React Router 5 Course Material\n\nReact Router 5 is out and we have created this course so you can learn all about it. The course covers basic and advanced topics. We'll be building a Firebase-looking app that has complex nested layouts and interesting problems to solve.\n\n![Animated Demo](./firebase.gif)\n\n## What's New?\n\nIf you're wondering what's new in React Router 5? Not a lot, except internal optimizations and fixes for React 16.x. So you could also think of this as a React Router 4 course since the API is the same. [See more info on the React Router 5 release](https://reacttraining.com/blog/react-router-v5/).\n\n## Download and Install\n\nAfter downloading the repository from Github, enter the following commands into your command line from the project folder:\n\n```bash\nnpm install\nnpm start\n```\n\nThen go to [localhost:8000](http://localhost:8000)\n\n> Be sure to see notes on Lesson Branches below...\n\n## Each Lesson is a Git Branch\n\nTo view the code for a given lesson, _checkout_ the appropriate branch name. The branch will have the finished code from that lesson.\n\n### Installing lesson branches\n\nAll the branches are checked out to your local machine automatically when you do `npm install`. Just do a `git branch` to verify and see all branches after. If they didn't appear, try running `npm run branches` to download all the branches.\n\nTo view a branch: `git checkout [branch-name]`\n\n<hr />\n\nBranch names are minimal for easy typing:\n\n- **01-basics** - JSX Routing with `BrowserRouter` and `Route`\n- **02-basics** - Route Matching - Inclusive vs Exclusive (exact) and Switch\n- **03-basics** - `Link` (anchors)\n- **04-basics** - `BrowserRouter` vs `HashRouter`\n- **05-basics** - Dynamic (Parameter) Matching\n- **06-basics** - Nested Layout Strategy\n- **07-basics** - `match.url`\n- **08-basics** - `match.path`\n- **09-basics** - `NavLink`\n- **10-basics** - `withRouter` HoC\n- **11-basics** - Programmatic Navigation (History Object)\n- **12-basics** - URL Query Strings\n- **13-advanced** - Route Render Methods\n- **14-advanced** - React Router - Just Components ™\n- **15-advanced** - Authentication Strategy with Context\n- **16-advanced** - Authenticated Routes (Dynamic Routes)\n- **17-advanced** - Navigation With State\n- **18-advanced** - Prompt Before Route Changes\n- **19-advanced** - Animating Route Changes (Part One)\n- **20-advanced** - Animating Route Changes (Part Two)\n\n## Fake Database\n\nJust so we can mimic some data and pretend it's asynchronous, there's a `src/database.json`. Feel free to add more \"Firebase Projects\" if you want.\n\n## The CSS\n\nI used `className`. Who cares, it keeps the styling clutter out of the JS files since this is teaching material for routing.\n\n## Code Organization\n\nIn `/src` you'll see:\n\n- `layouts` for highly re-usable app-wide layouts\n- `styles` for Sass modules\n- `ui` is where I like to put re-usable \"leaf-types\" of components\n- `utils` is a catch all for React components that are more utilitarian in nature (and less UI in nature) and other general utils.\n\nAny other folders in `/src` is a section of the site, like `/auth` and `/projects` which correspond to `localhost:8000/auth` etc. I guess I could organize those into a `/pages` folder, but who likes deep nesting anyways?\n"
  },
  {
    "path": "config/env.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst React = require('react')\nconst packageJSON = require('../package.json')\n\nconst NODE_ENV = process.env.NODE_ENV\nif (!NODE_ENV) {\n  throw new Error(\n    'The NODE_ENV environment variable is required but was not specified.'\n  )\n}\n\n// Create list of possible env files\nconst envPath = path.resolve(process.cwd(), '.env')\nconst dotenvFiles = [\n  `${envPath}.${NODE_ENV}`,\n  envPath,\n]\n\n// Load environment variables from .env* files. Suppress warnings using silent\n// if this file is missing. dotenv will never modify any environment variables\n// that have already been set.\n// https://github.com/motdotla/dotenv\ndotenvFiles.forEach(dotenvFile => {\n  if (fs.existsSync(dotenvFile)) {\n    require('dotenv').config({\n      path: dotenvFile,\n    })\n  }\n})\n\n// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be\n// injected into the application via DefinePlugin in Webpack configuration.\nconst REACT_APP = /^REACT_APP_/i\n\nfunction getClientEnvironment() {\n  const raw = Object.keys(process.env)\n    .filter(key => REACT_APP.test(key))\n    .reduce(\n      (env, key) => {\n        env[key] = process.env[key]\n        return env\n      },\n      {\n        // Useful for determining whether we’re running in production mode.\n        // Most importantly, it switches React into the correct mode.\n        NODE_ENV: process.env.NODE_ENV || 'development',\n        // Determines where static assets are located. In development, Webpack\n        // DevServer puts them in a virtual folder called `/static`. But we can\n        // customise that with .env for production in situations where our assets\n        // might be on a CDN\n        STATIC_ASSET_URL: process.env.STATIC_ASSET_URL || '/static',\n        REACT_VERSION: React.version,\n        APP_VERSION: packageJSON.version\n      }\n    )\n\n  // Stringify all values so we can feed into Webpack DefinePlugin\n  const stringified = {\n    'process.env': Object.keys(raw).reduce(\n      (env, key) => {\n        env[key] = JSON.stringify(raw[key])\n        return env\n      },\n      {}\n    ),\n  }\n\n  return { raw, stringified }\n}\n\nmodule.exports = getClientEnvironment"
  },
  {
    "path": "config/setup.js",
    "content": "/* global process */\n\nif (typeof Promise === 'undefined') {\n  // Rejection tracking prevents a common issue where React gets into an\n  // inconsistent state due to an error, but it gets swallowed by a Promise,\n  // and the user has no idea what causes React's erratic future behavior.\n  // Only do this in dev for performance reasons\n  if (process.env.NODE_ENV) {\n    require('promise/lib/rejection-tracking').enable()\n  }\n  window.Promise = require('promise/lib/es6-extensions.js')\n}\n"
  },
  {
    "path": "config/webpack.config.dev.js",
    "content": "// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.NODE_ENV = 'development'\nconst getClientEnvironment = require('./env')\nconst env = getClientEnvironment()\n\nconst webpack = require('webpack')\nconst path = require('path')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst webpackDevServerConfig = require('./webpack.devserver.config.js')\n\nmodule.exports = {\n  devServer: webpackDevServerConfig,\n  // Enhanced dev support (like correct line numbers on errors)\n  devtool: 'source-map',\n  entry: [path.resolve(process.cwd(), 'config/setup.js'), path.resolve(process.cwd(), 'src/index.js')],\n  output: {\n    // This does not produce a real file. It's just the virtual path that is\n    // served by WebpackDevServer in development. This is the JS bundle\n    // containing code from all our entry points, and the Webpack runtime.\n    filename: '[name].bundle.js',\n    chunkFilename: '[name].bundle.js',\n    // Where to create the build\n    path: path.resolve(process.cwd(), 'build'),\n    publicPath: '/',\n  },\n  module: {\n    rules: [\n      // First, run the linter.\n      // It's important to do this before Babel processes the JS.\n      {\n        test: /\\.js$/,\n        enforce: 'pre',\n        exclude: /node_modules/,\n        loader: 'eslint-loader',\n      },\n      // Process JS with Babel\n      {\n        test: /\\.js$/,\n        exclude: /node_modules/,\n        use: {\n          options: {\n            // This is a feature of `babel-loader` for webpack (not Babel itself).\n            // It enables caching results in ./node_modules/.cache/babel-loader/\n            // directory for faster rebuilds.\n            cacheDirectory: true,\n          },\n          loader: 'babel-loader',\n        },\n      },\n      {\n        test: /\\.scss$/,\n        use: [\n          'style-loader', // creates style nodes from JS strings\n          'css-loader', // translates CSS into CommonJS\n          'sass-loader', // compiles Sass to CSS, using Node Sass by default\n        ],\n      },\n    ],\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      // Path to HTML file\n      template: './public/index.html',\n      // Variables listed here in the configurations for HtmlWebpackPlugin become ejs\n      // variables for interpolation in the HTML file, accessible with\n      // <%- htmlWebpackPlugin.options.[varName] %>. We put our env variables in for HTML access\n      env: env.raw,\n    }),\n    // Make global variables available to the application. We use this to\n    // set process.env vars in the front-end\n    new webpack.DefinePlugin(env.stringified),\n  ],\n  // Some libraries import Node modules but don't use them in the browser.\n  // Tell Webpack to provide empty mocks for them so importing them works.\n  node: {\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty',\n  },\n}\n"
  },
  {
    "path": "config/webpack.config.production.js",
    "content": "// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.NODE_ENV = 'production'\nconst getClientEnvironment = require('./env')\nconst env = getClientEnvironment()\n\nconst webpack = require('webpack')\nconst path = require('path')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\n\nmodule.exports = {\n  // Don't attempt to continue if there are any errors.\n  bail: true,\n  entry: [path.resolve(process.cwd(), 'config/setup.js'), path.resolve(process.cwd(), 'src/index.js')],\n  output: {\n    filename: 'static/js/bundle.js',\n    // Where to create the build\n    path: path.resolve(process.cwd(), 'build'),\n    publicPath: '/',\n  },\n  module: {\n    rules: [\n      // First, run the linter.\n      // It's important to do this before Babel processes the JS.\n      {\n        test: /\\.js$/,\n        enforce: 'pre',\n        exclude: /node_modules/,\n        loader: 'eslint-loader',\n      },\n      // Process JS with Babel\n      {\n        test: /\\.js$/,\n        exclude: /node_modules/,\n        use: {\n          options: { compact: true },\n          loader: 'babel-loader',\n        },\n      },\n      {\n        test: /\\.scss$/,\n        use: [\n          'style-loader', // creates style nodes from JS strings\n          'css-loader', // translates CSS into CommonJS\n          'sass-loader', // compiles Sass to CSS, using Node Sass by default\n        ],\n      },\n    ],\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      // Path to HTML file\n      template: './public/index.html',\n      // Minification options\n      minify: {\n        removeComments: false,\n        collapseWhitespace: false,\n        removeRedundantAttributes: true,\n        useShortDoctype: true,\n        removeEmptyAttributes: true,\n        removeStyleLinkTypeAttributes: true,\n        keepClosingSlash: true,\n        minifyJS: true,\n        minifyCSS: true,\n        minifyURLs: true,\n      },\n      hash: true,\n      // Variables listed here in the configurations for HtmlWebpackPlugin become ejs\n      // variables for interpolation in the HTML file, accessible with\n      // <%- htmlWebpackPlugin.options.[varName] %>. We put our env variables in for HTML access\n      env: env.raw,\n    }),\n    // Copy static assets from public/static to build/static\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(process.cwd(), 'public/static'),\n        to: 'static',\n      },\n    ]),\n    // Make global variables available to the application. We use this to\n    // set process.env vars in the front-end\n    new webpack.DefinePlugin(env.stringified),\n  ],\n  // Some libraries import Node modules but don't use them in the browser.\n  // Tell Webpack to provide empty mocks for them so importing them works.\n  node: {\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty',\n  },\n  // Don't show performance hints at build time. They just tell us to use code splitting\n  performance: {\n    hints: false,\n  },\n}\n"
  },
  {
    "path": "config/webpack.devserver.config.js",
    "content": "const path = require('path')\n\nmodule.exports = {\n  // Tell the server where to serve static content from.\n  contentBase: path.join(process.cwd(), 'public'),\n  // By default files from `contentBase` will not trigger a page reload.\n  watchContentBase: true,\n  // Open a web browser.\n  open: true,\n  // Overlay errors in the browser\n  overlay: true,\n  // Enable gzip compression of generated files.\n  compress: true,\n  // Port to view dev env (localhost:3010)\n  port: 8000,\n  // Silence WebpackDevServer's own logs since they're generally not useful.\n  // It will still show compile warnings and errors with this setting.\n  clientLogLevel: 'none',\n  // Show less info in the terminal (errors only)\n  stats: \"errors-only\",\n  // Reportedly, this avoids CPU overload on some systems.\n  // https://github.com/facebookincubator/create-react-app/issues/293\n  watchOptions: {\n    ignored: /node_modules/,\n  },\n  // Since development mode thinks of Webpack DevServer as the place to get index.html\n  // instead of a file system or a Node backend, we need to tell DevServer to always\n  // serve index.html for refreshes since we have an SPA\n  historyApiFallback: true\n}\n"
  },
  {
    "path": "getbranches.sh",
    "content": "for branch in `git branch -a | grep remotes | grep -v HEAD | grep -v master `; do\n   git branch --track ${branch#remotes/origin/} $branch\ndone"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-router-course\",\n  \"version\": \"0.1.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"webpack-dev-server --mode development --config config/webpack.config.dev.js\",\n    \"build\": \"webpack --mode production --config config/webpack.config.production.js\",\n    \"branches\": \"sh getbranches.sh\",\n    \"postinstall\": \"npm run branches\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.2.2\",\n    \"@babel/plugin-syntax-dynamic-import\": \"^7.2.0\",\n    \"@babel/preset-env\": \"^7.2.3\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"babel-eslint\": \"^8.2.3\",\n    \"babel-loader\": \"^8.0.4\",\n    \"copy-webpack-plugin\": \"^4.5.1\",\n    \"css-loader\": \"^2.1.1\",\n    \"dotenv\": \"^5.0.1\",\n    \"eslint\": \"^4.19.1\",\n    \"eslint-loader\": \"^2.0.0\",\n    \"eslint-plugin-react\": \"^7.7.0\",\n    \"fs-extra\": \"^5.0.0\",\n    \"html-webpack-plugin\": \"^3.2.0\",\n    \"sass\": \"^1.34.1\",\n    \"sass-loader\": \"^7.3.1\",\n    \"style-loader\": \"^0.23.1\",\n    \"webpack\": \"^4.28.0\",\n    \"webpack-cli\": \"^3.1.1\",\n    \"webpack-dev-server\": \"^3.2.1\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.2.0\",\n    \"classnames\": \"^2.2.6\",\n    \"js-cookie\": \"^2.2.0\",\n    \"promise\": \"^8.0.3\",\n    \"prop-types\": \"^15.6.2\",\n    \"react\": \"^16.12.0\",\n    \"react-dom\": \"^16.12.0\",\n    \"react-flex-columns\": \"^0.4.2\",\n    \"react-router-dom\": \"^5.1.2\",\n    \"react-transition-group\": \"^4.1.1\"\n  }\n}\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>React Router 5 Course</title>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css\" />\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:400,300\" />\n  </head>\n  <body>\n    <div id=\"root\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/static/css/main.css",
    "content": "/* Empty */"
  },
  {
    "path": "src/auth/Login.js",
    "content": "import React, { useState } from 'react'\nimport Panel from '../ui/Panel'\nimport Card from '../ui/Card'\nimport { useAuthUser } from '../utils/AuthUser'\n\n// Fake API Network Call\nconst apiLogin = (username, password) => {\n  return new Promise((resolve, reject) => {\n    if (username === 'react' && password === 'react') {\n      resolve()\n    } else {\n      reject()\n    }\n  })\n}\n\nconst Login = ({ history }) => {\n  const { setLogged } = useAuthUser()\n  const [errorMessage, setErrorMessage] = useState()\n\n  function handleSubmit(e) {\n    e.preventDefault()\n    const [usernameNode, passwordNode] = e.target.elements\n\n    apiLogin(usernameNode.value, passwordNode.value)\n      .then(() => {\n        setLogged(true)\n        history.push('/projects')\n      })\n      .catch(() => {\n        setLogged(false)\n        setErrorMessage('Invalid')\n      })\n  }\n\n  return (\n    <Panel>\n      <Card style={{ minHeight: '10em' }}>\n        <h1 className=\"heading-2\">Welcome to Firebase, almost...</h1>\n        <p>\n          This mock application will demonstrate React Router with nested layouts and a strategy for authenticated\n          (protected) routes.\n        </p>\n        <p>\n          The username is <strong>react</strong> and the password is <strong>react</strong>\n        </p>\n        {errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>}\n        <form className=\"spacing\" onSubmit={handleSubmit}>\n          <input type=\"text\" placeholder=\"Username\" required />\n          <input type=\"password\" placeholder=\"Password\" required />\n          <button type=\"submit\" className=\"button\">\n            Login\n          </button>\n        </form>\n      </Card>\n    </Panel>\n  )\n}\n\nexport default Login\n"
  },
  {
    "path": "src/database.json",
    "content": "{\n  \"projects\": [\n    {\n      \"id\": \"react-training\",\n      \"name\": \"ReactTraining.com\"\n    },\n    {\n      \"id\": \"secret-project\",\n      \"name\": \"Secret Project\"\n    }\n  ]\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { Switch, Route, Redirect } from 'react-router-dom'\nimport Router from './utils/Router'\nimport { AuthUserProvider } from './utils/AuthUser'\nimport AuthorizedRoute from './utils/AuthorizedRoute'\nimport UnauthorizedLayout from './layouts/UnauthorizedLayout'\nimport AuthorizedLayout from './layouts/AuthorizedLayout'\nimport './styles/main.scss'\n\nconst App = () => (\n  <Router>\n    <AuthUserProvider>\n      <Switch>\n        <Route path=\"/auth\" component={UnauthorizedLayout} />\n        <AuthorizedRoute path=\"/projects\" component={AuthorizedLayout} />\n        <Redirect to=\"/projects\" />\n      </Switch>\n    </AuthUserProvider>\n  </Router>\n)\n\nReactDOM.render(<App />, document.getElementById('root'))\n"
  },
  {
    "path": "src/layouts/AccountSubLayout.js",
    "content": "import React, { useEffect, useState, Fragment } from 'react'\nimport { Link, Route } from 'react-router-dom'\nimport Panel from '../ui/Panel'\nimport Tiles from '../ui/Tiles'\nimport Card from '../ui/Card'\nimport AddProject from '../projects/AddProject'\nimport { getProjects } from '../utils/api'\n\nconst AccountSubLayout = ({ match, history }) => {\n  const [projects, setProjects] = useState(false)\n\n  useEffect(() => {\n    let isCurrent = true\n    getProjects().then(projects => {\n      if (isCurrent) {\n        setProjects(projects)\n      }\n    })\n    return () => (isCurrent = false)\n  }, [])\n\n  return (\n    <div className=\"account-sub-layout\">\n      <Panel className=\"panel-welcome-to-firebase\">\n        <h1 className=\"heading-1\">Almost Firebase!</h1>\n        <p>\n          Tools from Google for developing great apps, engaging with\n          <br />\n          your users, and earning more through mobile ads.\n        </p>\n        <p className=\"horizontal-spacing\">\n          <a href=\"#\">Learn More</a>\n          <a href=\"#\">Documentation</a>\n          <a href=\"#\">Support</a>\n        </p>\n      </Panel>\n      <Panel className=\"panel-recent-projects\">\n        <Route path=\"/projects/add\" component={AddProject} />\n        <Route\n          path=\"/projects\"\n          exact\n          render={() => {\n            return (\n              <Fragment>\n                <p>Recent projects</p>\n                <div>\n                  <Tiles>\n                    <Card className=\"card-recent-project center-blocks\" style={{ height: '14em', cursor: 'pointer' }}>\n                      <Link className=\"block\" to=\"/projects/add\">\n                        Add Project\n                      </Link>\n                    </Card>\n                    {Array.isArray(projects) &&\n                      projects.map(project => (\n                        <div role=\"link\" key={project.id} onClick={() => history.push(`/projects/${project.id}`)}>\n                          <Card\n                            className=\"card-recent-project spacing-small\"\n                            style={{ height: '14em', cursor: 'pointer' }}>\n                            <h1 className=\"heading-3\">{project.name}</h1>\n                            <div>{project.id}</div>\n                          </Card>\n                        </div>\n                      ))}\n                  </Tiles>\n                </div>\n              </Fragment>\n            )\n          }}\n        />\n      </Panel>\n    </div>\n  )\n}\n\nexport default AccountSubLayout\n"
  },
  {
    "path": "src/layouts/AuthorizedLayout.js",
    "content": "import React from 'react'\nimport { Switch, Route } from 'react-router-dom'\nimport AuthorizedPrimaryHeader from '../ui/AuthorizedPrimaryHeader'\nimport AccountSubLayout from './AccountSubLayout'\nimport ProjectSubLayout from './ProjectSubLayout'\n\nconst AuthorizedLayout = ({ match }) => (\n  <div className=\"app authorized-layout\">\n    <AuthorizedPrimaryHeader />\n    <Switch>\n      <Route path=\"/projects/add\" exact component={AccountSubLayout} />\n      <Route path=\"/projects\" exact component={AccountSubLayout} />\n      <Route path=\"/projects/:projectId\" component={ProjectSubLayout} />\n    </Switch>\n  </div>\n)\n\nexport default AuthorizedLayout\n"
  },
  {
    "path": "src/layouts/ProjectSubLayout.js",
    "content": "import React, { useEffect, useState } from 'react'\nimport { Switch, Route, Redirect, Link } from 'react-router-dom'\nimport ProjectContext from '../utils/ProjectContext'\nimport ProjectSidebar from '../ui/ProjectSidebar'\nimport Overview from '../projects/Overview'\nimport { getProject } from '../utils/api'\nimport AuthenticationLayout from '../projects/authentication/AuthenticationLayout'\nimport DatabaseHome from '../projects/database/DatabaseHome'\nimport DatabaseLayout from '../projects/database/DatabaseLayout'\n\nconst ProjectSubLayout = ({ match, pathname }) => {\n  const [project, setProject] = useState(false)\n  const { projectId } = match.params\n\n  useEffect(() => {\n    let isCurrent = true\n    getProject(projectId).then(project => {\n      if (isCurrent) {\n        setProject(project)\n      }\n    })\n    return () => (isCurrent = false)\n  }, [projectId])\n\n  return (\n    <ProjectContext.Provider value={project}>\n      <div className=\"project-sub-layout\">\n        <ProjectSidebar />\n        <div className=\"project-primary-content\">\n          <nav className=\"project-nav horizontal-spacing\" style={{ color: 'white' }}>\n            <span className=\"text-light-tint\">{project.name}</span>\n            <span> : </span>\n            <Link\n              to={{ pathname: '/projects/add', state: { cancelPathname: location.pathname } }}\n              className=\"text-light-tint\">\n              Add Project\n            </Link>\n          </nav>\n          <main>\n            <Switch>\n              <Route path={`${match.path}/overview`} component={Overview} />\n              <Route path={`${match.path}/authentication`} component={AuthenticationLayout} />\n              <Route path={`${match.path}/database`} exact component={DatabaseHome} />\n              <Route path={`${match.path}/database/:databaseType`} component={DatabaseLayout} />\n              <Redirect to={`${match.path}/overview`} />\n            </Switch>\n          </main>\n        </div>\n      </div>\n    </ProjectContext.Provider>\n  )\n}\n\nexport default ProjectSubLayout\n"
  },
  {
    "path": "src/layouts/UnauthorizedLayout.js",
    "content": "import React from 'react'\nimport { Switch, Route } from 'react-router-dom'\nimport Login from '../auth/Login'\n\nconst UnauthorizedLayout = ({ match }) => (\n  <div className=\"app unauthorized-layout\">\n    <Switch>\n      <Route path={match.url} component={Login} />\n    </Switch>\n  </div>\n)\n\nexport default UnauthorizedLayout\n"
  },
  {
    "path": "src/projects/AddProject.js",
    "content": "import React, { useState } from 'react'\nimport { Link, Prompt } from 'react-router-dom'\nimport Card from '../ui/Card'\n\nconst AddProject = ({ location }) => {\n  const [formIsDirty, setFormIsDirty] = useState(false)\n\n  function handleSubmit(e) {\n    e.preventDefault()\n  }\n\n  return (\n    <Card style={{ minHeight: '15em' }}>\n      <Prompt when={formIsDirty} message=\"Are you sure you want to leave this form before saving?\" />\n      <form className=\"spacing\" onSubmit={handleSubmit}>\n        <input onChange={() => setFormIsDirty(true)} type=\"text\" placeholder=\"Project Name\" required />\n        <footer className=\"horizontal-spacing\">\n          <button type=\"submit\" className=\"button\">\n            Add Project\n          </button>\n          <Link to={(location.state && location.state.cancelPathname) || '/projects'}>Cancel</Link>\n        </footer>\n      </form>\n    </Card>\n  )\n}\n\nexport default AddProject\n"
  },
  {
    "path": "src/projects/Overview.js",
    "content": "import React, { Fragment, useContext } from 'react'\nimport { Columns, Column } from 'react-flex-columns'\nimport ProjectContext from '../utils/ProjectContext'\nimport Panel from '../ui/Panel'\nimport Card from '../ui/Card'\nimport PageHeader from '../ui/PageHeader'\n\nconst Overview = () => {\n  const projectContext = useContext(ProjectContext)\n\n  return (\n    <Fragment>\n      <PageHeader style={{ height: '25em' }}>\n        <h1 className=\"heading-2 horizontal-spacing\">\n          <span>{projectContext.name}</span>\n          <span className=\"plan\">Blaze plan</span>\n        </h1>\n        <div>\n          <span className=\"add-app text-light-tint\">Add app</span>\n        </div>\n      </PageHeader>\n      {/* The extra height of the header and the negative margin of this div\n          pulls the panel up into the blue area of the header\n      */}\n      <div style={{ marginTop: '-18em' }}>\n        <Panel>\n          <h2 className=\"heading-3 text-light-tint\">Develop</h2>\n          <Columns gutterSize={0.8}>\n            <Column flex>\n              <Card style={{ height: '20em' }}>Hosting</Card>\n            </Column>\n            <Column flex>\n              <Card style={{ height: '20em' }}>Realtime Database</Card>\n            </Column>\n          </Columns>\n        </Panel>\n      </div>\n    </Fragment>\n  )\n}\n\nexport default Overview\n"
  },
  {
    "path": "src/projects/authentication/AuthenticationLayout.js",
    "content": "import React, { Fragment } from 'react'\nimport { Switch, Route, Redirect } from 'react-router-dom'\nimport { TransitionGroup, CSSTransition } from 'react-transition-group'\nimport Panel from '../../ui/Panel'\nimport PageHeader from '../../ui/PageHeader'\nimport { PageHeaderTabs, Tab } from '../../ui/PageHeaderTabs'\nimport Users from './Users'\nimport SigninMethods from './SigninMethods'\nimport Templates from './Templates'\n\nconst AuthenticationLayout = ({ match, location }) => {\n  return (\n    <Fragment>\n      {match.url === location.pathname && <Redirect to={`${match.url}/users`} />}\n      <PageHeader title=\"Authentication\" useMaxWidth={false}>\n        <PageHeaderTabs>\n          <Tab to={`${match.url}/users`}>Users</Tab>\n          <Tab to={`${match.url}/signin-method`}>Sign-in method</Tab>\n          <Tab to={`${match.url}/templates`}>Templates</Tab>\n        </PageHeaderTabs>\n      </PageHeader>\n      <Panel>\n        <TransitionGroup className=\"animated-cards\">\n          <CSSTransition key={location.key} timeout={600} classNames=\"animated-card\">\n            <Switch location={location}>\n              <Route path={`${match.path}/users`} component={Users} />\n              <Route path={`${match.path}/signin-method`} component={SigninMethods} />\n              <Route path={`${match.path}/templates`} component={Templates} />\n              <Route component={() => null} />\n            </Switch>\n          </CSSTransition>\n        </TransitionGroup>\n      </Panel>\n    </Fragment>\n  )\n}\n\nexport default AuthenticationLayout\n"
  },
  {
    "path": "src/projects/authentication/SignInMethods.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst SigninMethods = () => {\n  return <Card style={{ height: '20em' }}>Sign-in Methods</Card>\n}\n\nexport default SigninMethods\n"
  },
  {
    "path": "src/projects/authentication/Templates.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst Templates = () => {\n  return <Card style={{ height: '20em' }}>Templates</Card>\n}\n\nexport default Templates\n"
  },
  {
    "path": "src/projects/authentication/Users.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst Users = () => {\n  return <Card style={{ height: '20em' }}>Users</Card>\n}\n\nexport default Users\n"
  },
  {
    "path": "src/projects/database/Data.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst Data = ({ match }) => {\n  return <Card style={{ height: '20em' }}>Data ({match.params.databaseType})</Card>\n}\n\nexport default Data\n"
  },
  {
    "path": "src/projects/database/DataIndexes.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst DataIndexes = ({ match }) => {\n  return <Card style={{ height: '20em' }}>Indexes ({match.params.databaseType})</Card>\n}\n\nexport default DataIndexes\n"
  },
  {
    "path": "src/projects/database/DatabaseHome.js",
    "content": "import React, { Fragment } from 'react'\nimport { Link } from 'react-router-dom'\nimport { Columns, Column } from 'react-flex-columns'\nimport Panel from '../../ui/Panel'\nimport PageHeader from '../../ui/PageHeader'\nimport Card from '../../ui/Card'\n\nconst DatabaseHome = ({ match }) => {\n  return (\n    <Fragment>\n      <PageHeader title=\"Database\" useMaxWidth={false} />\n      <Panel>\n        <Columns gutterSize={0.8}>\n          <Column flex>\n            <Card className=\"spacing\">\n              <h1 className=\"heading-3\">Cloud Firestore</h1>\n              <div>\n                <Link to={`${match.url}/firestore`}>View</Link>\n              </div>\n            </Card>\n          </Column>\n          <Column flex>\n            <Card className=\"spacing\">\n              <h1 className=\"heading-3\">Realtime Database</h1>\n              <div>\n                <Link to={`${match.url}/realtime`}>View</Link>\n              </div>\n            </Card>\n          </Column>\n        </Columns>\n      </Panel>\n    </Fragment>\n  )\n}\n\nexport default DatabaseHome\n"
  },
  {
    "path": "src/projects/database/DatabaseLayout.js",
    "content": "import React, { Fragment } from 'react'\nimport { Switch, Route, Redirect } from 'react-router-dom'\nimport { TransitionGroup, CSSTransition } from 'react-transition-group'\nimport Panel from '../../ui/Panel'\nimport PageHeader from '../../ui/PageHeader'\nimport { PageHeaderTabs, Tab } from '../../ui/PageHeaderTabs'\nimport Data from './Data'\nimport Rules from './Rules'\nimport DataIndexes from './DataIndexes'\n\nconst DatabaseLayout = ({ match, location }) => {\n  return (\n    <Fragment>\n      {match.url === location.pathname && <Redirect to={`${match.url}/data`} />}\n      <PageHeader title=\"Database\" useMaxWidth={false}>\n        <PageHeaderTabs>\n          <Tab to={`${match.url}/data`}>Data</Tab>\n          <Tab to={`${match.url}/rules`}>Rules</Tab>\n          <Tab to={`${match.url}/indexes`}>Indexes</Tab>\n        </PageHeaderTabs>\n      </PageHeader>\n      <Panel>\n        <TransitionGroup className=\"animated-cards\">\n          <CSSTransition key={location.key} timeout={600} classNames=\"animated-card\">\n            <Switch>\n              <Route path={`${match.path}/data`} component={Data} />\n              <Route path={`${match.path}/rules`} component={Rules} />\n              <Route path={`${match.path}/indexes`} component={DataIndexes} />\n              <Route component={() => null} />\n            </Switch>\n          </CSSTransition>\n        </TransitionGroup>\n      </Panel>\n    </Fragment>\n  )\n}\n\nexport default DatabaseLayout\n"
  },
  {
    "path": "src/projects/database/Rules.js",
    "content": "import React from 'react'\nimport Card from '../../ui/Card'\n\nconst Rules = ({ match }) => {\n  return <Card style={{ height: '20em' }}>Rules ({match.params.databaseType})</Card>\n}\n\nexport default Rules\n"
  },
  {
    "path": "src/styles/layouts/account-sub-layout.scss",
    "content": ".account-sub-layout {\n  min-height: 100vh;\n\n  [class*='heading-'] {\n    color: #222;\n  }\n}\n"
  },
  {
    "path": "src/styles/layouts/app.scss",
    "content": ".app {\n  min-height: 100vh;\n}\n\n.app.authorized-layout {\n  background-color: $light-gray;\n}\n\n.app.unauthorized-layout {\n  background-image: url('/static/background.svg');\n  background-repeat: no-repeat;\n  background-size: cover;\n  padding-top: 6em;\n}\n"
  },
  {
    "path": "src/styles/layouts/project-sub-layout.scss",
    "content": ".project-sub-layout {\n  min-height: 100vh;\n  display: flex;\n}\n\n.project-primary-content {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n\n  main {\n    flex: 1;\n    overflow-y: scroll;\n    padding: 2em;\n  }\n}\n\n.project-nav {\n  font-size: 0.9em;\n  background-color: $blue;\n  height: $nav-height;\n  display: flex;\n  align-items: center;\n  padding-left: 2em;\n  &:hover {\n    color: #fff;\n  }\n}\n"
  },
  {
    "path": "src/styles/main.scss",
    "content": "// Setup\n@import './mixins/layout';\n@import './vars';\n\n// Reset and Utilities\n@import './reset';\n@import './utilities';\n\n// UI\n@import './ui/panels/panel';\n@import './ui/panels/recent-projects';\n@import './ui/panels/welcome-to-firebase';\n@import './ui/authorized-primary-header';\n@import './ui/button';\n@import './ui/card';\n@import './ui/input-fields';\n@import './ui/page-header';\n@import './ui/project-sidebar';\n@import './ui/tiles';\n\n// Layout\n@import './layouts/app';\n@import './layouts/project-sub-layout';\n@import './layouts/account-sub-layout';\n"
  },
  {
    "path": "src/styles/mixins/layout.scss",
    "content": "// Vertical and Horizontal Gutters (Lobotomized Owl)\n@mixin spacing($gutter, $horizontal: false) {\n  /**\n   * Apply to containers to get nice gutters between vertical items\n   *\n   * More Details: https://www.youtube.com/watch?v=w4skJXB03Ho&noredirect=1\n   */\n\n  @if $horizontal {\n    // Its the \"Lobotomized Owl\" horizontally\n    > * + * {\n      margin-left: $gutter !important;\n    }\n  } @else {\n    // Reset all first-child elements to have no vertical margin\n    > * {\n      margin-top: 0;\n      margin-bottom: 0;\n    }\n\n    // Its the \"Lobotomized Owl\" vertically\n    > * + * {\n      margin-top: $gutter !important;\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/reset.scss",
    "content": "// ****************************************\n//   Inherited Styles\n// ****************************************\n\n// See this explanation for details\n// https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/\n\nhtml {\n  box-sizing: border-box;\n  color: #888;\n  font-family: 'Roboto';\n  font-size: 12pt;\n  font-weight: 300;\n  line-height: 1.5em;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n}\n\n// ****************************************\n//   Resets\n// ****************************************\n\nbody {\n  margin: 0;\n}\n\na {\n  color: $blue;\n  text-decoration: none;\n}\n\nh1,\nh2,\nh3,\nh4 {\n  font-size: 1em;\n  font-weight: 300;\n}\n\np a {\n  text-decoration: underline;\n}\n\nstrong {\n  color: #222;\n}\n\n/****************************************\n  Misc\n*****************************************/\n\n// Find class names that are undefined (an error from JS)\n.undefined {\n  background-color: lime !important;\n}\n"
  },
  {
    "path": "src/styles/ui/authorized-primary-header.scss",
    "content": ".authorized-primary-header {\n  position: relative;\n\n  .logo {\n    font-size: 18pt;\n    color: #777;\n    position: absolute;\n    top: 0.7rem;\n    left: 1rem;\n\n    img {\n      height: 1.1em;\n      display: inline-block;\n      margin-right: 0.5em;\n    }\n    > * {\n      vertical-align: middle;\n    }\n\n    &.logo-light {\n      color: #fff;\n    }\n  }\n\n  .primary-nav {\n    position: absolute;\n    top: 0.5rem;\n    right: 1rem;\n    // min-width: 10em;\n\n    > * {\n      vertical-align: middle;\n    }\n\n    .logout {\n      color: #fff;\n      margin-right: 1em;\n    }\n\n    .avatar {\n      border-radius: 50%;\n      width: 2em;\n      height: 2em;\n      background-color: rgba(0, 0, 0, 0.2);\n      display: inline-block;\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/button.scss",
    "content": "a.button {\n  text-decoration: none;\n}\n\nbutton {\n  color: $blue;\n  // font: ${theme.copy.fontFamily};\n  &:not(.button):hover {\n    text-decoration: underline;\n  }\n}\n\n.button,\nbutton {\n  padding: 0;\n  border: none;\n  background-color: transparent;\n  display: inline-block;\n  cursor: pointer;\n}\n\n.button {\n  background-color: $blue;\n  color: #fff;\n  letter-spacing: 0.1em;\n  padding: 0.7em 1.8em;\n  border-radius: 0.4rem;\n\n  &:hover,\n  &:focus {\n    background-color: darken($blue, 5%);\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/card.scss",
    "content": ".card {\n  background-color: #fff;\n  box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n  border-radius: 0.5em;\n  padding: 1em;\n  min-height: 3em;\n}\n\n.animated-cards {\n  position: relative;\n\n  > * {\n    position: absolute;\n    width: 100%;\n  }\n}\n\n.animated-card {\n  &-exit {\n    opacity: 1;\n    transition: all 0.6s;\n  }\n  &-exit-active {\n    opacity: 0;\n    transform: translateY(3em);\n  }\n\n  &-enter {\n    opacity: 0;\n    transition: all 0.6s;\n    transform: translateY(-1em);\n  }\n  &-enter-active {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/input-fields.scss",
    "content": "// Just enough styling to get the job done. Don't take\n// this too seriously\n\n[type='text'],\n[type='password'] {\n  display: block;\n  width: 25em;\n  padding: 0.5em;\n  border: 1px solid $gray;\n  &:focus {\n    outline: none;\n    border-color: $blue;\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/page-header.scss",
    "content": ".page-header {\n  background-color: $blue;\n  color: #fff;\n\n  // Offset padding of <main>\n  margin: -2rem -2rem 2rem -2rem;\n  padding: 1rem 2rem;\n\n  .plan {\n    font-size: 0.7rem;\n    letter-spacing: 0.1em;\n    font-weight: 300;\n    padding: 0.5em 1.2em;\n    border-radius: 5em;\n    background-color: rgba(#fff, 0.2);\n  }\n\n  .add-app {\n    border-radius: 5em;\n    padding: 0.3em 1em;\n    background-color: rgba(#000, 0.1);\n  }\n}\n\n.page-header-tabs {\n  font-size: 0.9em;\n  // Offsets padding of container\n  margin-bottom: -1rem;\n  a {\n    display: inline-block;\n    border-bottom: 3px solid transparent;\n    color: rgba(#fff, 0.6);\n    padding: 0.5em 0;\n\n    + a {\n      margin-left: 1.5em;\n    }\n\n    &.active {\n      border-bottom-color: #fff;\n      color: #fff;\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/panels/panel.scss",
    "content": ".panel {\n  &.max-width > div {\n    max-width: 60em;\n    margin: 0 auto;\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/panels/recent-projects.scss",
    "content": ".panel.panel-recent-projects {\n  margin-top: -7em;\n  padding-bottom: 3em;\n}\n\n.card-recent-project {\n  min-height: 12em;\n}\n"
  },
  {
    "path": "src/styles/ui/panels/welcome-to-firebase.scss",
    "content": ".panel.panel-welcome-to-firebase {\n  background: #fff url('/static/account-background.png');\n  background-position: right top;\n  background-repeat: no-repeat;\n  padding: 8em 0 12em 0;\n}\n"
  },
  {
    "path": "src/styles/ui/project-sidebar.scss",
    "content": ".project-sidebar {\n  width: 16em;\n  // Offset Nav Height\n  padding-top: $nav-height;\n  background-color: #262f3e;\n  border-left: 1px solid #404854;\n\n  [class*='heading-'] {\n    color: #fff;\n  }\n\n  nav {\n    font-size: 0.8em;\n    color: rgba(255, 255, 255, 0.2);\n    border-bottom: 1px solid #404854;\n    padding: 1.5em;\n\n    &:first-child {\n      border-top: 1px solid #404854;\n    }\n\n    &.overview a:not(.active) {\n      color: #babdc0;\n    }\n\n    a.active {\n      color: #4fc3f7;\n    }\n\n    &.open {\n      background-color: #19212b;\n\n      a {\n        color: #babdc0;\n        font-size: 1.2em;\n        display: block;\n        margin-left: 1em;\n        &.active {\n          color: #4fc3f7;\n        }\n      }\n    }\n\n    p {\n      white-space: nowrap;\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/ui/tiles.scss",
    "content": ".tiles {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));\n  grid-gap: 1.3em;\n}\n"
  },
  {
    "path": "src/styles/utilities.scss",
    "content": "$gutter: 1em;\n$gutter-small: 0.6em;\n\n/**\n * Layout\n */\n\n.block {\n  display: block;\n}\n\n.spacing-small {\n  @include spacing(0.2em);\n}\n.spacing-medium {\n  @include spacing($gutter-small);\n}\n.spacing {\n  @include spacing($gutter);\n}\n\n.horizontal-spacing-small {\n  @include spacing(0.2em, true);\n}\n.horizontal-spacing-medium {\n  @include spacing($gutter-small, true);\n}\n.horizontal-spacing {\n  @include spacing($gutter, true);\n}\n\n.vertical-middle > * {\n  vertical-align: middle;\n}\n\n.left-content {\n  text-align: left;\n}\n.center-content {\n  text-align: center;\n}\n.right-content {\n  text-align: right;\n}\n\n.center-blocks {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n/**\n * Copy\n */\n\n.heading-1,\n.heading-2,\n.heading-3,\n.heading-4 {\n  display: block;\n}\n\n.heading-1 {\n  font-size: 2rem;\n}\n\n.heading-2 {\n  font-size: 1.6rem;\n  font-weight: 400;\n}\n\n.heading-3 {\n  font-size: 1.3rem;\n  font-weight: 400;\n}\n\n.heading-4 {\n  font-size: 1rem;\n  font-weight: 400;\n}\n\n.text-light-tint {\n  color: rgba(#fff, 0.6);\n}\n"
  },
  {
    "path": "src/styles/vars.scss",
    "content": "// Colors\n$light-blue: #039be5;\n$blue: #0099e8;\n$light-gray: #eceff1;\n$gray: #d1d2d3;\n\n// Layout\n$nav-height: 50px;\n"
  },
  {
    "path": "src/ui/AuthorizedPrimaryHeader.js",
    "content": "import React from 'react'\nimport { Link, withRouter } from 'react-router-dom'\nimport classnames from 'classnames'\nimport { useAuthUser } from '../utils/AuthUser'\n\nconst AuthorizedPrimaryHeader = ({ location }) => {\n  const { setLogged } = useAuthUser()\n  const lightBackground = ['/projects', '/projects/add'].includes(location.pathname)\n\n  return (\n    <header className=\"authorized-primary-header\">\n      <Link to=\"/projects\" className={classnames('logo', { 'logo-light': !lightBackground })}>\n        <img src=\"/static/firebase.svg\" alt=\"Firebase Logo\" />\n        <span>Firebase</span>\n      </Link>\n      <nav className=\"primary-nav\">\n        <button onClick={() => setLogged(false)} className=\"logout text-light-tint\">\n          Logout\n        </button>\n        <div className=\"avatar\" />\n      </nav>\n    </header>\n  )\n}\n\nexport default withRouter(AuthorizedPrimaryHeader)\n"
  },
  {
    "path": "src/ui/Card.js",
    "content": "import React from 'react'\nimport classnames from 'classnames'\n\nconst Card = ({ children, className, ...rest }) => (\n  <div className={classnames('card', className)} {...rest}>\n    {children}\n  </div>\n)\n\nexport default Card\n"
  },
  {
    "path": "src/ui/PageHeader.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport Panel from '../ui/Panel'\n\nconst PageHeader = ({ children, title, useMaxWidth, ...rest }) => (\n  <header className=\"page-header\" {...rest}>\n    <Panel useMaxWidth={useMaxWidth}>\n      {title && <h1 className=\"heading-2\">{title}</h1>}\n      {children}\n    </Panel>\n  </header>\n)\n\nPageHeader.defaultProps = {\n  useMaxWidth: true,\n}\n\nPageHeader.propTypes = {\n  useMaxWidth: PropTypes.bool,\n}\n\nexport default PageHeader\n"
  },
  {
    "path": "src/ui/PageHeaderTabs.js",
    "content": "import React from 'react'\nimport { NavLink } from 'react-router-dom'\n\nexport const PageHeaderTabs = ({ children }) => <div className=\"page-header-tabs\">{children}</div>\n\n// Just a wrapper around NavLink\nexport const Tab = ({ ...rest }) => <NavLink activeClassName=\"active\" {...rest} />\n"
  },
  {
    "path": "src/ui/Panel.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport classnames from 'classnames'\n\nconst Panel = ({ children, useMaxWidth, className }) => (\n  <div className={classnames('panel', className, { 'max-width': useMaxWidth })}>\n    <div className=\"spacing\">{children}</div>\n  </div>\n)\n\nPanel.defaultProps = {\n  useMaxWidth: true,\n}\n\nPanel.propTypes = {\n  useMaxWidth: PropTypes.bool,\n}\n\nexport default Panel\n"
  },
  {
    "path": "src/ui/ProjectSidebar.js",
    "content": "import React from 'react'\nimport { NavLink, withRouter } from 'react-router-dom'\n\nconst ProjectSidebar = ({ match }) => (\n  <aside className=\"project-sidebar\">\n    <nav className=\"overview\">\n      <NavLink activeClassName=\"active\" to={`${match.url}/overview`} className=\"heading-4\">\n        Project Overview\n      </NavLink>\n    </nav>\n    <nav className=\"open spacing\">\n      <h1 className=\"heading-4\">Develop</h1>\n      <div className=\"spacing-medium\">\n        <NavLink activeClassName=\"active\" to={`${match.url}/authentication`}>\n          Authentication\n        </NavLink>\n        <NavLink activeClassName=\"active\" to={`${match.url}/database`}>\n          Database\n        </NavLink>\n        <NavLink activeClassName=\"active\" to={`${match.url}/storage`}>\n          Storage\n        </NavLink>\n        <NavLink activeClassName=\"active\" to={`${match.url}/hosting`}>\n          Hosting\n        </NavLink>\n        <NavLink activeClassName=\"active\" to={`${match.url}/functions`}>\n          Functions\n        </NavLink>\n        <NavLink activeClassName=\"active\" to={`${match.url}/ml-kit`}>\n          ML Kit\n        </NavLink>\n      </div>\n    </nav>\n    <nav className=\"spacing-small\">\n      <h1 className=\"heading-4\">Quality</h1>\n      <p>Crashlytics, Performance, Test Lab</p>\n    </nav>\n    <nav className=\"spacing-small\">\n      <h1 className=\"heading-4\">Analytics</h1>\n      <p>Dashboard, Events, Conversions, Audiences</p>\n    </nav>\n    <nav className=\"spacing-small\">\n      <h1 className=\"heading-4\">Grow</h1>\n      <p>Predictions, A/B Testing, Cloud Messaging</p>\n    </nav>\n  </aside>\n)\n\nexport default withRouter(ProjectSidebar)\n"
  },
  {
    "path": "src/ui/Tiles.js",
    "content": "import React from 'react'\n\nconst Tiles = ({ children, ...rest }) => {\n  // The reason for wrapping in an arbitrary div is for one, we need to ensure\n  // the first child of grid is under our control without affecting the children\n  // passed in, but also grid items will grow vertically to the height of their\n  // row siblings, we might not want that for the children passed in, but we can\n  // do that to our arbitrary div\n  return (\n    <div {...rest} className=\"tiles\">\n      {React.Children.map(children, child => (\n        <div>{child}</div>\n      ))}\n    </div>\n  )\n}\n\nexport default Tiles\n"
  },
  {
    "path": "src/utils/AuthUser.js",
    "content": "import React, { useState, useContext, useEffect } from 'react'\nimport Cookies from 'js-cookie'\n\nconst AuthUserContext = React.createContext()\n\n// So we don't conflict with your localhost\nconst cookieName = 'RR5CourseLogged'\n\nexport const AuthUserProvider = ({ children }) => {\n  const cookieLogged = Cookies.getJSON(cookieName)\n  const [logged, setLogged] = useState(cookieLogged ? cookieLogged.logged : false)\n\n  useEffect(() => {\n    if (logged) {\n      Cookies.set(cookieName, { logged: true })\n    } else {\n      Cookies.remove(cookieName)\n    }\n  }, [logged])\n\n  return <AuthUserContext.Provider value={{ logged, setLogged }}>{children}</AuthUserContext.Provider>\n}\n\nexport const useAuthUser = () => {\n  return useContext(AuthUserContext)\n}\n"
  },
  {
    "path": "src/utils/AuthorizedRoute.js",
    "content": "import React from 'react'\nimport { Route, Redirect } from 'react-router-dom'\nimport { useAuthUser } from './AuthUser'\n\nconst AuthorizedRoute = ({ component, ...rest }) => {\n  const { logged } = useAuthUser()\n\n  if (logged === null) return <div>Loading...</div>\n  if (logged !== true) return <Redirect push to=\"/auth\" />\n  return <Route component={component} {...rest} />\n}\n\nexport default AuthorizedRoute\n"
  },
  {
    "path": "src/utils/ProjectContext.js",
    "content": "import React from 'react'\n\nconst ProjectContext = React.createContext()\nexport default ProjectContext\n"
  },
  {
    "path": "src/utils/Router.js",
    "content": "import React from 'react'\nimport { BrowserRouter, Route, Redirect } from 'react-router-dom'\n\n// Normalize all paths to not have trailing slashes even if they\n// matched <Route> with one:\nconst Router = ({ children }) => (\n  <BrowserRouter>\n    <Route\n      render={({ location: { pathname, search, hash } }) =>\n        pathname !== '/' && pathname.slice(-1) === '/' ? (\n          <Redirect to={`${pathname.slice(0, -1)}${search}${hash}`} />\n        ) : (\n          children\n        )\n      }\n    />\n  </BrowserRouter>\n)\n\nexport default Router\n"
  },
  {
    "path": "src/utils/api.js",
    "content": "// Fake little database\nimport database from '../database.json'\n\nexport function getProjects() {\n  return Promise.resolve(database.projects)\n}\n\nexport function getProject(id) {\n  const project = database.projects.find(p => p.id === id)\n  return Promise.resolve(project || null)\n}\n"
  },
  {
    "path": "src/wireframe/App.js",
    "content": "import React from 'react'\nimport { BrowserRouter, Switch, Route, Redirect, Link } from 'react-router-dom'\n\nimport './wireframes.scss'\n\nconst App = () => (\n  <BrowserRouter>\n    <div className=\"app\">\n      <Switch>\n        <Route path=\"/auth\" component={UnauthorizedLayout} />\n        <Route path=\"/\" component={AuthorizedLayout} />\n      </Switch>\n    </div>\n  </BrowserRouter>\n)\n\nconst AuthorizedLayout = () => (\n  <div className=\"authorized-layout\">\n    <header>\n      <Link to=\"/dashboard\">Dashboard</Link>\n      <Link to=\"/products\">Products</Link>\n      <Link to=\"/auth\">Logout</Link>\n    </header>\n    <div className=\"content\">\n      <Route path=\"/dashboard\" component={DashboardLayout} />\n      <Route path=\"/products\" component={ProductsLayout} />\n    </div>\n    <footer />\n  </div>\n)\n\nconst UnauthorizedLayout = () => (\n  <div className=\"unauthorized-layout\">\n    <main>\n      <Link to=\"/\">Login</Link>\n    </main>\n  </div>\n)\n\nconst DashboardLayout = () => (\n  <div className=\"dashboard-layout\">\n    <aside />\n    <div className=\"content\">\n      <nav>\n        <Link to=\"/dashboard/settings\">Settings</Link>\n        <Link to=\"/dashboard/search\">Search</Link>\n      </nav>\n      <main>\n        <Route path=\"/dashboard/settings\" component={SettingsPage} />\n        <Route path=\"/dashboard/search\" component={SearchResultsPage} />\n      </main>\n    </div>\n  </div>\n)\n\nconst ProductsLayout = () => (\n  <div className=\"products-layout\">\n    <main>\n      <div />\n      <div />\n      <div />\n      <div />\n      <div />\n    </main>\n  </div>\n)\n\nconst SettingsPage = () => (\n  <div className=\"settings-page\">\n    <div />\n    <div />\n  </div>\n)\n\nconst SearchResultsPage = () => (\n  <div className=\"search-results-page\">\n    <div />\n    <div />\n    <div />\n  </div>\n)\n\nexport default App\n"
  },
  {
    "path": "src/wireframe/wireframes.scss",
    "content": ".app {\n  background-image: linear-gradient(45deg, #fafafa 25%, transparent 25%),\n    linear-gradient(-45deg, #fafafa 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #fafafa 75%),\n    linear-gradient(-45deg, transparent 75%, #fafafa 75%);\n  background-size: 40px 40px;\n  background-position: 0 0, 0 20px, 20px -20px, -20px 0px;\n\n  padding: 2em 5em;\n}\n\n$blue: #0099e8;\n$green: #1cd41c;\n\n@mixin box($color, $top: true, $right: true, $bottom: true, $left: true) {\n  @if $top {\n    border-top: 5px dashed $color;\n  }\n  @if $right {\n    border-right: 5px dashed $color;\n  }\n  @if $bottom {\n    border-bottom: 5px dashed $color;\n  }\n  @if $left {\n    border-left: 5px dashed $color;\n  }\n}\n\n.authorized-layout {\n  min-height: 50vh;\n  > header {\n    @include box($blue);\n    padding: 2em;\n  }\n  > footer {\n    @include box($blue);\n    padding: 2em;\n    margin-top: 1em;\n  }\n  > .content {\n    @include box($blue);\n    display: flex;\n    min-height: 100px;\n    padding: 1em;\n    margin-top: 1em;\n  }\n  a + a {\n    margin-left: 2em;\n  }\n}\n\n.unauthorized-layout {\n  display: flex;\n  padding: 3em;\n  main {\n    @include box($blue);\n    margin: auto;\n    width: 50%;\n    height: 200px;\n    padding: 1em;\n  }\n}\n\n.dashboard-layout {\n  display: flex;\n  width: 100%;\n  a {\n    color: red;\n  }\n  > aside {\n    width: 12em;\n    @include box(red);\n  }\n  > .content {\n    flex: 1;\n    @include box(red, true, true, true, false);\n    > nav {\n      @include box(red, false, false, true, false);\n      padding: 1em;\n    }\n    > main {\n      padding: 1em;\n      min-height: 10em;\n    }\n  }\n}\n\n.products-layout {\n  width: 100%;\n  main {\n    @include box(red);\n    padding: 1em;\n    overflow: hidden;\n    > div {\n      @include box(red);\n      width: 100px;\n      height: 100px;\n      float: left;\n      margin-right: 1em;\n    }\n  }\n}\n\n.settings-page {\n  display: flex;\n  padding: 1em;\n  @include box($green);\n  > div {\n    flex: 1;\n    min-height: 10em;\n  }\n  > :first-child {\n    @include box($green);\n  }\n  > :last-child {\n    @include box($green, true, true, true, false);\n  }\n}\n\n.search-results-page {\n  > div + div {\n    margin-top: 1em;\n  }\n  > div {\n    padding: 2em;\n    @include box($green);\n  }\n}\n"
  }
]