Skip to content

Commit

Permalink
fix: vendor scripts included and no more console warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
activescott committed Dec 9, 2020
1 parent 7d41a4a commit 3923338
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ sam.yaml
/public/
!/public/readme.md
src/react-app/src/**/*.js
src/react-app/public/static/vendor/svg-injector/
src/react-app/public/static/vendor/bootstrap/
src/react-app/public/static/vendor/cookieconsent/
src/react-app/public/static/vendor/jquery/
src/react-app/public/static/vendor/popper.js/
90 changes: 44 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

This project is intended to be a template for using [Architect](https://arc.codes/) on the server and React on the client for a web application. It uses TypeScript on both the server and the client.


## Goals

The goal is to make it quick and easy to start a new web application with the most basic functionality that any application needs set up and ready to go.


## Stack

**[Architect](https://arc.codes/)** provides lightweight infrastructure as code (IaC) deployment for an AWS-based backend. It provides support for TypeScript-based serverless functions on Lambda/APIG, DynamoDB, SQS, Static assets, and more. It's really a lightweight facade over cloudformation and provides [IaC extensibility via macros](https://arc.codes/primitives/macros).
Expand All @@ -23,67 +21,67 @@ Architect also supports multiple environments and local development.

**Hygene** Linting of all files is handled with a combo of eslint & prettier. See `lint*` scripts in `package.json`.


## Getting Started

To run the base stack as is, run the following commands:

npm run install-all
npm start


## Usage

### To add a new page

1. *Add it to `src/react-app/src/pages` as `mypage.tsx`
2. *Add a route for the page in `src/react-app/src/App.tsx` (this allows react-router to handle it)
3. *Add a serverless function for the route's path in `app.arc` that returns react-app's `index.html` OR copy `index.html` to that path in `src/react-app/public/...` so that index.html is always returned if someone navigates directly to the route's path on the server (see https://create-react-app.dev/docs/deployment#serving-apps-with-client-side-routing for details)
1. Add it to `src/react-app/src/pages` as `mypage.tsx`
2. Add a route for the page in `src/react-app/src/App.tsx` (this allows react-router to handle it)
3. Add a serverless function for the route's path in `app.arc` that returns react-app's `index.html` OR copy `index.html` to that path in `src/react-app/public/...` so that index.html is always returned if someone navigates directly to the route's path on the server (see https://create-react-app.dev/docs/deployment#serving-apps-with-client-side-routing for details)

**NOTE**: We use the `spa` support in Architect to force all 404s to just return `index.html` which supports our react-client-app (where client routing shows the right thing based on the route). This has a couple side-effects:
1. You don't get clean 404 when it should be a 404. Instead the client routing just kind of poops a blank page out.
2. If we used static assets (just put a copy of index.html) then it would be possible to leverage cloudfront for much faster support of the non-404 files. See fingerprinting at https://arc.codes/reference/arc/static and then you could front-end it with cloudfront and more at https://docs.begin.com/en/static-assets/working-with-static-assets and https://arc.codes/primitives/cdn
For info on `spa` and how to turn off the "return index.html by default" behavior see https://arc.codes/reference/functions/http/node/proxy

1. You don't get clean 404 when it should be a 404. Instead the client routing just kind of poops a blank page out.
2. If we used static assets (just put a copy of index.html) then it would be possible to leverage cloudfront for much faster support of the non-404 files. See fingerprinting at https://arc.codes/reference/arc/static and then you could front-end it with cloudfront and more at https://docs.begin.com/en/static-assets/working-with-static-assets and https://arc.codes/primitives/cdn
For info on `spa` and how to turn off the "return index.html by default" behavior see https://arc.codes/reference/functions/http/node/proxy

## Roadmap

* [+] Bootstrap
* [+] Get two pages working and routing between them.
* [+] Layout:
* [+] Add components for Layout & Head (ala [next/head](https://nextjs.org/docs/api-reference/next/head)) to make it easy to have a common layout across all pages.

* [+] Document the steps to add a new page.

* [+] demo of a client-side fetching api data in the `/data` page
* [+] fix: footer policy link color
* [ ] fix: path for cookieconsent `/static/vendor/cookieconsent/cookieconsent.min.js` (is it deployed? Is SVGInjector deployed?)
* [ ] fix: no more console warnings
* [ ] chore: basic unit tests
* [ ] chore: git hooks for linting
* [ ] chore: git hooks for unit tests
* [ ] fix: hamburger menu dropdown in a responsive view for narrow mobile clients

* Allow adding multiple OAuth Authorization servers to allow a user to authenticate:
* [ ] feat: configuration for client ID & secret
* [ ] feat: DDB tables to store user and table to store tokens by provider
* [ ] feat: user can use one or more OAuth providers

* UserContext:
* [ ] feat: UserContext available as a react context so that client side app always has access to user/auth when authenticated (see alert genie, but no need for auth0)
* [ ] feat: when serving index.html always return a signed cookie that also has an accessToken claim in it (HOW??)
* See the session stuff [here](https://arc.codes/reference/functions/http/node/session) and [here](https://docs.begin.com/en/http-functions/sessions) (which one??) the `requireLogin` example at https://arc.codes/reference/functions/http/node/async
* The "Greedy Root" behavior means we can inject cookies: https://docs.begin.com/en/http-functions/provisioning#greedy-root. Should we?
* CSRF: See https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#xmlhttprequest-native-javascript to include it in the fetch client by default. See https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#hmac-based-token-pattern for HMAC-based CRSF. Needed on all "state changing requests".
* [ ] feat: all local API requests in `src/react-app/src/lib/useApiHooks.ts` use accessToken
* [ ] feat: login/logout pages
* [ ] feat: Avatar and login/logout/profile stuff in header
- [+] Bootstrap
- [+] Get two pages working and routing between them.
- [+] Layout:

- [+] Add components for Layout & Head (ala [next/head](https://nextjs.org/docs/api-reference/next/head)) to make it easy to have a common layout across all pages.

- [+] Document the steps to add a new page.

- [+] demo of a client-side fetching api data in the `/data` page
- [+] fix: footer policy link color
- [+] fix: path for cookieconsent `/static/vendor/cookieconsent/cookieconsent.min.js` (is it deployed? Is SVGInjector deployed?)
- [+] fix: no more console warnings
- [ ] chore: basic unit tests
- [ ] chore: git hooks for linting
- [ ] chore: git hooks for unit tests
- [ ] fix: hamburger menu dropdown in a responsive view for narrow mobile clients

- Allow adding multiple OAuth Authorization servers to allow a user to authenticate:

- [ ] feat: configuration for client ID & secret
- [ ] feat: DDB tables to store user and table to store tokens by provider
- [ ] feat: user can use one or more OAuth providers

- UserContext:
- [ ] feat: UserContext available as a react context so that client side app always has access to user/auth when authenticated (see alert genie, but no need for auth0)
- [ ] feat: when serving index.html always return a signed cookie that also has an accessToken claim in it (HOW??)
- See the session stuff [here](https://arc.codes/reference/functions/http/node/session) and [here](https://docs.begin.com/en/http-functions/sessions) (which one??) the `requireLogin` example at https://arc.codes/reference/functions/http/node/async
- The "Greedy Root" behavior means we can inject cookies: https://docs.begin.com/en/http-functions/provisioning#greedy-root. Should we?
- CSRF: See https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#xmlhttprequest-native-javascript to include it in the fetch client by default. See https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#hmac-based-token-pattern for HMAC-based CRSF. Needed on all "state changing requests".
- [ ] feat: all local API requests in `src/react-app/src/lib/useApiHooks.ts` use accessToken
- [ ] feat: login/logout pages
- [ ] feat: Avatar and login/logout/profile stuff in header

### Future
* [ ] feat: HMR for react-app while using architect's sandbox (so API's still work) 🤔
* [ ] chore: Integration tests for pages (see puppeteer, https://arc.codes/guides/testing)
* [ ] chore: Automated test to detect console warnings (puppeteer? part of unit test fixture?)
* [ ] chore: Integration tests for api (see https://arc.codes/guides/testing)
* [ ] feat: server-side rendering for react (like Next.js, see https://reacttraining.com/react-router/web/guides/server-rendering)
* [ ]

- [ ] feat: HMR for react-app while using architect's sandbox (so API's still work) 🤔
- [ ] chore: Integration tests for pages (see puppeteer, https://arc.codes/guides/testing)
- [ ] chore: Automated test to detect console warnings (puppeteer? part of unit test fixture?)
- [ ] chore: Integration tests for api (see https://arc.codes/guides/testing)
- [ ] feat: server-side rendering for react (like Next.js, see https://reacttraining.com/react-router/web/guides/server-rendering)
- [ ]
26 changes: 23 additions & 3 deletions src/react-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion src/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,32 @@
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.0.0",
"bootstrap": "^4.5.0",
"cookieconsent": "^3.1.1",
"isomorphic-unfetch": "^3.0.0",
"jquery": "^3.5.1",
"node-sass": "^4.14.1",
"popper.js": "^1.16.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-helmet": "^6.1.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"svg-injector": "^1.1.3",
"typescript": "^3.7.5"
},
"scripts": {
"start": "react-scripts start",
"prebuild": "npm run -s copy-vendors",
"build": "react-scripts build && mkdir -p ../../public ; cp -R ./build/* ../../public/",
"test": "react-scripts test"
"test": "react-scripts test",
"copy-vendors": "rm -rfd ./public/static/vendor && npm run -s copy-bootstrap && npm run -s copy-jquery && npm run -s copy-popper && npm run -s copy-svg-injector && npm run -s copy-cookieconsent && echo \"copy-vendors completed!\n\"",
"copy-bootstrap": "THEVENDOR=bootstrap npm run -s copy-vendor-dist",
"copy-jquery": "THEVENDOR=jquery npm run -s copy-vendor-dist",
"copy-popper": "THEVENDOR=popper.js npm run -s copy-vendor-dist",
"copy-svg-injector": "THEVENDOR=svg-injector npm run -s copy-vendor-dist",
"copy-cookieconsent": "THEVENDOR=cookieconsent npm run -s copy-vendor-build",
"copy-vendor-dist": "mkdir -p \"./public/static/vendor/$THEVENDOR\" && cp -fR ./node_modules/$THEVENDOR/dist/* \"./public/static/vendor/$THEVENDOR/\"",
"copy-vendor-build": "mkdir -p \"./public/static/vendor/$THEVENDOR\" && cp -fR ./node_modules/$THEVENDOR/build/* \"./public/static/vendor/$THEVENDOR/\""
},
"eslintConfig": {
"extends": "react-app"
Expand Down
2 changes: 1 addition & 1 deletion src/react-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"

import Home from "./pages/home"
import About from "./pages/about"
Expand Down
13 changes: 7 additions & 6 deletions src/react-app/src/components/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@ const Layout = (props: Props): JSX.Element => {
href="/static/images/iconic/font/css/open-iconic-bootstrap.min.css"
rel="stylesheet"
></link>
{/* Bootstrap requirements */}
<script src="/static/vendor/jquery/jquery.slim.min.js"></script>
<script src="/static/vendor/popper.js/umd/popper.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.js"></script>
{/* for iconic svg icon support https://useiconic.com/open#reference */}
<script src="/static/vendor/svg-injector/svg-injector.min.js"></script>
</Helmet>
<CookieConsent />
{/* Bootstrap requirements */}
<script src="/static/vendor/jquery/jquery.slim.min.js"></script>
<script src="/static/vendor/popper.js/umd/popper.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.js"></script>
{/* for iconic svg icon support https://useiconic.com/open#reference */}
<script src="/static/vendor/svg-injector/svg-injector.min.js"></script>
{/* for iconic svg icon support https://useiconic.com/open#reference */}
<img
src="/static/images/iconic/sprite/open-iconic.min.svg"
className="iconic-sprite"
style={{ display: "none" }}
alt=""
/>
<Nav />
<main id="content" className="py-5">
Expand Down
6 changes: 4 additions & 2 deletions src/react-app/src/components/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Nav = (): JSX.Element => {
style={{
width: 36,
height: 40,
backgroundImage: "url('/static/images/alert-genie-logo-white.svg')",
backgroundImage: "url('/static/images/logo-light.svg')",
backgroundRepeat: "no-repeat",
}}
>
Expand Down Expand Up @@ -62,7 +62,9 @@ const Nav = (): JSX.Element => {
<a href={href}>{label}</a>
) : (
<Link to={href}>
<a className="nav-link">{label}</a>
<a href={href} className="nav-link">
{label}
</a>
</Link>
)}
</li>
Expand Down
4 changes: 3 additions & 1 deletion src/react-app/src/components/policyNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ const NavLink = (props: NavLinkProps): JSX.Element => {
const { href, label } = props
return (
<li className="nav-item">
<Link className="nav-link" to={href}>{label}</Link>
<Link className="nav-link" to={href}>
{label}
</Link>
</li>
)
}
Expand Down
5 changes: 2 additions & 3 deletions src/react-app/src/lib/useApiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,11 @@ export const useApiGet = <TData>(
setResponse(rawData)
setIsError(false)
setError(undefined)
setIsLoading(false)
} catch (reason) {
// eslint-disable-next-line no-console
console.error(`Error fetching ${url}:`, reason)
setIsError(true)
setError(reason)
setIsError(true)
} finally {
setIsLoading(false)
}
Expand Down Expand Up @@ -176,7 +175,7 @@ const useAccessToken = (): Promise<string> => {
.catch(tokenState.rejectToken)
}
*/
// TODO: Implement a proper local token (get it from cookie?)
// TODO: Implement a proper local token (get it from cookie?)
tokenState.resolveToken("not yet implemented")
}
getToken()
Expand Down
6 changes: 5 additions & 1 deletion src/react-app/src/pages/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ const Page = (): JSX.Element => (
)

const ApiResult: React.FunctionComponent = (): React.ReactElement => {
const [{ response, isError, isLoading }] = useApiGet("/api/echo", {}, { requiresAuthentication: false })
const [{ response, isError, isLoading }] = useApiGet(
"/api/echo",
{},
{ requiresAuthentication: false }
)

return (
<div>
Expand Down

0 comments on commit 3923338

Please sign in to comment.