Update with-aws-amplify-typescript example (#24292)
- Update the Readme to use the latest Amplify CLI versions' prompts - Update the example to use the [SSR features of AWS Amplify ](https://aws.amazon.com/blogs/mobile/ssr-support-for-aws-amplify-javascript-libraries/) - Update to use Next version `10` instead of `9` - Correctly use the types produced by the Amplify CLI in `API.ts` - Add `amplify auth` and auth rules to the GraphQL model as suggested by the Amplify CLI.
This commit is contained in:
parent
1caa7f4971
commit
c481147b86
12 changed files with 445 additions and 269 deletions
10
examples/with-aws-amplify-typescript/.gitignore
vendored
10
examples/with-aws-amplify-typescript/.gitignore
vendored
|
@ -36,9 +36,17 @@ yarn-error.log*
|
|||
# Amplify
|
||||
amplify/\#current-cloud-backend
|
||||
amplify/.config/local-*
|
||||
amplify/logs
|
||||
amplify/mock-data
|
||||
amplify/backend/amplify-meta.json
|
||||
amplify/backend/awscloudformation
|
||||
build/
|
||||
amplify/backend/.temp
|
||||
dist/
|
||||
aws-exports.js
|
||||
awsconfiguration.json
|
||||
amplifyconfiguration.json
|
||||
amplifyconfiguration.dart
|
||||
amplify-build-config.json
|
||||
amplify-gradle-config.json
|
||||
amplifytools.xcconfig
|
||||
.secret-*
|
||||
|
|
|
@ -6,9 +6,9 @@ This example shows how to build a server rendered web application with NextJS an
|
|||
|
||||
Two routes are implemented :
|
||||
|
||||
- `/` : A static route that uses getStaticProps to load data from AppSync and renders it on the server (Code in [pages/index.tsx](pages/index.tsx))
|
||||
- `/` : A server-rendered route that uses `getServersideProps` to load data from AppSync and renders it on the server (Code in [pages/index.tsx](src/pages/index.tsx))
|
||||
|
||||
- `/todo/[id]` : A dynamic route that uses `getStaticProps` and the id from the provided context to load a single todo from AppSync and render it on the server. (Code in [pages/todo/[id].tsx](pages/todo/[id].tsx))
|
||||
- `/todo/[id]` : A dynamic route that uses `getStaticPaths`, `getStaticProps` and the id from the provided context to load a single todo from AppSync and render it on the server. (Code in [pages/todo/[id].tsx](src/pages/todo/[id].tsx))
|
||||
|
||||
## How to use
|
||||
|
||||
|
@ -59,15 +59,17 @@ $ amplify init
|
|||
❯ javascript
|
||||
? What javascript framework are you using react
|
||||
? Source Directory Path: src
|
||||
? Distribution Directory Path: out
|
||||
? Build Command: (npm run-script build)
|
||||
? Start Command: (npm run-script start)
|
||||
? Distribution Directory Path: build
|
||||
? Build Command: npm run build
|
||||
? Start Command: npm run start
|
||||
? Do you want to use an AWS profile? Y
|
||||
? Select the authentication method you want to use: AWS Profile
|
||||
? Please choose the profile you want to use: <Your profile
|
||||
|
||||
# </Interactive>
|
||||
```
|
||||
|
||||
#### Add the API
|
||||
#### Add the API and the Auth
|
||||
|
||||
```sh
|
||||
$ amplify add api
|
||||
|
@ -76,14 +78,66 @@ $ amplify add api
|
|||
❯ GraphQL
|
||||
REST
|
||||
? Provide API name: <API_NAME>
|
||||
? Choose an authorization type for the API (Use arrow keys)
|
||||
? Choose the default authorization type for the API (Use arrow keys)
|
||||
❯ API key
|
||||
Amazon Cognito User Pool
|
||||
? Do you have an annotated GraphQL schema? (y/N) y
|
||||
? Provide your schema file path: ./schema.graphql
|
||||
IAM
|
||||
OpenID Connect
|
||||
? Enter a description for the API key: <API_DESCRIPTION>
|
||||
? After how many days from now the API key should expire (1-365): 7
|
||||
? Do you want to configure advanced settings for the GraphQL API:
|
||||
No, I am done.
|
||||
❯ Yes, I want to make some additional changes.
|
||||
? Configure additional auth types? y
|
||||
? Choose the additional authorization types you want to configure for the API
|
||||
❯(*) Amazon Cognito User Pool
|
||||
( ) IAM
|
||||
( ) OpenID Connect
|
||||
Do you want to use the default authentication and security configuration? (Use arrow keys)
|
||||
❯ Default configuration
|
||||
Default configuration with Social Provider (Federation)
|
||||
Manual configuration
|
||||
I want to learn more.
|
||||
How do you want users to be able to sign in? (Use arrow keys)
|
||||
Username
|
||||
❯ Email
|
||||
Phone Number
|
||||
Email or Phone Number
|
||||
I want to learn more.
|
||||
Do you want to configure advanced settings? (Use arrow keys)
|
||||
❯ No, I am done.
|
||||
Yes, I want to make some additional changes.
|
||||
? Enable conflict detection? N
|
||||
? Do you have an annotated GraphQL schema? N
|
||||
? Choose a schema template: (Use arrow keys)
|
||||
❯ Single object with fields (e.g., “Todo” with ID, name, description)
|
||||
One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
|
||||
Objects with fine-grained access control (e.g., a project management app with owner-based authorization)
|
||||
? Do you want to edit the schema now? Y
|
||||
# </Interactive>
|
||||
```
|
||||
|
||||
#### Edit GraphQL Schema
|
||||
|
||||
Open [`amplify/backend/api/nextjswithamplifyts/schema.graphql`](amplify/backend/api/nextjswithamplifyts/schema.graphql) and change it to the following:
|
||||
|
||||
```
|
||||
type Todo
|
||||
@model
|
||||
@auth(
|
||||
rules: [
|
||||
{ allow: owner } # Allow the creator of a todo to perform Create, Update, Delete operations.
|
||||
{ allow: public, operations: [read] } # Allow public (guest users without an account) to Read todos.
|
||||
{ allow: private, operations: [read] } # Allow private (other signed in users) to Read todos.
|
||||
]
|
||||
) {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Deploy infrastructure
|
||||
|
||||
```sh
|
||||
|
@ -95,10 +149,10 @@ $ amplify push
|
|||
javascript
|
||||
❯ typescript
|
||||
flow
|
||||
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.js)
|
||||
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.ts)
|
||||
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions (Y/n) Y
|
||||
? Enter maximum statement depth [increase from default if your schema is deeply nested] (2)
|
||||
|
||||
? Enter the file name for the generated code: src\API.ts
|
||||
# </Interactive>
|
||||
```
|
||||
|
||||
|
@ -111,9 +165,3 @@ npm run dev
|
|||
yarn
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### Edit GraphQL Schema
|
||||
|
||||
1. Open [`amplify/backend/api/nextjswithamplifyts/schema.graphql`](amplify/backend/api/nextjswithamplifyts/schema.graphql) and change what you need to.
|
||||
2. Run `amplify push`
|
||||
3. 👍
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
|
@ -11,17 +11,16 @@
|
|||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-amplify": "2.1.0",
|
||||
"immer": "3.1.3",
|
||||
"nanoid": "2.0.3",
|
||||
"next": "latest",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1"
|
||||
"@aws-amplify/ui-react": "^1.0.7",
|
||||
"aws-amplify": "^3.3.27",
|
||||
"next": "10.1.3",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "12.6.8",
|
||||
"@types/react": "16.9.36",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"typescript": "4.0"
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
import { Reducer, useReducer, Dispatch } from 'react'
|
||||
import { API, graphqlOperation } from 'aws-amplify'
|
||||
import nanoid from 'nanoid'
|
||||
import produce from 'immer'
|
||||
|
||||
import { ListTodosQuery, GetTodoListQuery } from '../src/API'
|
||||
import config from '../src/aws-exports'
|
||||
import {
|
||||
createTodo,
|
||||
deleteTodo,
|
||||
createTodoList,
|
||||
} from '../src/graphql/mutations'
|
||||
import { getTodoList } from '../src/graphql/queries'
|
||||
|
||||
const MY_ID = nanoid()
|
||||
API.configure(config)
|
||||
|
||||
type Todo = Omit<
|
||||
ListTodosQuery['listTodos']['items'][0],
|
||||
'__typename' | 'todoList'
|
||||
>
|
||||
|
||||
type Props = {
|
||||
todos: Todo[]
|
||||
}
|
||||
|
||||
type State = {
|
||||
todos: Todo[]
|
||||
currentName: string
|
||||
}
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: 'add-todo'
|
||||
payload: Todo
|
||||
}
|
||||
| {
|
||||
type: 'delete-todo'
|
||||
payload: string
|
||||
}
|
||||
| {
|
||||
type: 'reset-current'
|
||||
}
|
||||
| { type: 'set-current'; payload: string }
|
||||
|
||||
const reducer: Reducer<State, Action> = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'add-todo': {
|
||||
return produce(state, (draft) => {
|
||||
draft.todos.push(action.payload)
|
||||
})
|
||||
}
|
||||
case 'delete-todo': {
|
||||
const index = state.todos.findIndex(({ id }) => action.payload === id)
|
||||
if (index === -1) return state
|
||||
return produce(state, (draft) => {
|
||||
draft.todos.splice(index, 1)
|
||||
})
|
||||
}
|
||||
case 'reset-current': {
|
||||
return produce(state, (draft) => {
|
||||
draft.currentName = ''
|
||||
})
|
||||
}
|
||||
case 'set-current': {
|
||||
return produce(state, (draft) => {
|
||||
draft.currentName = action.payload
|
||||
})
|
||||
}
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createToDo = async (dispatch: Dispatch<Action>, currentToDo) => {
|
||||
const todo = {
|
||||
id: nanoid(),
|
||||
name: currentToDo,
|
||||
createdAt: `${Date.now()}`,
|
||||
completed: false,
|
||||
todoTodoListId: 'global',
|
||||
userId: MY_ID,
|
||||
}
|
||||
dispatch({ type: 'add-todo', payload: todo })
|
||||
dispatch({ type: 'reset-current' })
|
||||
try {
|
||||
await API.graphql(graphqlOperation(createTodo, { input: todo }))
|
||||
} catch (err) {
|
||||
dispatch({ type: 'set-current', payload: todo.name })
|
||||
console.warn('Error adding to do ', err)
|
||||
}
|
||||
}
|
||||
const deleteToDo = async (dispatch: Dispatch<Action>, id: string) => {
|
||||
dispatch({ type: 'delete-todo', payload: id })
|
||||
try {
|
||||
await API.graphql({
|
||||
...graphqlOperation(deleteTodo),
|
||||
variables: { input: { id } },
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('Error deleting to do ', err)
|
||||
}
|
||||
}
|
||||
const App = (props: Props) => {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
todos: props.todos,
|
||||
currentName: '',
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<h3>Add a Todo</h3>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
ev.preventDefault()
|
||||
createToDo(dispatch, state.currentName)
|
||||
}}
|
||||
>
|
||||
<input
|
||||
value={state.currentName}
|
||||
onChange={(e) => {
|
||||
dispatch({ type: 'set-current', payload: e.target.value })
|
||||
}}
|
||||
/>
|
||||
<button type="submit">Create Todo</button>
|
||||
</form>
|
||||
<h3>Todos List</h3>
|
||||
{state.todos.map((todo, index) => (
|
||||
<p key={index}>
|
||||
<a href={`/todo/${todo.id}`}>{todo.name}</a>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteToDo(dispatch, todo.id)
|
||||
}}
|
||||
>
|
||||
delete
|
||||
</button>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
let result = (await API.graphql(
|
||||
graphqlOperation(getTodoList, { id: 'global' })
|
||||
)) as { data: GetTodoListQuery; errors: any[] }
|
||||
|
||||
if (result.errors) {
|
||||
console.error('Failed to fetch todolist.', result.errors)
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
if (result.data.getTodoList !== null) {
|
||||
return {
|
||||
props: {
|
||||
todos: result.data.getTodoList.todos.items,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
await API.graphql(
|
||||
graphqlOperation(createTodoList, {
|
||||
input: {
|
||||
id: 'global',
|
||||
createdAt: `${Date.now()}`,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
props: {
|
||||
todos: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,48 +0,0 @@
|
|||
import { API, graphqlOperation } from 'aws-amplify'
|
||||
|
||||
import { GetTodoQuery, GetTodoListQuery } from '../../src/API'
|
||||
import { getTodo, getTodoList } from '../../src/graphql/queries'
|
||||
import config from '../../src/aws-exports'
|
||||
|
||||
API.configure(config)
|
||||
|
||||
const TodoPage = (props: { todo: GetTodoQuery['getTodo'] }) => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Individual Todo {props.todo.id}</h2>
|
||||
<pre>{JSON.stringify(props.todo, null, 2)}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
let result = (await API.graphql(
|
||||
graphqlOperation(getTodoList, { id: 'global' })
|
||||
)) as { data: GetTodoListQuery; errors: any[] }
|
||||
if (result.errors) {
|
||||
console.error('Failed to fetch todos paths.', result.errors)
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
const paths = result.data.getTodoList.todos.items.map(({ id }) => ({
|
||||
params: { id },
|
||||
}))
|
||||
return { paths, fallback: false }
|
||||
}
|
||||
|
||||
export const getStaticProps = async ({ params: { id } }) => {
|
||||
const todo = (await API.graphql({
|
||||
...graphqlOperation(getTodo),
|
||||
variables: { id },
|
||||
})) as { data: GetTodoQuery; errors: any[] }
|
||||
if (todo.errors) {
|
||||
console.error('Failed to fetch todo.', todo.errors)
|
||||
throw new Error(todo.errors[0].message)
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
todo: todo.data.getTodo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default TodoPage
|
BIN
examples/with-aws-amplify-typescript/public/favicon.ico
Normal file
BIN
examples/with-aws-amplify-typescript/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
4
examples/with-aws-amplify-typescript/public/vercel.svg
Normal file
4
examples/with-aws-amplify-typescript/public/vercel.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,15 +0,0 @@
|
|||
type Todo @model {
|
||||
# ! means non-null GraphQL fields are allowed to be null by default
|
||||
id: ID!
|
||||
name: String!
|
||||
createdAt: String!
|
||||
completed: Boolean!
|
||||
todoList: TodoList! @connection(name: "SortedList")
|
||||
userId: String!
|
||||
}
|
||||
|
||||
type TodoList @model {
|
||||
id: ID!
|
||||
createdAt: String!
|
||||
todos: [Todo] @connection(name: "SortedList", sortField: "createdAt")
|
||||
}
|
118
examples/with-aws-amplify-typescript/src/pages/index.tsx
Normal file
118
examples/with-aws-amplify-typescript/src/pages/index.tsx
Normal file
|
@ -0,0 +1,118 @@
|
|||
import { AmplifyAuthenticator } from '@aws-amplify/ui-react'
|
||||
import { Amplify, API, Auth, withSSRContext } from 'aws-amplify'
|
||||
import Head from 'next/head'
|
||||
import awsExports from '../aws-exports'
|
||||
import { createTodo } from '../graphql/mutations'
|
||||
import { listTodos } from '../graphql/queries'
|
||||
import {
|
||||
CreateTodoInput,
|
||||
CreateTodoMutation,
|
||||
ListTodosQuery,
|
||||
Todo,
|
||||
} from '../API'
|
||||
import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'
|
||||
import { useRouter } from 'next/router'
|
||||
import { GetServerSideProps } from 'next'
|
||||
import styles from '../styles/Home.module.css'
|
||||
|
||||
Amplify.configure({ ...awsExports, ssr: true })
|
||||
|
||||
export default function Home({ todos = [] }: { todos: Todo[] }) {
|
||||
const router = useRouter()
|
||||
|
||||
async function handleCreateTodo(event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = new FormData(event.target)
|
||||
|
||||
try {
|
||||
const createInput: CreateTodoInput = {
|
||||
name: form.get('title').toString(),
|
||||
description: form.get('content').toString(),
|
||||
}
|
||||
|
||||
const request = (await API.graphql({
|
||||
authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
|
||||
query: createTodo,
|
||||
variables: {
|
||||
input: createInput,
|
||||
},
|
||||
})) as { data: CreateTodoMutation; errors: any[] }
|
||||
|
||||
router.push(`/todo/${request.data.createTodo.id}`)
|
||||
} catch ({ errors }) {
|
||||
console.error(...errors)
|
||||
throw new Error(errors[0].message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Head>
|
||||
<title>Amplify + Next.js</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main className={styles.main}>
|
||||
<h1 className={styles.title}>Amplify + Next.js</h1>
|
||||
|
||||
<p className={styles.description}>
|
||||
<code className={styles.code}>{todos.length}</code>
|
||||
Todos
|
||||
</p>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{todos.map((todo) => (
|
||||
<a href={`/todo/${todo.id}`} key={todo.id}>
|
||||
<h3>{todo.name}</h3>
|
||||
<p>{todo.description}</p>
|
||||
</a>
|
||||
))}
|
||||
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.title}>New Todo</h3>
|
||||
|
||||
<AmplifyAuthenticator>
|
||||
<form onSubmit={handleCreateTodo}>
|
||||
<fieldset>
|
||||
<legend>Title</legend>
|
||||
<input
|
||||
defaultValue={`Today, ${new Date().toLocaleTimeString()}`}
|
||||
name="title"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Content</legend>
|
||||
<textarea
|
||||
defaultValue="I built an Amplify app with Next.js!"
|
||||
name="content"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<button>Create Todo</button>
|
||||
<button type="button" onClick={() => Auth.signOut()}>
|
||||
Sign out
|
||||
</button>
|
||||
</form>
|
||||
</AmplifyAuthenticator>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
|
||||
const SSR = withSSRContext({ req })
|
||||
|
||||
const response = (await SSR.API.graphql({ query: listTodos })) as {
|
||||
data: ListTodosQuery
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
todos: response.data.listTodos.items,
|
||||
},
|
||||
}
|
||||
}
|
100
examples/with-aws-amplify-typescript/src/pages/todo/[id].tsx
Normal file
100
examples/with-aws-amplify-typescript/src/pages/todo/[id].tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import { Amplify, API, withSSRContext } from 'aws-amplify'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { DeleteTodoInput, GetTodoQuery, Todo, ListTodosQuery } from '../../API'
|
||||
import awsExports from '../../aws-exports'
|
||||
import { deleteTodo } from '../../graphql/mutations'
|
||||
import { getTodo, listTodos } from '../../graphql/queries'
|
||||
import { GetStaticProps, GetStaticPaths } from 'next'
|
||||
import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api'
|
||||
import styles from '../../styles/Home.module.css'
|
||||
|
||||
Amplify.configure({ ...awsExports, ssr: true })
|
||||
|
||||
export default function TodoPage({ todo }: { todo: Todo }) {
|
||||
const router = useRouter()
|
||||
|
||||
if (router.isFallback) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>Loading…</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function handleDelete(): Promise<void> {
|
||||
try {
|
||||
const deleteInput: DeleteTodoInput = {
|
||||
id: todo.id,
|
||||
}
|
||||
|
||||
await API.graphql({
|
||||
authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
|
||||
query: deleteTodo,
|
||||
variables: {
|
||||
input: deleteInput,
|
||||
},
|
||||
})
|
||||
|
||||
router.push(`/`)
|
||||
} catch ({ errors }) {
|
||||
console.error(...errors)
|
||||
throw new Error(errors[0].message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Head>
|
||||
<title>{todo.name} – Amplify + Next.js</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main className={styles.main}>
|
||||
<h1 className={styles.title}>{todo.name}</h1>
|
||||
<p className={styles.description}>{todo.description}</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<button className={styles.footer} onClick={handleDelete}>
|
||||
💥 Delete todo
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const SSR = withSSRContext()
|
||||
|
||||
const todosQuery = (await SSR.API.graphql({
|
||||
query: listTodos,
|
||||
authMode: GRAPHQL_AUTH_MODE.API_KEY,
|
||||
})) as { data: ListTodosQuery; errors: any[] }
|
||||
|
||||
const paths = todosQuery.data.listTodos.items.map((todo: Todo) => ({
|
||||
params: { id: todo.id },
|
||||
}))
|
||||
|
||||
return {
|
||||
fallback: true,
|
||||
paths,
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
const SSR = withSSRContext()
|
||||
|
||||
const response = (await SSR.API.graphql({
|
||||
query: getTodo,
|
||||
variables: {
|
||||
id: params.id,
|
||||
},
|
||||
})) as { data: GetTodoQuery }
|
||||
|
||||
return {
|
||||
props: {
|
||||
todo: response.data.getTodo,
|
||||
},
|
||||
}
|
||||
}
|
123
examples/with-aws-amplify-typescript/src/styles/Home.module.css
Normal file
123
examples/with-aws-amplify-typescript/src/styles/Home.module.css
Normal file
|
@ -0,0 +1,123 @@
|
|||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer img {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
flex-basis: 45%;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
16
examples/with-aws-amplify-typescript/src/styles/globals.css
Normal file
16
examples/with-aws-amplify-typescript/src/styles/globals.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
Loading…
Reference in a new issue