Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add social network example #174

Merged
merged 7 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
runtimes
runtimes
examples/social-network/frontend
15 changes: 15 additions & 0 deletions examples/social-network/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Social Network Example

This example was created as part of the [talk Fran Mendez did at QCon Plus 2021](https://plus.qconferences.com/plus2021/speakers/fran-mendez).

## Run it

There are three services. All of them can be started by running:
fmvilas marked this conversation as resolved.
Show resolved Hide resolved

```
npm run dev
```

You'll have to customize the `db.json` file with your own data (especially the Slack IDs).

For the notifications service, you'll have to configure the Slack API URL in the `.env` file. Head over https://api.slack.com/messaging/webhooks to know more.
46 changes: 46 additions & 0 deletions examples/social-network/db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"likes": [
],
"users": {
"1": {
"id": 1,
"slackId": "<@U34F2JRRS>",
"coverImageUrl": "https://pbs.twimg.com/profile_banners/87489271/1617659258/1500x500",
"imageUrl": "https://pbs.twimg.com/profile_images/1373387614238179328/cB1gp6Lh_400x400.jpg",
"name": "Fran Mendez"
},
"2": {
"id": 2,
"slackId": "<@UD698Q5LM>",
"coverImageUrl": "https://pbs.twimg.com/profile_banners/407861759/1632725036/1500x500",
"imageUrl": "https://pbs.twimg.com/profile_images/1435858354967093254/carWrDQa_400x400.jpg",
"name": "Lukasz Gornicki"
}
},
"posts": [
{
"id": 1,
"userId": 1,
"imageUrl": "https://images.unsplash.com/photo-1466637574441-749b8f19452f?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=880&q=80",
"text": "Cooking at home with some friends and good wine 🍷"
},
{
"id": 11,
"userId": 2,
"imageUrl": "https://images.unsplash.com/photo-1502599213010-875782f9bf52?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=697&q=80",
"text": "The best of working remotely? Hacking on my favorite coffee-shop 🥤🤙🏽<br /><br /><strong>#lifestyle</strong>"
},
{
"id": 2,
"userId": 1,
"imageUrl": "https://images.unsplash.com/photo-1503919545889-aef636e10ad4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=687&q=80",
"text": "Going to the forest 🌳🦌 with my little homie 👧🏻"
},
{
"id": 10,
"userId": 2,
"imageUrl": "https://images.unsplash.com/photo-1586348943529-beaae6c28db9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=512&q=80",
"text": "I was missing a bit of this. Holidays, finally!"
}
]
}
34 changes: 34 additions & 0 deletions examples/social-network/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
Comment on lines +5 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣 First I thought it said php, and I was like 🤨


# 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
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
29 changes: 29 additions & 0 deletions examples/social-network/frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Next.js + Tailwind CSS Example

This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).

It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.

## Preview

Preview the example live on [StackBlitz](http://stackblitz.com/):

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)

## Deploy your own

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-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)

## 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-tailwindcss with-tailwindcss-app
# or
yarn create next-app --example with-tailwindcss with-tailwindcss-app
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
9 changes: 9 additions & 0 deletions examples/social-network/frontend/components/Avatar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function Avatar({ imageUrl, name, className='' }) {
return (
<img
className={`inline-block h-10 w-10 rounded-full ${className}`}
src={imageUrl}
alt={name}
/>
)
}
37 changes: 37 additions & 0 deletions examples/social-network/frontend/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MailIcon } from '@heroicons/react/solid'
import classNames from 'classnames'

export default function Header({profile}) {
return (
<div>
<div>
<img className="h-32 w-full object-cover lg:h-48" src={profile.coverImageUrl} alt="" />
</div>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:pl-8 lg:pr-4">
<div className="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
<div className="flex">
<img
className="h-24 w-24 rounded-full ring-4 ring-white sm:h-32 sm:w-32"
src={profile.imageUrl}
alt=""
/>
</div>
<div className="mt-6 sm:flex-1 sm:min-w-0 sm:flex sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
<div className="sm:block mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
<div className="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<MailIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Message</span>
</button>
</div>
</div>
</div>
</div>
</div>
)
}
59 changes: 59 additions & 0 deletions examples/social-network/frontend/components/Post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useContext, useEffect, useState } from 'react'
import { HeartIcon } from '@heroicons/react/outline'
import { HeartIcon as HeartIconSolid } from '@heroicons/react/solid'
import WS from '../helpers/ws'
import UserContext from '../context/user'
import Avatar from './Avatar'

export default function Post({ post, user }) {
const loggedInUser = useContext(UserContext)
const [liked, setLiked] = useState(!!post.likes.find(l => l.postId === post.id && l.userId === loggedInUser.id))
const [likeCount, setLikes] = useState(post.likes.filter(l => l.postId === post.id).length)

const onLike = () => {
if (liked) {
WS.send('dislike', { postId: post.id, userId: loggedInUser.id })
setLiked(false)
} else {
WS.send('like', { postId: post.id, userId: loggedInUser.id })
setLiked(true)
}
}

useEffect(() => {
if (typeof window !== 'undefined') {
WS.listen('likes_count_updated', (data) => {
if (data.postId === post.id) {
setLikes(data.totalCount)
}
})
}
}, [])

return (
<div className="mb-16">
<div className="mb-4">
<Avatar imageUrl={user.imageUrl} name={user.name} className="mr-4" />
<strong>{user.name}</strong>
</div>
<p className="text-gray-700 my-2" dangerouslySetInnerHTML={{__html: post.text}} />
<img className="w-full object-cover max-h-96" src={post.imageUrl} alt="" />
<div className="mt-2">
<button
type="button"
className={`inline-flex items-center px-3 py-2 text-md font-medium ${liked ? 'text-red-600' : 'text-gray-700'} bg-white hover:text-red-600 focus:outline-none`}
onClick={onLike}
>
{
liked ? (
<HeartIconSolid className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
) : (
<HeartIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
)
}
{likeCount}
</button>
</div>
</div>
)
}
3 changes: 3 additions & 0 deletions examples/social-network/frontend/context/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createContext } from 'react'

export default createContext()
34 changes: 34 additions & 0 deletions examples/social-network/frontend/helpers/ws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default class WS {
static ws
static listeners = {}

static init() {
this.ws = new WebSocket('ws://localhost:3001/')

this.ws.addEventListener('open', () => {
console.log('WS client connected to server!')
})

this.ws.addEventListener('message', (event) => {
try {
const json = JSON.parse(event.data)
console.log(json)
if (this.listeners[json.type]) {
this.listeners[json.type].forEach(listener => listener(json.data))
}
} catch (e) {
console.error('Unable to decode the WebSocket message:')
console.error(e)
}
})
}

static listen(eventName, fn) {
this.listeners[eventName] = this.listeners[eventName] || []
this.listeners[eventName].push(fn)
}

static send(eventName, payload) {
this.ws.send(JSON.stringify({ type: eventName, data: payload }))
}
}
Loading