Skip to content

Commit

Permalink
Add nextjs-scheduler example (#637)
Browse files Browse the repository at this point in the history
  • Loading branch information
banma1234 authored and hackerwins committed Sep 5, 2023
1 parent 4bd7a07 commit 6272388
Show file tree
Hide file tree
Showing 22 changed files with 2,379 additions and 18 deletions.
2 changes: 2 additions & 0 deletions examples/nextjs-scheduler/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_YORKIE_API_ADDR='http://localhost:8080'
NEXT_PUBLIC_YORKIE_API_KEY=''
2 changes: 2 additions & 0 deletions examples/nextjs-scheduler/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_YORKIE_API_ADDR='https://api.yorkie.dev'
NEXT_PUBLIC_YORKIE_API_KEY='cedaovjuioqlk4pjqn6g'
10 changes: 10 additions & 0 deletions examples/nextjs-scheduler/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
};
35 changes: 35 additions & 0 deletions examples/nextjs-scheduler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
29 changes: 29 additions & 0 deletions examples/nextjs-scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Yorkie Next.js react-calendar Example

<p>
<a href="https://yorkie.dev/yorkie-js-sdk/examples/nextjs-scheduler/" target="_blank">
<img src="https://img.shields.io/badge/preview-message?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMTUiIHZpZXdCb3g9IjAgMCAyNCAxNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYuODU3MTcgMi43ODE5OUwxMS4yNzUxIDkuMTI2NzhDMTEuNTU0NCA5LjUyODAxIDEyLjEwNjIgOS42MjY3NiAxMi41MDc0IDkuMzQ3NDRDMTIuNTkzNCA5LjI4NzUgMTIuNjY4MSA5LjIxMjggMTIuNzI4MSA5LjEyNjc4TDE3LjE0NiAyLjc4MTk5QzE3LjcwNDggMS45Nzk1NCAxNy41MDcyIDAuODc2MTMxIDE2LjcwNDggMC4zMTc0OTRDMTYuNDA4IDAuMTEwODM3IDE2LjA1NSAwIDE1LjY5MzIgMEg4LjMxMDAxQzcuMzMyMiAwIDYuNTM5NTUgMC43OTI2NTQgNi41Mzk1NSAxLjc3MDQ2QzYuNTM5NjggMi4xMzIxMSA2LjY1MDUxIDIuNDg1MTEgNi44NTcxNyAyLjc4MTk5WiIgZmlsbD0iIzUxNEM0OSIvPgo8cGF0aCBkPSJNMTMuODA4OSAxNC4yMzg4QzE0LjEyMzEgMTQuNDE4IDE0LjQ4NDcgMTQuNDk2NiAxNC44NDUgMTQuNDY0MkwyMi45MjYgMTMuNzM1QzIzLjU3NTMgMTMuNjc2NSAyNC4wNTQgMTMuMTAyNyAyMy45OTU1IDEyLjQ1MzVDMjMuOTkyNCAxMi40MTkyIDIzLjk4NzggMTIuMzg1MSAyMy45ODE3IDEyLjM1MTNDMjMuNzM4OSAxMC45OTY4IDIzLjI2MTEgOS42OTUyNyAyMi41Njk5IDguNTA1NDZDMjEuODc4NiA3LjMxNTY1IDIwLjk4NDggNi4yNTU3NyAxOS45Mjg2IDUuMzczOTFDMTkuNDI4MiA0Ljk1NjE0IDE4LjY4MzkgNS4wMjMwNyAxOC4yNjYyIDUuNTIzNTZDMTguMjQ0MiA1LjU0OTkgMTguMjIzMyA1LjU3NzI2IDE4LjIwMzYgNS42MDU1MUwxMy41NjcgMTIuMjY0MUMxMy4zNjAzIDEyLjU2MSAxMy4yNDk1IDEyLjkxNCAxMy4yNDk1IDEzLjI3NThWMTMuMjUzN0MxMy4yNDk1IDEzLjQ1NjIgMTMuMzAxNiAxMy42NTU0IDEzLjQwMDggMTMuODMxOUMxMy41MDUgMTQuMDA1NCAxMy42NTIxIDE0LjE0OTMgMTMuODI4MSAxNC4yNDk2IiBmaWxsPSIjRkRDNDMzIi8+CjxwYXRoIGQ9Ik0xMC42NDE2IDEzLjc0MzRDMTAuNTM3NSAxMy45NTU5IDEwLjM3MiAxNC4xMzIyIDEwLjE2NjUgMTQuMjQ5NEwxMC4xOTE1IDE0LjIzNTFDOS44NzczNCAxNC40MTQzIDkuNTE1NjkgMTQuNDkyOSA5LjE1NTQ0IDE0LjQ2MDVMMS4wNzQ0MSAxMy43MzEzQzEuMDQwMTggMTMuNzI4MyAxLjAwNjA3IDEzLjcyMzcgMC45NzIyMjUgMTMuNzE3NkMwLjMzMDYyIDEzLjYwMjUgLTAuMDk2MzExOSAxMi45ODkyIDAuMDE4NzI0MiAxMi4zNDc2QzAuMjYxNTIyIDEwLjk5MyAwLjczOTM1NCA5LjY5MTU2IDEuNDMwNDYgOC41MDE2M0MyLjEyMTU3IDcuMzExNjkgMy4wMTU1MSA2LjI1MjA2IDQuMDcxODQgNS4zNzAwOEM0LjA5ODE4IDUuMzQ4MDYgNC4xMjU1NCA1LjMyNzE5IDQuMTUzNzkgNS4zMDc0N0M0LjY4ODc2IDQuOTM1IDUuNDI0MjcgNS4wNjY3MSA1Ljc5Njg3IDUuNjAxNjhMMTAuNDMzNCAxMi4yNjA0QzEwLjY0MDEgMTIuNTU3MyAxMC43NTA5IDEyLjkxMDMgMTAuNzUwOSAxMy4yNzIxVjEzLjI0MzJDMTAuNzUwOSAxMy40Nzk3IDEwLjY3OTggMTMuNzExIDEwLjU0NjggMTMuOTA2NyIgZmlsbD0iI0ZEQzQzMyIvPgo8L3N2Zz4K&color=FEF3D7" alt="Live Preview" />
</a>
</p>

<img width="500" alt="Next.js react-calendar" src="thumbnail.jpg"/>

## How to run demo

At project root, run below command to start Yorkie server and Envoy proxy.

```bash
$ docker-compose up -f docker/docker-compose.yml up --build -d
```

Then install dependencies and run the demo.

```bash
$ npm install
```

Now you can run the demo.

```bash
$ npm run dev
```
89 changes: 89 additions & 0 deletions examples/nextjs-scheduler/app/Scheduler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client';

import React, { useState } from 'react';
import './styles/calendar.css';
import styles from './styles/page.module.css';

import { EditorPropsTypes, CalendarValue } from './utils/types';
import { parseDate } from './utils/parseDate';
import Calendar from 'react-calendar';

/**
* handle calendar component
*/
export default function Scheduler(props: EditorPropsTypes) {
const { content, actions } = props;
const [date, onChange] = useState<CalendarValue>(new Date());
const [text, setText] = useState<string>('Enter text here!');

const currentDate = date ? parseDate(new Date(date.toString())) : '';

const eventHandler = (event: string) => {
let flag = false;
switch (event) {
case 'PUSH':
flag = false;
content.forEach((item) => {
if (item.date === currentDate) {
flag = !flag;
return 0;
}
});

flag
? actions.updateContent(currentDate, text)
: actions.addContent(currentDate, text);

setText('Enter text here!');
break;
case 'DELETE':
actions.deleteContent(currentDate);
break;
}
};

return (
<article>
<div>
<Calendar
onChange={onChange}
value={date}
locale="en-EN"
showNeighboringMonth={false}
formatDay={(locale, date) =>
date.toLocaleString('en', { day: 'numeric' })
}
tileClassName={({ date }) =>
content.find((item) => item.date === parseDate(date))
? 'highlight'
: ''
}
/>
<p>selected day : {currentDate}</p>
<div className={styles.memo}>
{content.map((item, i: number) => {
if (item.date === currentDate) {
return <p key={i}>{item.text}</p>;
}
})}
</div>
<div className={styles.inputForm_editor}>
<h3>input form</h3>
<textarea
className={styles.textArea}
value={text}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setText(e.target.value)
}
/>
</div>
<button className="button" onClick={() => eventHandler('PUSH')}>
push
</button>
<button className="button" onClick={() => eventHandler('DELETE')}>
pop
</button>
</div>
</article>
);
}
Binary file added examples/nextjs-scheduler/app/favicon.ico
Binary file not shown.
25 changes: 25 additions & 0 deletions examples/nextjs-scheduler/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import './styles/globals.css';
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Next.js react-calendar example',
description: 'example of yorkie-js-sdk with next.js & react-calendar',
icons: {
icon: './favicon.ico',
},
};

/**
* default root layout of service
*/
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
6 changes: 6 additions & 0 deletions examples/nextjs-scheduler/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* 404-not found
*/
export default function notFound() {
return <h1>404 not found</h1>;
}
150 changes: 150 additions & 0 deletions examples/nextjs-scheduler/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* yorkie-js-sdk must be loaded on client-side
*/
'use client';

import styles from './styles/page.module.css';
import React, { useEffect, useState } from 'react';

import { ContentTypes, ENVtypes } from './utils/types';
import { displayPeers, createRandomPeers } from './utils/handlePeers';
import { parseDate } from './utils/parseDate';
import yorkie, { Document, JSONArray, DocEventType } from 'yorkie-js-sdk';
import Scheduler from './Scheduler';

// parseDate() value's format = "DD-MM-YYYY"
const defaultContent: JSONArray<ContentTypes> = [
{
date: parseDate(new Date()).replace(/^\d{2}/, '01'),
text: 'payday',
},
{
date: parseDate(new Date()).replace(/^\d{2}/, '17'),
text: "Garry's birthday",
},
];

const ENV: ENVtypes = {
url: process.env.NEXT_PUBLIC_YORKIE_API_ADDR!,
apiKey: process.env.NEXT_PUBLIC_YORKIE_API_KEY!,
};

const documentKey = `next.js-Scheduler-${parseDate(new Date())}`;

/**
* main page
*/
export default function Editor() {
const [peers, setPeers] = useState<Array<string>>([]);
const [content, setContent] = useState<Array<ContentTypes>>(defaultContent);

// create Yorkie Document with useState value
const [doc] = useState<Document<{ content: JSONArray<ContentTypes> }>>(
() =>
new yorkie.Document<{ content: JSONArray<ContentTypes> }>(documentKey),
);

const actions = {
// push new content to Yorkie's database
addContent(date: string, text: string) {
doc.update((root) => {
root.content.push({ date, text });
});
},

// delete selected content at Yorkie's database
deleteContent(date: string) {
doc.update((root) => {
let target;
for (const item of root.content) {
if (item.date === date) {
target = item as any;
break;
}
}

if (target) {
root.content.deleteByID!(target.getID());
}
});
},

// edit selected content at Yorkie's database
updateContent(date: string, text: string) {
doc.update((root) => {
let target;
for (const item of root.content) {
if (item.date === date) {
target = item;
break;
}
}

if (target) {
target.text = text;
}
});
},
};

useEffect(() => {
// create Yorkie Client at client-side
const client = new yorkie.Client(ENV.url, {
apiKey: ENV.apiKey,
});

// subscribe document event of "PresenceChanged"(="peers-changed")
doc.subscribe('presence', (event) => {
if (event.type !== DocEventType.PresenceChanged) {
setPeers(displayPeers(doc.getPresences()));
}
});

/**
* `attachDoc` is a helper function to attach the document into the client.
*/
async function attachDoc(
doc: Document<{ content: JSONArray<ContentTypes> }>,
callback: (props: any) => void,
) {
// 01. activate client
await client.activate();
// 02. attach the document into the client with presence
await client.attach(doc, {
initialPresence: {
userName: createRandomPeers(),
},
});

// 03. create default content if not exists.
doc.update((root) => {
if (!root.content) {
root.content = defaultContent;
}
}, 'create default content if not exists');

// 04. subscribe doc's change event from local and remote.
doc.subscribe((event) => {
callback(doc.getRoot().content);
});

// 05. set content to the attached document.
callback(doc.getRoot().content);
}

attachDoc(doc, (content) => setContent(content));
}, []);

return (
<main className={styles.main}>
<p>
peers : [
{peers.map((man: string, i: number) => {
return <span key={i}> {man}, </span>;
})}{' '}
]
</p>
<Scheduler content={content} actions={actions} />
</main>
);
}
Loading

0 comments on commit 6272388

Please sign in to comment.