Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ brightspot-types
brightspot-server
.next
generated.ts
generated
78 changes: 78 additions & 0 deletions content-management/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Content Management
In this tutorial, you will learn how to use a Content Management API Endpoint provided by Brightspot to power a frontend application.

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## What you will learn
1. How to create a Content Management API endpoint with Brightspot's JS Classes
2. How to create a front-end application with [Next.js](https://nextjs.org/), [Apollo Client](https://www.apollographql.com/docs/react/), and [GraphQl Code Generator](https://www.the-guild.dev/graphql/codegen/docs/getting-started) that can perform all CRUD operations

## Running the example application
Refer to the [README](/README.md) at the root of the `react-examples` repository for details on running example applications in depth. If you have run an example application before, make sure you have the docker instance for the example applications running, then follow the quick-start steps:

brightspot (`http://localhost/cms`):

```
cd brightspot
yarn
npx brightspot types download
npx brightspot types upload src

```

frontend:

```
cd app
yarn
yarn codegen
yarn dev
```

The frontend application will open automatically in the browser.

## Using the example application
You can start with the frontend application. Click on the form at the top of the dashboard to create a new Note. Make sure to enter the Username that you are using in Brightspot (the name you logged in with). After creating your Note, you should see it appear in the dashboard. Confirm in Brightspot that the Note is also listed on the dashboard. You should see your Username avatar next to the Note.

## How everything works
Brightspot makes it possible to create content that you can then query for using the GraphQL API. One GraphQL API Brightspot offers is the Content Management API (CMA). Refer to the Brightspot documentation for more information.

Navigate to `brightspot/src/examples/content_management`. This directory contains the JS Classes files that are uploaded to Brightspot.

#### JS Classes Files:
- `NotesEndpoint.ts`: the class that create a custom CMA Endpoint with the following configurations:
- `getEntryFields`: specify the class(es) to determine the schema for the custom endpoint
- `NotesEndpointClient.ts`: the class that creates an API Client. The API Client has a client ID and API Key that are stored in the `app/.env` file to access the CMA Endpoint

> **_Note_** A CMA Endpoint requires an API Client with an client ID and API Key. Normally, this is created by an admin editorially in Brightspot. For convenience, an API Client has been generated programatically.

#### Queries:
All queries are located in `app/queries`:

- `GetNotes.graphql`: either query for notes ("\* matches ?") given certain arguments or pass in other filter options (ex: "not \_id matches ?") with arguments.
- `CreateAndUpdateNote.graphql`: either create a new note (if no id is provided) or update an existing one
- `DeleteNote.graphql`: delete a note by id - add `permanently: true` to delete without archiving.

[GraphQl Code Generator](https://www.the-guild.dev/graphql/codegen/docs/getting-started) is used to generate code from the GraphQl schema based on the content uploaded to Brightspot. This helps makes development faster and more consistent since queries, mutations, hooks etc are already typed. If you update your GraphQL query files, be sure to run `yarn codegen` to update your generated files (found in the `app/generated` directory).

#### API Routes
All GraphQL queries are made using [API Routes](https://nextjs.org/docs/api-routes/introduction) provided by Next.js. Navigate to `pages/api/notes` to see how the GraphQL query requests are made. By using the API Routes, the client Id and API key are kept from being a part of the front-end bundle for increased security.

> **_Note_** This application is for demonstration purposes only. In a production-level application, you would want to implement authentication. Although this application requires a username for creating new content and updating content in the frontend, further authentication would be needed.

## Try it yourself
The following are suggestions for further exploration:

1. Change one field in a note in the frontend (example, change the title of a note). In Brightspot, change the description of that same note. Refresh both the frontend and Brightspot. What do those fields display?

2. Remove `permanently: true` from the `DeleteNote.graphql` query (be sure to run `yarn codegen`!). Now try deleting that note, then navigate to Brightspot dashboard. Select `Note` for Content Type, then Status: Archived. Do you see the note you deleted?
## Troubleshooting

1. I am getting getting a 401 network error on initial page load in the browser for the frontend...

- Verify that the Notes Endpoint API Client Client ID and API Key (found in Brightspot: Menu Button -> `Admin` -> `APIs` -> `Clients`) are the same values that are in the `app/.env` file.

2. I get an error when trying to create or update a Note: "Variable 'toolUser' has an invalid value"....
- Verify the the Username you entered in the New Note Form or note Card is a Username in Brightspot.

Having other issues running the example application? Refer to the [Common Issues](/README.md) section in the respository README for assistance.
4 changes: 4 additions & 0 deletions content-management/app/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": []
}
4 changes: 4 additions & 0 deletions content-management/app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
NEXT_PUBLIC_HOST=http://localhost:3000
NEXT_PUBLIC_GRAPHQL_URL=http://localhost/graphql/management/notes
GRAPHQL_CLIENT_ID=d47b5d36c97d3eaf8858c471c124b2e8
GRAPHQL_CLIENT_SECRET=90a52c3d-96d9-391a-878d-fe4ef4fe431e
3 changes: 3 additions & 0 deletions content-management/app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/babel", "next/core-web-vitals", "prettier"]
}
5 changes: 5 additions & 0 deletions content-management/app/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
generated
styles
.next
public
13 changes: 13 additions & 0 deletions content-management/app/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
overwrite: true
schema:
- ${NEXT_PUBLIC_GRAPHQL_URL}:
headers:
X-Client-ID: ${GRAPHQL_CLIENT_ID}
X-Client-Secret: ${GRAPHQL_CLIENT_SECRET}
documents: 'queries/*.graphql'
generates:
generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.section {
max-width: 1240px;
padding: 7rem 1rem 0 1rem;
margin: 0 auto;
}
32 changes: 32 additions & 0 deletions content-management/app/components/Container/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import styles from './Container.module.css'
import { Dispatch, SetStateAction } from 'react'

import { Brightspot_Example_Content_Management_Note } from 'generated/graphql'
import CreateNoteForm from '../CreateNoteForm'
import Notes from '../Notes'

type Props = {
items: Brightspot_Example_Content_Management_Note[]
getItems: (
pageNumber: number,
predicate: boolean,
queryItem?: string,
newItem?: Brightspot_Example_Content_Management_Note
) => void
pageNumber: number
setPageNumber: Dispatch<SetStateAction<number>>
}

const Container = ({ items, getItems, pageNumber, setPageNumber }: Props) => (
<section className={styles.section}>
<CreateNoteForm getItems={getItems} pageNumber={pageNumber} />
<Notes
getItems={getItems}
items={items}
pageNumber={pageNumber}
setPageNumber={setPageNumber}
/>
</section>
)

export default Container
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.form {
max-width: 540px;
background: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: var(--primaryTextColor);
border-radius: 10px;
box-shadow: var(--lightShadow);
margin: 0.7rem auto;
}

.collapsed {
display: none;
}

.notCollapsed {
display: unset;
}

.input {
width: 100%;
font-size: 1rem;
border: 0;
border-radius: 3px;
min-height: 1.7rem;
padding: 5px;
margin-bottom: 7px;
font-family: 'Noto Sans', sans-serif;
}

.button {
font-size: 1rem;
padding: 7px;
margin-right: 10px;
color: var(--moduleBgColor);
font-weight: 700;
border: 0;
background-color: transparent;
border-radius: var(--mainBorderRadius);
}

.button:hover {
cursor: pointer;
background-color: var(--grayscaleFour);
}

.bottom {
width: 100%;
min-height: 30px;
text-align: end;
margin-bottom: 4px;
}

.error {
font-size: 0.9rem;
color: var(--primaryRed);
font-style: italic;
}

.errorCloseBtn {
border: 0;
background: transparent;
}

.errorCloseBtn:hover {
cursor: pointer;
}

.errorCloseIcon {
margin-right: 4px;
height: 15px;
width: 15px;
color: var(--primaryRed);
}

.wrapper {
margin: 5px 0;

width: 100%;
padding: 5px 10px;
}
Loading