Skip to content

Commit

Permalink
Feat: other user profile (#63)
Browse files Browse the repository at this point in the history
* Fix some issues

* Add note at sign up

* rename useUser to useCurrentUser

* Move sendVerificationEmail

* eslint

* Move settings page

* add fields

* Change profile link in layout

* add _id field

* Add user page

* add an ugly default profile picture

* Add babel-eslint

* Move from profile to user/[userId]

* update roadmap

* Fix
  • Loading branch information
hoangvvo authored May 5, 2020
1 parent 4467e6e commit 10d7fcd
Show file tree
Hide file tree
Showing 15 changed files with 116 additions and 74 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ MONGODB_URI=mongodb+srv://testuser:[email protected]/next
CLOUDINARY_URL=cloudinary://741947492169653:vkyuRmZ3EbSULnkfXJdtSqwhURw@dbplcha6k
DB_NAME=nextjsmongodbapp
SENDGRID_API_KEY=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
EMAIL_FROM=averyniceguy@nextjs-mongodb-app.hoangvvo.now.sh
WEB_URI=http://localhost:3000
EMAIL_FROM=[email protected]
WEB_URI=https://nextjs-mongodb.now.sh
5 changes: 4 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"extends": "airbnb",
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true
Expand Down Expand Up @@ -31,6 +32,8 @@
],
"jsx-a11y/click-events-have-key-events": "off",
"react/prop-types": "off",
"react/jsx-props-no-spreading": "off"
"react/jsx-props-no-spreading": "off",
"import/prefer-default-export": "off",
"no-param-reassign": "off"
}
}
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ A full-fledged app made with [**Next.js**](https://github.com/zeit/next.js/) and

</div>

<h3 align="center">:eyes: Users and social</h3>

<div align="center">

- [x] Other user profile
- [ ] Posting
- [ ] PM?

</div>

<div align="center">

<sup>Have any features that interest you, [make an issue](https://github.com/hoangvvo/nextjs-mongodb-app/issues). Would like to work on a feature, [make a PR](https://github.com/hoangvvo/nextjs-mongodb-app/pulls).<sup>
Expand Down Expand Up @@ -101,8 +111,6 @@ Required environmental variables in this project include:

Start the development server by running `yarn dev` or `npm run dev`. The project supports using `.env`. Getting started by create a `.env` file with the above variables.

**Styles (CSS):** This project does not contain any stylesheets, and no component has classes. To remove the style, simply remove all `<style jsx>` and `<style jsx global>` tags.

#### `.env`

I include my own MongoDB, Cloudinary, SendGrid environment variables in [.env.example](.env.example) for experimentation purposes. Please replace them with your owns and refrain from sabotaging them. You can try them in development by renaming it into `.env`.
Expand Down
12 changes: 6 additions & 6 deletions components/layout.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import Head from 'next/head';
import Link from 'next/link';
import { useUser } from '../lib/hooks';
import { useCurrentUser } from '../lib/hooks';

export default ({ children }) => {
const [user, { mutate }] = useUser();
const [user, { mutate }] = useCurrentUser();
const handleLogout = async () => {
await fetch('/api/auth', {
method: 'DELETE',
Expand Down Expand Up @@ -161,7 +161,7 @@ export default ({ children }) => {
</>
) : (
<>
<Link href="/profile">
<Link href="/user/[userId]" as={`/user/${user._id}`}>
<a>Profile</a>
</Link>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
Expand Down Expand Up @@ -189,14 +189,14 @@ export default ({ children }) => {
</span>
, and a keyboard by
{' '}
<a href="https://www.hoangvvo.com/">Hoang Vo</a>
.
<a href="https://hoangvvo.com/">Hoang Vo</a>
.
</p>
<p>
Source code is on
{' '}
<a href="https://github.com/hoangvvo/nextjs-mongodb-app">Github</a>
.
.
</p>
</footer>
</>
Expand Down
5 changes: 2 additions & 3 deletions lib/api-helpers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* eslint-disable import/prefer-default-export */
// take only needed user fields to avoid sensitive ones (such as password)
export function extractUser(req) {
if (!req.user) return null;
const {
name, email, bio, profilePicture, emailVerified,
_id, name, email, bio, profilePicture, emailVerified,
} = req.user;
return {
name, email, bio, profilePicture, emailVerified,
_id, name, email, bio, profilePicture, emailVerified,
};
}
20 changes: 20 additions & 0 deletions lib/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ObjectId } from 'mongodb';

export async function getUser(req, userId) {
const user = await req.db.collection('users').findOne({
_id: ObjectId(userId),
});
if (!user) return null;
const {
_id, name, email, bio, profilePicture, emailVerified,
} = user;
const isAuth = _id.toString() === req.user?._id.toString();
return {
_id: _id.toString(),
name,
email: isAuth ? email : null,
bio,
profilePicture: profilePicture || "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E",
emailVerified: isAuth ? emailVerified : null,
};
}
2 changes: 1 addition & 1 deletion lib/hooks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((r) => r.json());

export function useUser() {
export function useCurrentUser() {
const { data, mutate } = useSWR('/api/user', fetcher);
const user = data && data.user;
return [user, { mutate }];
Expand Down
2 changes: 1 addition & 1 deletion now.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"CLOUDINARY_URL": "cloudinary://741947492169653:vkyuRmZ3EbSULnkfXJdtSqwhURw@dbplcha6k",
"DB_NAME": "nextjsmongodbapp",
"SENDGRID_API_KEY": "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",
"EMAIL_FROM": "averyniceguy@nextjs-mongodb-app.hoangvvo.now.sh",
"EMAIL_FROM": "[email protected]",
"WEB_URI": "https://nextjs-mongodb.now.sh",
"USING_SECRETS": "Consider using Zeit Now Secrets instead."
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"type": "git",
"url": "git+https://github.com/hoangvvo/nextjs-mongodb-app.git"
},
"author": "Hoang Vo (https://www.hoangvvo.com)",
"author": "Hoang Vo (https://hoangvvo.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/hoangvvo/nextjs-mongodb-app/issues"
Expand All @@ -38,6 +38,7 @@
"validator": "^13.0.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-plugin-import": "^2.20.1",
Expand Down
6 changes: 4 additions & 2 deletions pages/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ handler.post(async (req, res) => {
const hashedPassword = await bcrypt.hash(password, 10);
const user = await req.db
.collection('users')
.insertOne({ email, password: hashedPassword, name })
.insertOne({
email, password: hashedPassword, name, emailVerified: false, bio: '', profilePicture: null,
})
.then(({ ops }) => ops[0]);
req.logIn(user, (err) => {
if (err) throw err;
res.status(201).json({
user: extractUser(req.user),
user: extractUser(req),
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions pages/index.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { useUser } from '../lib/hooks';
import { useCurrentUser } from '../lib/hooks';

const IndexPage = () => {
const [user] = useUser();
const [user] = useCurrentUser();

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions pages/login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useUser } from '../lib/hooks';
import { useCurrentUser } from '../lib/hooks';

const LoginPage = () => {
const router = useRouter();
const [errorMsg, setErrorMsg] = useState('');
const [user, { mutate }] = useUser();
const [user, { mutate }] = useCurrentUser();
useEffect(() => {
// redirect to home if user is authenticated
if (user) router.push('/');
Expand Down
46 changes: 30 additions & 16 deletions pages/profile/settings.jsx → pages/settings.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import Head from 'next/head';
import { useUser } from '../../lib/hooks';
import { useCurrentUser } from '../lib/hooks';

const ProfileSection = () => {
const [user, { mutate }] = useUser();
const [user, { mutate }] = useCurrentUser();
const [isUpdating, setIsUpdating] = useState(false);
const [name, setName] = useState(user.name);
const [bio, setBio] = useState(user.bio);
const profilePictureRef = React.createRef();
const nameRef = useRef();
const bioRef = useRef();
const profilePictureRef = useRef();
const [msg, setMsg] = useState({ message: '', isError: false });

useEffect(() => {
setName(user.name);
setBio(user.bio);
nameRef.current.value = user.name;
bioRef.current.value = user.bio;
}, [user]);

const handleSubmit = async (event) => {
Expand All @@ -21,8 +21,8 @@ const ProfileSection = () => {
setIsUpdating(true);
const formData = new FormData();
if (profilePictureRef.current.files[0]) { formData.append('profilePicture', profilePictureRef.current.files[0]); }
formData.append('name', name);
formData.append('bio', bio);
formData.append('name', nameRef.current.value);
formData.append('bio', bioRef.current.value);
const res = await fetch('/api/user', {
method: 'PATCH',
body: formData,
Expand Down Expand Up @@ -63,6 +63,12 @@ const ProfileSection = () => {
}
};

async function sendVerificationEmail() {
await fetch('/api/user/email/verify', {
method: 'POST',
});
}

return (
<>
<Head>
Expand All @@ -72,6 +78,16 @@ const ProfileSection = () => {
<h2>Edit Profile</h2>
{msg.message ? <p style={{ color: msg.isError ? 'red' : '#0070f3', textAlign: 'center' }}>{msg.message}</p> : null}
<form onSubmit={handleSubmit}>
{!user.emailVerified ? (
<p>
Your email has not been verify.
{' '}
{/* eslint-disable-next-line */}
<a role="button" onClick={sendVerificationEmail}>
Send verification email
</a>
</p>
) : null}
<label htmlFor="name">
Name
<input
Expand All @@ -80,8 +96,7 @@ const ProfileSection = () => {
name="name"
type="text"
placeholder="Your name"
value={name}
onChange={(e) => setName(e.target.value)}
ref={nameRef}
/>
</label>
<label htmlFor="bio">
Expand All @@ -91,8 +106,7 @@ const ProfileSection = () => {
name="bio"
type="text"
placeholder="Bio"
value={bio}
onChange={(e) => setBio(e.target.value)}
ref={bioRef}
/>
</label>
<label htmlFor="avatar">
Expand Down Expand Up @@ -134,7 +148,7 @@ const ProfileSection = () => {
};

const SettingPage = () => {
const [user] = useUser();
const [user] = useCurrentUser();

if (!user) {
return (
Expand All @@ -146,7 +160,7 @@ const SettingPage = () => {
return (
<>
<h1>Settings</h1>
<ProfileSection user={user} />
<ProfileSection />
</>
);
};
Expand Down
8 changes: 6 additions & 2 deletions pages/signup.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import Router from 'next/router';
import { useUser } from '../lib/hooks';
import { useCurrentUser } from '../lib/hooks';

const SignupPage = () => {
const [user, { mutate }] = useUser();
const [user, { mutate }] = useCurrentUser();
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
// redirect to home if user is authenticated
Expand Down Expand Up @@ -66,6 +66,10 @@ const SignupPage = () => {
</label>
<button type="submit">Sign up</button>
</form>
<p style={{ color: '#777', textAlign: 'center' }}>
Note: The database is public. For your privacy,
please avoid using your personal, work email.
</p>
</div>
</>
);
Expand Down
Loading

1 comment on commit 10d7fcd

@vercel
Copy link

@vercel vercel bot commented on 10d7fcd May 5, 2020

Choose a reason for hiding this comment

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

Please sign in to comment.