-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #63 from HubSpot/jmiller/routing-example
Add Routing example
- Loading branch information
Showing
29 changed files
with
1,661 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
module.exports = { | ||
parserOptions: { | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
env: { | ||
node: true, | ||
es2021: true, | ||
}, | ||
extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'], | ||
rules: { | ||
'react/react-in-jsx-scope': 'off', | ||
'react/prop-types': 'off', | ||
}, | ||
ignorePatterns: ['hello-world-project/cms-assets/dist/*'], | ||
settings: { | ||
react: { | ||
version: '18.1', | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules/ | ||
hubspot.config.yml | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"trailingComma": "all", | ||
"tabWidth": 2, | ||
"semi": true, | ||
"singleQuote": true, | ||
"overrides": [ | ||
{ | ||
"files": "*.hubl.html", | ||
"options": { | ||
"parser": "hubl" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Copyright 2022 HubSpot, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Routing | ||
With CMS React, you can use popular routing libraries like React-Router to enable SPA-style routing within your CMS website. The Pokedex example in this directory showcases how to set up SPA routing in your own project using React-Router. This documentation will walk through key aspects of our example, but for more information on React-Router, please visit their [docs](https://reactrouter.com/en/main). | ||
|
||
## Reference Files | ||
### `App.tsx` | ||
This component is where we set up our routes using `react-router-dom` components. The idea is to define which components should render for given paths, resulting in individual pages for each route. The `AppRoutes` component within this file sets up our paths using both static and dynamic routes. | ||
|
||
#### Static Routes | ||
```js | ||
const AppRoutes = () => { | ||
return ( | ||
<Routes> | ||
{/* Static Route for Home */} | ||
<Route path="/" element={<Home />} /> | ||
{/* Static Route for Pokedex */} | ||
<Route path="/pokemon" element={<Pokedex pokemonList={pokemonList} />} /> | ||
{/* Dynamic Route for individual Pokemon */} | ||
<Route | ||
path="/pokemon/:name" | ||
element={<Pokemon pokemonList={pokemonList} />} | ||
/> | ||
</Routes> | ||
); | ||
}; | ||
``` | ||
|
||
In the first route `<Route path="/" element={<Home />}` />, we explicitly tell React-Router to render the `<Home />` component at the `/` path. This means that when a user visits `your-website.com/`, they will see a home page rendered by the `<Home />` component. The final `<Route />` component leverages [React-Router's dynamic segments](https://reactrouter.com/en/main/route/route#dynamic-segments). As you can see in the example above, the path `/pokedex/:name` includes `:name` which is our dynamic segment. This allows us to render different content based on the value of `:name` at render time. | ||
|
||
Once our routes are setup, we need to update our `<Pokemon />` component to know what pokemon page to render. As you can see in the example below `useParams` extracts the dynamic segment (:name) from the URL, and we use it to find the corresponding Pokemon in our pokemonList. | ||
|
||
```js | ||
import { Link, useParams } from 'react-router-dom'; | ||
|
||
export default function Pokemon({ pokemonList }: { pokemonList: any }) { | ||
const params = useParams(); | ||
const pokemon = pokemonList.find((pokemon) => pokemon.name === params.name); | ||
|
||
return ( | ||
<main className={pageStyles.page}> | ||
<h1>Profile</h1> | ||
<ProfileCard pokemon={pokemon} /> | ||
<div className={pageStyles.back}> | ||
<Link to="/pokemon">Back to Pokedex</Link> | ||
</div> | ||
</main> | ||
); | ||
} | ||
``` | ||
|
||
### `Router/index.tsx` | ||
|
||
To integrate the `App.tsx` router component into your website, import it with the `?island` suffix and pass it to the module prop of the `<Island />` component. This setup ensures that the necessary JavaScript is sent down to enable routing via React-Router. See example below: | ||
|
||
```js | ||
import AppIsland from './App?island'; | ||
|
||
export const Component = () => { | ||
return <Island module={AppIsland} />; | ||
}; | ||
``` | ||
|
||
## Create a website page | ||
The final step is to create a CMS website or landing page using your Router module and add `[:dynamic-slug]` to the `Page URL` which will look something like this: | ||
|
||
![dynamic slug example](./routing-project/src/assets/dynamic-slug-screenshot.png "Dynamic slug example") | ||
|
||
`[:dynamic-slug]` is a special keyword that lets the CMS know that it should expect to receive arbitrary paths and it should render the page contents when a match is found. Once your slug is updated to include `[:dynamic-slug]` and the CMS page is published, you can view the live page, swap out `[:dynamic-slug]` with a valid dynamic slug (e.g. `/pokedex/[:dynamic-slug] --> /pokedex/pokemon/ivysaur`) and that is all! You now have SPA routing within your CMS website page 🚀 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "cms-react-routing", | ||
"description": "CMS React - Routing", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@hubspot/cli": "latest", | ||
"@hubspot/prettier-plugin-hubl": "latest", | ||
"eslint": "^8.24.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-react": "^7.31.10", | ||
"prettier": "^2.7.1", | ||
"yarpm": "^1.2.0" | ||
}, | ||
"scripts": { | ||
"start": "cd routing-project/src && yarpm start --", | ||
"postinstall": "cd routing-project/src && yarpm install", | ||
"lint:js": "eslint . --ext .js,.jsx", | ||
"prettier": "prettier . --check", | ||
"watch:hubl": "hs watch routing-theme routing-theme", | ||
"upload:hubl": "hs upload routing-theme routing-theme", | ||
"deploy": "hs project upload routing-project" | ||
}, | ||
"engines": { | ||
"node": ">=16.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "routing-project", | ||
"srcDir": ".", | ||
"platformVersion": "2023.2" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
declare module '*.module.css'; |
Binary file added
BIN
+31.1 KB
examples/routing/routing-project/src/assets/dynamic-slug-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"label": "CMS React - Routing", | ||
"outputPath": "" | ||
} |
37 changes: 37 additions & 0 deletions
37
examples/routing/routing-project/src/components/Header.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { NavLink } from 'react-router-dom'; | ||
import navigationStyles from '../styles/header.module.css'; | ||
import pokeball from '../assets/pokeball.svg'; | ||
|
||
export default function Header() { | ||
return ( | ||
<header className={navigationStyles.header}> | ||
<NavLink to="/"> | ||
<img src={pokeball} height="75" width="auto" alt="Pokeball" /> | ||
</NavLink> | ||
<nav className={navigationStyles.nav}> | ||
<ul> | ||
<li> | ||
<NavLink | ||
to="/" | ||
className={({ isActive }) => | ||
isActive ? navigationStyles.active : undefined | ||
} | ||
> | ||
Home | ||
</NavLink> | ||
</li> | ||
<li> | ||
<NavLink | ||
to="/pokemon" | ||
className={({ isActive }) => | ||
isActive ? navigationStyles.active : undefined | ||
} | ||
> | ||
Pokedex | ||
</NavLink> | ||
</li> | ||
</ul> | ||
</nav> | ||
</header> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Link } from 'react-router-dom'; | ||
import pageStyles from '../styles/page.module.css'; | ||
|
||
export default function Home() { | ||
return ( | ||
<div className={pageStyles.home}> | ||
<h1>CMS React Routing</h1> | ||
<Link to="/pokemon">See Pokedex</Link> | ||
</div> | ||
); | ||
} |
24 changes: 24 additions & 0 deletions
24
examples/routing/routing-project/src/components/ListingCard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Link } from 'react-router-dom'; | ||
import cardStyles from '../styles/card.module.css'; | ||
|
||
export function ListingCard({ pokemonList }: { pokemonList: any }) { | ||
return ( | ||
<div className={cardStyles.cards}> | ||
{pokemonList.map((pokemon) => ( | ||
<Link key={pokemon.name} to={`${pokemon.name}`}> | ||
<div className={cardStyles.card}> | ||
<img | ||
src={pokemon.pokemon_v2_pokemonsprites[0].sprites} | ||
height={100} | ||
alt={pokemon.name} | ||
/> | ||
<div> | ||
<p>{pokemon.pokemon_v2_pokemontypes[0].pokemon_v2_type.name}</p> | ||
<h2>{pokemon.name}</h2> | ||
</div> | ||
</div> | ||
</Link> | ||
))} | ||
</div> | ||
); | ||
} |
13 changes: 13 additions & 0 deletions
13
examples/routing/routing-project/src/components/Pokedex.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import pageStyles from '../styles/page.module.css'; | ||
import { ListingCard } from './ListingCard.tsx'; | ||
|
||
const POKEMON_GRAPHQL_SCHEMA_URL = 'https://beta.pokeapi.co/graphql/v1beta/'; | ||
|
||
export default function Pokedex({ pokemonList }: { pokemonList: any }) { | ||
return ( | ||
<main className={pageStyles.page}> | ||
<h1>Pokedex</h1> | ||
<ListingCard pokemonList={pokemonList} /> | ||
</main> | ||
); | ||
} |
17 changes: 17 additions & 0 deletions
17
examples/routing/routing-project/src/components/Pokemon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Link, useParams } from 'react-router-dom'; | ||
import pageStyles from '../styles/page.module.css'; | ||
import ProfileCard from './ProfileCard.tsx'; | ||
|
||
export default function Pokemon({ pokemonList }: { pokemonList: any }) { | ||
const params = useParams(); | ||
const pokemon = pokemonList.find((pokemon) => pokemon.name === params.name); | ||
return ( | ||
<main className={pageStyles.page}> | ||
<h1>Profile</h1> | ||
<ProfileCard pokemon={pokemon} /> | ||
<div className={pageStyles.back}> | ||
<Link to="/pokemon">Back to Pokedex</Link> | ||
</div> | ||
</main> | ||
); | ||
} |
52 changes: 52 additions & 0 deletions
52
examples/routing/routing-project/src/components/ProfileCard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import pageStyles from '../styles/page.module.css'; | ||
|
||
export default function ProfileCard({ pokemon }: any) { | ||
const { | ||
name, | ||
height, | ||
weight, | ||
base_experience, | ||
pokemon_v2_pokemonmoves: moves, | ||
pokemon_v2_pokemonsprites: sprites, | ||
pokemon_v2_pokemontypes: types, | ||
} = pokemon; | ||
|
||
const profileImageSrc = sprites[0].sprites; | ||
|
||
const listOfMoves = moves.map((move: any) => ( | ||
<li key={move.pokemon_v2_move.name}>{move.pokemon_v2_move.name}</li> | ||
)); | ||
|
||
const listOfTypes = types.map((type: any) => ( | ||
<li key={type.pokemon_v2_type.name}>{type.pokemon_v2_type.name}</li> | ||
)); | ||
|
||
return ( | ||
<div className={pageStyles.card}> | ||
<img src={profileImageSrc} alt={name} width={300} /> | ||
<div className={pageStyles.info}> | ||
<div> | ||
<h1>{name}</h1> | ||
<div> | ||
<ul> | ||
<li className={pageStyles.attributeTitle}>STATS</li> | ||
<li>HP: {base_experience}</li> | ||
<li>HEIGHT: {height}m</li> | ||
<li>WEIGHT: {weight}kg</li> | ||
</ul> | ||
</div> | ||
</div> | ||
<ul className={pageStyles.moves}> | ||
<div> | ||
<li className={pageStyles.attributeTitle}>TYPES</li> | ||
{listOfTypes} | ||
</div> | ||
<div> | ||
<li className={pageStyles.attributeTitle}>MOVES</li> | ||
{listOfMoves} | ||
</div> | ||
</ul> | ||
</div> | ||
</div> | ||
); | ||
} |
53 changes: 53 additions & 0 deletions
53
examples/routing/routing-project/src/components/islands/App.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Routes, Route, BrowserRouter, Link } from 'react-router-dom'; | ||
import { StaticRouter } from 'react-router-dom/server'; | ||
import { | ||
useIsServerRender, | ||
usePageUrl, | ||
useBasePath, | ||
} from '@hubspot/cms-components'; | ||
import Header from '../Header.tsx'; | ||
import Pokedex from '../Pokedex.tsx'; | ||
import Pokemon from '../Pokemon.tsx'; | ||
import Home from '../Home.tsx'; | ||
import { pokemonList } from '../../constants.ts'; | ||
|
||
const AppRoutes = () => { | ||
return ( | ||
<Routes> | ||
<Route path="/" element={<Home />} /> | ||
<Route path="/pokemon" element={<Pokedex pokemonList={pokemonList} />} /> | ||
<Route | ||
path="/pokemon/:name" | ||
element={<Pokemon pokemonList={pokemonList} />} | ||
/> | ||
</Routes> | ||
); | ||
}; | ||
|
||
const App = () => { | ||
const isServerRender = useIsServerRender(); | ||
const pageUrl = usePageUrl(); | ||
const basePath = useBasePath(); | ||
|
||
let app: JSX.Element; | ||
|
||
if (isServerRender) { | ||
app = ( | ||
<StaticRouter basename={basePath} location={pageUrl.pathname}> | ||
<Header /> | ||
<AppRoutes /> | ||
</StaticRouter> | ||
); | ||
} else { | ||
app = ( | ||
<BrowserRouter basename={basePath}> | ||
<Header /> | ||
<AppRoutes /> | ||
</BrowserRouter> | ||
); | ||
} | ||
|
||
return app; | ||
}; | ||
|
||
export default App; |
12 changes: 12 additions & 0 deletions
12
examples/routing/routing-project/src/components/modules/Router/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Island } from '@hubspot/cms-components'; | ||
import AppIsland from '../../islands/App?island'; | ||
|
||
export const Component = () => { | ||
return <Island module={AppIsland} />; | ||
}; | ||
|
||
export const fields = []; | ||
|
||
export const meta = { | ||
label: 'Router Module', | ||
}; |
Oops, something went wrong.