Skip to content

Commit

Permalink
Merge pull request #75 from HubSpot/jmiller/getting-started-v2
Browse files Browse the repository at this point in the history
Update getting started example
  • Loading branch information
jontallboy authored Jul 16, 2024
2 parents bc7ef22 + 048e8d1 commit 069b881
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 201 deletions.
163 changes: 122 additions & 41 deletions examples/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,164 @@
The getting started example within this directory covers a variety of key features/components of working with CMS React within a HubSpot website. This guide outlines the specifics of using CMS React primitives such as Modules, Fields, and Islands, as well as integrating third-party dependencies, styling, assets, and setting up local development tooling for your project.

## Setup

### Environment

To set up your development environment to work with CMS React, you’ll need the following:

- A HubSpot account with CMS Free, Starter, Pro, or Enterprise access
- Node.js v18.0 or higher and the associated version of NPM

## Creating and Deploying a CMS React Project
## Creating and deploying a CMS React project
Before we dive into running your first CMS React project, its important to understand 3 significant directories that you will come across within this example:

- `*-project`: The HubSpot project directory that gets uploaded via the Projects system to build and deploy your CMS React code found in the `/src` directory.
- `*-theme`: This is where your HubL templates and theme configuration lives.
- `*-project/*-app`: A package inside your project directory containing all your assets, styles, components, modules, islands, and any local development tooling you need, such as ESLint, Prettier, TypeScript, etc.

### 1. Clone the Repo
> **Note** The directory naming convention mentioned above is not a requirement for your CMS React project to work, but rather an example of how you could organize your CMS React files.
First, clone the CMS React repository:
### Clone the `getting-started` directory
From your local directory for the following commands:

```bash
git clone https://github.com/HubSpot/cms-react.git
git clone --filter=blob:none --no-checkout https://github.com/HubSpot/cms-react.git;
cd cms-react;
git sparse-checkout set --cone;
git checkout main;
git sparse-checkout set examples/getting-started;
```

CMS React projects have a 3 significant directories:

- `getting-started-project`: The HubSpot project directory that gets uploaded via the Projects system to build and deploy your CMS React code found in the `/src` directory.
- `getting-started-project/getting-started-app`: A package inside your project directory containing all your assets, styles, components, modules, islands, and any local development tooling you need, such as ESLint, Prettier, TypeScript, etc.
- `getting-started-theme`: This is where your HubL templates and theme configuration lives.

At the root of this example, you will configure any local dev tooling you want, such as ESLint, Prettier, TypeScript, etc.
This will clone only the `getting-started` directory from within cms-react repository down to your local file system.

### 2. Install Dependencies

Navigate to the `cms-react/examples/getting-started` directory in your terminal and run:

```bash
npm install
```
### Setting up your `hubspot.config.yaml` file
In order to develop locally and deploy your code to your HubSpot portal. You will need to configure a `hubspot.config.yaml` file. Run `hs init` from the root of the repository and follow the prompts to setup your `hubspot.config.yaml` file. Keep in mind that when setting up JS assets for the first time, you will need to deactivate and regenerate your personal access key, ensuring it includes `CMS Pages`, `Design Manager` and `Developer Projects` permissions. See the [HubSpot CLI documentation](https://developers.hubspot.com/docs/cms/guides/getting-started-with-local-development) for more information.

This will install all the local development tooling you need, including `@hubspot/cli`, `@hubspot/cms-dev-server`, and any other dev tooling you configured.
> **Warning:** If you are using an existing access key from a previous call to `hs init` or `hs auth`, you will need to deactivate and regenerate the access key to include new scopes necessary for local CMS React development.
### 3. Local Development

To run this example project locally, navigate to the `getting-started-project/getting-started-app` directory and run:
### Local development

```bash
npm run start
```
Now that our `hubspot.config.yaml` file is configured we can get to running and developing our project locally! Let's first install our dependencies. From the root of the `getting-started` directory, run `npm install` in your terminal. This will install all your local dev tooling and any other dependencies found in your project's root package.json file. Next we will also need to run `npm install` from within the `/getting-started-app` directory which will install all dependencies required for our CMS react code. With all of our deps installed, you can now run `npm run start` from the `getting-started-project/getting-started-app` directory.

_This example makes use of a weather API through [RapidAPI](https://rapidapi.com/search?term=weatherapi-com&sortBy=ByRelevance). You will need to go through the process of singing up for this service (free) to fully see this example in action. Once you have an API Key you can add it to `getting-started-project/getting-started-app/utils.ts` at the `apiKey` constant._
Once running, open up your browser to [http://hslocal.net:3000](http://hslocal.net:3000) to see the index page that links to all the modules associated to this project. Click on the "local version" of the "Weather" module to see a locally running instance of the module. The locally running instance will pick up any changes you make to your files instantly to streamline the feedback loop when iterating on your CMS components 🚀

Then navigate to [http://hslocal.net:3000](http://hslocal.net:3000) to see an index page that links to all your modules. Click on the "local version" of the "Weather" module.
Let's give this a test drive by opening up our `Weather` module found at `components/modules/Weather/index.tsx`. From within this file find the `<TextField>` module field. That component has a `default` prop which signifies the starting value of the text field that a marketer will see in the page editor. Change the `default` prop's value to something else and save your changes. Take a look at your locally running instance of the [Weather` module](http://hslocal.net:3000/module/Weather) and it should now reflect your recent changes.

In your local code editor from the `getting-started-project/src` directory open `components/modules/Weather/index.tsx` and change the `city` field default from "Boston" to your local city.
### 4. Uploading and deploying to your portal

See your changes live update in the browser.
With some changes in place, let's deploy our code to your HubSpot portal. Navigate back to the `/getting-started/` directory and run `npm run deploy`. This will upload the `getting-started-project` to your HubSpot account. Once uploaded, built, and deployed, you can use your react modules in your website pages just as you would with any other modules.

### 4. Uploading and Deploying on HubSpot
From the repository root, run `npm run upload:hubl`. This uploads the corresponding example HubL files to your account. You should now be able to create a page from one of the `getting-started-theme` templates and see the output of the React components in the page preview.

Run `hs init` from the root of the repository to configure it to upload to your HubSpot account using your personal access key. When setting up JavaScript assets for the first time, you will need to deactivate and regenerate your personal access key, ensuring it includes `CMS Pages`, `Design Manager`, `Developer Projects`, and `GraphQL Data Fetching` permissions. This will create a `hubspot.config.yaml` file required for both uploading changes and local development. See the [HubSpot CLI documentation](https://developers.hubspot.com/docs/cms/guides/getting-started-with-local-development) for more information.

> **Warning:** If you are using an existing access key from a previous call to `hs init` or `hs auth`, you will need to deactivate and regenerate the access key to include new scopes necessary for local CMS React development.
### Making more changes
Let's say we want to show some default data on the initial load instead of forcing users to input a search before seeing weather data rendered. To do this, we can add a new module field for a `defaultCity` that a marketer can use within the context of the page editor to assign the module instance a default city to use at load time.

Navigate back to `/examples/getting-started/` and run:
To start, open the `/components/modules/Weather/index.tsx` in your code editor. Within this file you will see a `fields` variable that contains all of our module fields. These are the fields that a marketer can use to modify data for the Weather module from the WYSIWYG page editor. Since we already have a `TextField` component imported from `@hubspot/cms-components/fields` for our Weather Headline field, all we need to do is add a new `<TextField />` for our defaultCity

```bash
npm run deploy
```js
<TextField label="Default City" name="defaultCity" default="Boston" />
```

This will upload the `getting-started-project` to your HubSpot account. Once uploaded, built, and deployed, you can use your react modules in your website pages just as you would with any other modules.
Once this is added, the `Component` export within this same file will now have access to the value of this new field via the `fieldValues` prop. since the `name` property on our `<TextField>` component is `defaultCity` we can pull off this value from the `fieldValues` similarly to how we have for `headline` and add it to our island props like so:

From the repository root, run:
```js
const { defaultCity, headline } = fieldValues;
<Island
module={WeatherForecast}
headline={headline}
defaultCity={defaultCity}
/>
```

```bash
npm run upload:hubl
The final step is modify our `WeatherForecast` island component to enable the fetching weather data using the `defaultCity` value. To do this we have to make a couple updates:
1. Include `defaultCity` in our props list
2. Update our `WeatherForecastProps` interface to include `defaultCity`
3. Add a `useEffect` that fetches the weather forecast with the `defaultCity` value
4. Add a loading state during a fetch

The final component should resemble the following:

```js
import { useEffect, useState } from 'react';
import weatherStyles from '../../styles/weather.module.css';
import { getWeatherForecast } from '../../utils.ts';
import { WeatherForecast as WeatherForecastType } from '../../constants.ts';
import { CurrentWeatherCard, UpcomingWeatherCard } from '../WeatherCards.tsx';

interface WeatherForecastProps {
headline: string;
defaultCity: string; // added defaultCity to the interface
}

export default function WeatherForecast({
headline,
defaultCity, // included defaultCity in props list
}: WeatherForecastProps) {
const [city, setCity] = useState('');
const [weatherData, setWeatherData] = useState<WeatherForecastType>();

// adding useEffect to fetch weather forecast on component mount
useEffect(() => {
getWeatherForecast(defaultCity).then((data) => {
setWeatherData(data);
});
}, []);

const handleFetchWeather = () => {
getWeatherForecast(city).then((data) => {
setWeatherData(data);
});
};

const isFetching: boolean = !weatherData;
const hasError: boolean = !isFetching && !!weatherData.error;
const hasWeatherData: boolean =
!isFetching && !hasError && !!weatherData.forecast;
const missingData = !isFetching && !hasWeatherData && !hasError;

function WeatherForecast({ weatherData }) {
return (
<>
<div>
<CurrentWeatherCard weatherData={weatherData} />
</div>
<div className={weatherStyles.cardContainer}>
<UpcomingWeatherCard weatherData={weatherData} />
</div>
</>
);
}

return (
<div className={weatherStyles.wrapper}>
<h1>{headline}</h1>
<div className={weatherStyles.form}>
<input
type="text"
placeholder="Enter city"
onChange={(event) => setCity(event.target.value)}
/>
<button onClick={handleFetchWeather}>Update Forecast</button>
</div>
<div className={weatherStyles.currentWeather}>
{isFetching && <h2>Loading...</h2>} {/* add loading state during fetch */}
{hasError && <h2>Error occurred when fetching weather forecast</h2>}
{hasWeatherData && <WeatherForecast weatherData={weatherData} />}
{missingData && (
<h2>No results found for "{city}", please search another location</h2>
)}
</div>
</div>
);
}
```

This uploads the corresponding example HubL files to your account. You should now be able to create a page from one of the `getting-started-theme` templates and see the output of the React components in the page preview.
Once you are satisfied with your changes, you only need to re-run `npm run deploy` in order to get the latest react module built and deployed to your portal. Since no HubL files were modified, you **do not** need to re-run `npm run upload:hubl`.

### 5. Previewing Local Changes on Proxied Pages

You can preview your local CMS React components inside live HubL-rendered pages. To do this, create a page:
In addition to locally viewing your modules, you can also preview your local CMS React components inside live HubL-rendered pages. To do this, create a page:

- Go to Website Pages
- Click “Create”, then “Create Website Page"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
import dayjs from 'dayjs';
import weatherStyles from '../styles/weather.module.css';
import { getWeatherIcon } from '../utils.ts';
import { ForecastData } from '../constants.ts';

export function CurrentWeatherCard({ weatherData }: any) {
const { location, forecast } = weatherData;
const { forecastday } = forecast;
const currentDay = forecastday[0].day;
interface WeatherProps {
city: string;
forecast: ForecastData[];
}
interface CurrentWeatherCardProps {
weatherData: WeatherProps;
}

export function CurrentWeatherCard({ weatherData }: CurrentWeatherCardProps) {
const { forecast, city } = weatherData;
const currentDay = forecast[0];

return (
<div className={weatherStyles.current}>
<div className={weatherStyles.current} key={currentDay.time}>
<div className={weatherStyles.condition}>
<img
src={getWeatherIcon(currentDay.condition.text.trim())}
alt={currentDay.condition.text}
src={getWeatherIcon(currentDay.weather_code.toString())}
alt={`${city}-weather-icon-${currentDay.weather_code}`}
/>
<h3>
{currentDay.avgtemp_f}&deg;
{currentDay.apparent_temperature_max}&deg;
<span className={weatherStyles.unit}>F</span>
</h3>
</div>
<h2 className={weatherStyles.city}>{location.name}</h2>
<span className={weatherStyles.country}>{location.country}</span>
<h2 className={weatherStyles.city}>{city}</h2>
</div>
);
}

export function UpcomingWeatherCard({ weatherData }: any) {
interface UpcomingWeatherCardProps {
weatherData: WeatherProps;
}

export function UpcomingWeatherCard({ weatherData }: UpcomingWeatherCardProps) {
const { city, forecast } = weatherData;

return (
<div className={weatherStyles.card}>
<span>{dayjs(weatherData.date).format('dddd')}</span>
<img
src={getWeatherIcon(weatherData.day.condition.text.trim())}
alt={weatherData.day.condition.text}
/>
<h3>
{weatherData.day.avgtemp_f}&deg;
<span className={weatherStyles.unit}>F</span>
</h3>
</div>
<>
{forecast?.map((weather, index: number) => {
if (index === 0) return null;

return (
<div className={weatherStyles.card} key={index}>
<span>{dayjs(weather.time).format('dddd')}</span>
<img
src={getWeatherIcon(weather.weather_code.toString())}
alt={`${city}-weather-icon-${weather.weather_code}`}
/>
<h3>
{weather.apparent_temperature_max}&deg;
<span className={weatherStyles.unit}>F</span>
</h3>
</div>
);
})}
</>
);
}
Loading

0 comments on commit 069b881

Please sign in to comment.