From cd1e2e1d60d75a7ca1443c6c829ec6b49123a93b Mon Sep 17 00:00:00 2001 From: xeust Date: Sat, 14 Nov 2020 00:00:30 +0100 Subject: [PATCH] Example: Deta Base (#19061) Add an example for using Next.js with Deta Base on Vercel. --- examples/with-deta-base/.env.local.example | 1 + examples/with-deta-base/README.md | 73 +++++ examples/with-deta-base/package.json | 16 ++ examples/with-deta-base/pages/_app.js | 7 + .../with-deta-base/pages/api/todos/[tid].js | 27 ++ .../with-deta-base/pages/api/todos/index.js | 25 ++ examples/with-deta-base/pages/index.js | 147 +++++++++++ examples/with-deta-base/public/deta.svg | 7 + examples/with-deta-base/public/favicon.ico | Bin 0 -> 15086 bytes examples/with-deta-base/public/vercel.svg | 4 + .../with-deta-base/styles/Home.module.css | 249 ++++++++++++++++++ examples/with-deta-base/styles/globals.css | 26 ++ 12 files changed, 582 insertions(+) create mode 100644 examples/with-deta-base/.env.local.example create mode 100644 examples/with-deta-base/README.md create mode 100644 examples/with-deta-base/package.json create mode 100644 examples/with-deta-base/pages/_app.js create mode 100644 examples/with-deta-base/pages/api/todos/[tid].js create mode 100644 examples/with-deta-base/pages/api/todos/index.js create mode 100644 examples/with-deta-base/pages/index.js create mode 100644 examples/with-deta-base/public/deta.svg create mode 100644 examples/with-deta-base/public/favicon.ico create mode 100644 examples/with-deta-base/public/vercel.svg create mode 100644 examples/with-deta-base/styles/Home.module.css create mode 100644 examples/with-deta-base/styles/globals.css diff --git a/examples/with-deta-base/.env.local.example b/examples/with-deta-base/.env.local.example new file mode 100644 index 0000000000..330c81f9aa --- /dev/null +++ b/examples/with-deta-base/.env.local.example @@ -0,0 +1 @@ +DETA_PROJECT_KEY= diff --git a/examples/with-deta-base/README.md b/examples/with-deta-base/README.md new file mode 100644 index 0000000000..8e7e989fd1 --- /dev/null +++ b/examples/with-deta-base/README.md @@ -0,0 +1,73 @@ +# Deta Base Example + +An example using [Deta Base](https://docs.deta.sh/docs/base/about) in a Next.js project. + +## Deploy your own + +Once you have access to [the environment variables you'll need](#step-2-setting-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-deta-base&env=DETA_PROJECT_KEY&envDescription=The%20Deta%20Project%20Key%2C%20found%20in%20the%20Deta%20dashboard&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-deta-base%23configuration) + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example with-deta-base with-deta-base-app +# or +yarn create next-app --example with-deta-base with-deta-base-app +``` + +## Configuration + +### Step 1. Create a Deta Account + +Create an account on [Deta](https://www.deta.sh/?ref=next.js). Save the default _Project Key_ which will be auto-generated on account creation. + +### Step 2. Setting Up Environment Variables + +Copy the `.env.local.example` file from this directory to `.env.local` (which will be ignored by Git): + +```bash +cp .env.local.example .env.local +``` + +Then set each variable on `.env.local`: + +- `DETA_PROJECT_KEY` should be the default _Project Key_ that you saved from step 1. + +The resulting `env.local` file shoule look like this: + +```bash +DETA_PROEJECT_KEY=... +``` + +### Step 3. Run Next.js in development mode + +```bash +npm install +npm run dev + +# or + +yarn install +yarn dev +``` + +Your todo app should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). + +### Step 4. Deploy on Vercel + +You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +#### Deploy Your Local Project + +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. + +#### Deploy from Our Template + +Alternatively, you can deploy using our template by clicking on the Deploy button below. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-deta-base&env=DETA_PROJECT_KEY&envDescription=The%20Deta%20Project%20Key%2C%20found%20in%20the%20Deta%20dashboard&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-deta-base%23configuration) diff --git a/examples/with-deta-base/package.json b/examples/with-deta-base/package.json new file mode 100644 index 0000000000..12d79c5b44 --- /dev/null +++ b/examples/with-deta-base/package.json @@ -0,0 +1,16 @@ +{ + "name": "with-deta-base", + "version": "1.0.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "deta": "^0.0.8", + "next": "latest", + "react": "17.0.1", + "react-dom": "17.0.1" + }, + "license": "MIT" +} diff --git a/examples/with-deta-base/pages/_app.js b/examples/with-deta-base/pages/_app.js new file mode 100644 index 0000000000..1e1cec9242 --- /dev/null +++ b/examples/with-deta-base/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/with-deta-base/pages/api/todos/[tid].js b/examples/with-deta-base/pages/api/todos/[tid].js new file mode 100644 index 0000000000..67b70aec98 --- /dev/null +++ b/examples/with-deta-base/pages/api/todos/[tid].js @@ -0,0 +1,27 @@ +import { Deta } from 'deta' + +const deta = Deta(process.env.DETA_PROJECT_KEY) + +const base = deta.Base('todos') + +const handler = async (req, res) => { + let { + body, + method, + query: { tid }, + } = req + let respBody = {} + + if (method === 'PUT') { + body = JSON.parse(body) + respBody = await base.put(body) + res.statusCode = 200 + } else if (method === 'DELETE') { + respBody = await base.delete(tid) + res.statusCode = 200 + } + + res.json(respBody) +} + +export default handler diff --git a/examples/with-deta-base/pages/api/todos/index.js b/examples/with-deta-base/pages/api/todos/index.js new file mode 100644 index 0000000000..1609a9736e --- /dev/null +++ b/examples/with-deta-base/pages/api/todos/index.js @@ -0,0 +1,25 @@ +import { Deta } from 'deta' + +const deta = Deta(process.env.DETA_PROJECT_KEY) + +const base = deta.Base('todos') + +const handler = async (req, res) => { + let { body, method } = req + let respBody = {} + + if (method === 'GET') { + const { value: items } = await base.fetch([]).next() + respBody = items + res.statusCode = 200 + } else if (method === 'POST') { + body = JSON.parse(body) + body.isCompleted = false + respBody = await base.put(body) + res.statusCode = 201 + } + + res.json(respBody) +} + +export default handler diff --git a/examples/with-deta-base/pages/index.js b/examples/with-deta-base/pages/index.js new file mode 100644 index 0000000000..7fcc39ca6f --- /dev/null +++ b/examples/with-deta-base/pages/index.js @@ -0,0 +1,147 @@ +import { useState, useEffect } from 'react' +import Head from 'next/head' +import styles from '../styles/Home.module.css' + +const ToDo = ({ content, isCompleted, onChange, onDelete }) => { + const cards = ['card', 'card2', 'card3', 'card4', 'card5'] + return ( +
+
+ {content} +
+
+ + +
+
+ ) +} + +export default function Home() { + const [newContent, setNewContent] = useState('') + + const [toDos, setToDos] = useState([]) + + const getToDos = async () => { + const resp = await fetch('api/todos') + const toDos = await resp.json() + setToDos(toDos) + } + + const createToDo = async () => { + await fetch('api/todos', { + method: 'post', + body: JSON.stringify({ content: newContent }), + }) + await getToDos() + } + + const updateToDo = async (todo) => { + let newBody = { + ...todo, + isCompleted: !todo.isCompleted, + } + await fetch(`api/todos/${todo.key}`, { + method: 'put', + body: JSON.stringify(newBody), + }) + + await getToDos() + } + + const deleteToDo = async (tid) => { + await fetch(`api/todos/${tid}`, { method: 'delete' }) + setTimeout(getToDos, 300) + } + + useEffect(() => { + getToDos() + }, []) + + const completed = toDos.filter((todo) => todo.isCompleted) + const notCompleted = toDos.filter((todo) => !todo.isCompleted) + return ( +
+ + deta + next.js + + + +
+

+ deta base +{' '} + next.js to dos +

+
+
+
+
+
to dos
+
+ setNewContent(e.target.value)} + > + +
+
+
+ {notCompleted.map((todo, index) => ( + updateToDo(todo)} + onDelete={() => deleteToDo(todo.key)} + /> + ))} +
+
+ +
+
+
done
+
+
+ {completed.map((todo, index) => ( + updateToDo(todo)} + onDelete={() => deleteToDo(todo.key)} + /> + ))} +
+
+
+ + +
+ ) +} diff --git a/examples/with-deta-base/public/deta.svg b/examples/with-deta-base/public/deta.svg new file mode 100644 index 0000000000..3ecadda8de --- /dev/null +++ b/examples/with-deta-base/public/deta.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/with-deta-base/public/favicon.ico b/examples/with-deta-base/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4965832f2c9b0605eaa189b7c7fb11124d24e48a GIT binary patch literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- + + \ No newline at end of file diff --git a/examples/with-deta-base/styles/Home.module.css b/examples/with-deta-base/styles/Home.module.css new file mode 100644 index 0000000000..ca9ede8b8e --- /dev/null +++ b/examples/with-deta-base/styles/Home.module.css @@ -0,0 +1,249 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.main { + padding: 1rem 0; + width: 100%; + flex: 1; + height: calc(100vh - 100px); + display: flex; + flex-direction: row; + justify-content: center; +} + +.complete, +.incomplete { + display: flex; + flex-direction: column; + padding-left: 1.5rem; + padding-right: 1.5rem; + width: 50%; +} + +.scrolly { + display: flex; + flex-direction: column; + height: calc(100vh - 132px); + overflow: scroll; +} + +.firstRow { + display: flex; + align-items: center; + height: 52px; +} + +.reverseWrapper { + display: flex; + margin-left: auto; + height: 100%; + align-items: center; +} + +.header { + width: 100%; + height: 50px; + border-bottom: 1px solid #f1f1f1; + display: flex; + justify-content: flex-start; + align-items: center; +} + +.header h2 { + padding-left: 24px; +} + +.inpt { + border: 1px solid #c5c4c7; + border-radius: 2px; + padding-left: 5px; + padding-right: 5px; + font-size: 16px; + height: 32px; + margin-right: 10px; +} + +.inpt:focus, +.inpt:active { + border: 1px solid #2b66ff; +} + +.addBtn, +.delBtn { + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + background-color: transparent; + border: 1px solid transparent; + color: #c5c4c7; + border-radius: 2px; + height: 32px; + width: 32px; +} + +.addBtn:hover, +.addBtn:focus, +.addBtn:active { + color: #1cec73; +} + +.delBtn:hover, +.delBtn:focus, +.delBtn:active { + color: #ee6262; +} + +.check { + background-color: transparent; + border: 1px solid #c5c4c7; + border-radius: 2px; + height: 20px; + width: 20px; + margin-right: 10px; +} + +.footer { + width: 100%; + height: 50px; + border-top: 1px solid #f1f1f1; + 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: #2b66ff; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 2rem; + padding-top: 10px; + padding-bottom: 10px; +} + +.title, +.description { + text-align: flex-start; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #c5c4c7; + 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; +} + +.card, +.card2, +.card3, +.card4, +.card5 { + display: flex; + color: #c5c4c7; + font-size: 18px; + font-weight: bold; + margin-top: 1rem !important; + margin-bottom: 1rem; + height: 50px; + align-items: center; + text-align: left; + text-decoration: none; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; +} + +.card2:hover, +.card2:focus, +.card2:active { + color: #ae00f3; +} + +.card3:hover, +.card3:focus, +.card3:active { + color: #f3003d; +} + +.card4:hover, +.card4:focus, +.card4:active { + color: #f39200; +} + +.card4:hover, +.card4:focus, +.card4:active { + color: #29f300; +} + +.card5:hover, +.card5:focus, +.card5:active { + color: #00f3cb; +} + +.card, +.card2, +.card3, +.card4, +.card5 h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.text { + display: flex; + align-items: center; + text-transform: lowercase; + margin: 0; + font-size: 1.5rem; + line-height: 1.5; +} + +.logo { + height: 1em; +} + +@media (max-width: 600px) { + .main { + width: 100%; + flex-direction: column; + } +} diff --git a/examples/with-deta-base/styles/globals.css b/examples/with-deta-base/styles/globals.css new file mode 100644 index 0000000000..ee7434eb90 --- /dev/null +++ b/examples/with-deta-base/styles/globals.css @@ -0,0 +1,26 @@ +html, +body { + padding: 0; + margin: 0; + font-family: 'Source Code Pro', Courier New, monospace; +} + +a { + color: #0070f3; + text-decoration: none; +} + +input, +button { + font-family: 'Source Code Pro', Arial, sans-serif; +} + +a:hover, +a:focus, +a:active { + text-decoration: underline; +} + +* { + box-sizing: border-box; +}