Skip to content

Commit

Permalink
Finish up design (#134)
Browse files Browse the repository at this point in the history
* wip

* Fix APIs

* Finish design

* remove console.log
  • Loading branch information
hoangvvo authored Sep 24, 2021
1 parent 48dc4b3 commit f2ecbb1
Show file tree
Hide file tree
Showing 74 changed files with 1,685 additions and 335 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": ["eslint:recommended", "next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
Expand Down
11 changes: 6 additions & 5 deletions api-lib/auth/passport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { findUserByEmail, UNSAFE_findUserForAuth } from '@/api-lib/db';
import bcrypt from 'bcryptjs';
import {
findUserWithEmailAndPassword,
UNSAFE_findUserForAuth,
} from '@/api-lib/db';
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';

Expand All @@ -19,9 +21,8 @@ passport.use(
new LocalStrategy(
{ usernameField: 'email', passReqToCallback: true },
async (req, email, password, done) => {
const user = await findUserByEmail(req.db, email);
if (user && (await bcrypt.compare(password, user.password)))
done(null, user);
const user = await findUserWithEmailAndPassword(req.db, email, password);
if (user) done(null, user);
else done(null, false, { message: 'Email or password is incorrect' });
}
)
Expand Down
8 changes: 2 additions & 6 deletions api-lib/db/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ export async function findComments(db, postId, from, limit = 10) {
{
$match: {
postId: new ObjectId(postId),
...(from && {
createdAt: {
$lte: from,
},
}),
...(from && { createdAt: { $lte: from } }),
},
},
{ $limit: limit },
{ $sort: { _id: -1 } },
{ $limit: limit },
{
$lookup: {
from: 'users',
Expand Down
41 changes: 20 additions & 21 deletions api-lib/db/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,39 @@ import { ObjectId } from 'mongodb';
import { dbProjectionUsers } from './user';

export async function findPostById(db, id) {
const posts = await db.collection('posts').aggregate([
{ $match: { _id: new ObjectId(id) } },
{ $limit: 1 },
{
$lookup: {
from: 'users',
localField: 'creatorId',
foreignField: '_id',
as: 'creator',
const posts = await db
.collection('posts')
.aggregate([
{ $match: { _id: new ObjectId(id) } },
{ $limit: 1 },
{
$lookup: {
from: 'users',
localField: 'creatorId',
foreignField: '_id',
as: 'creator',
},
},
},
{ $unwind: '$creator' },
{ $project: dbProjectionUsers('creator.') },
]);
{ $unwind: '$creator' },
{ $project: dbProjectionUsers('creator.') },
])
.toArray();
if (!posts[0]) return null;
return posts[0];
}

export async function findPosts(db, from, by, limit = 10) {
export async function findPosts(db, before, by, limit = 10) {
return db
.collection('posts')
.aggregate([
{
$match: {
...(from && {
createdAt: {
$lte: from,
},
}),
...(by && { creatorId: by }),
...(by && { creatorId: new ObjectId(by) }),
...(before && { createdAt: { $lte: before } }),
},
},
{ $limit: limit },
{ $sort: { _id: -1 } },
{ $limit: limit },
{
$lookup: {
from: 'users',
Expand Down
48 changes: 39 additions & 9 deletions api-lib/db/user.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import bcrypt from 'bcryptjs';
import { ObjectId } from 'mongodb';
import normalizeEmail from 'validator/lib/normalizeEmail';

export async function UNSAFE_findUserForAuth(db, userId, all) {
export async function findUserWithEmailAndPassword(db, email, password) {
email = normalizeEmail(email);
const user = await db.collection('users').findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
return { ...user, password: undefined }; // filtered out password
}
return null;
}

export async function UNSAFE_findUserForAuth(db, userId) {
return db
.collection('users')
.findOne(
{ _id: new ObjectId(userId) },
{
projection: !all ? { password: 0 } : undefined,
}
)
.findOne({ _id: new ObjectId(userId) }, { projection: { password: 0 } })
.then((user) => user || null);
}

Expand Down Expand Up @@ -41,14 +46,14 @@ export async function updateUserById(db, id, data) {
.findOneAndUpdate(
{ _id: new ObjectId(id) },
{ $set: data },
{ returnDocument: 'after', projection: dbProjectionUsers() }
{ returnDocument: 'after', projection: { password: 0 } }
)
.then(({ value }) => value);
}

export async function insertUser(
db,
{ email, password, bio = '', name, profilePicture, username }
{ email, originalPassword, bio = '', name, profilePicture, username }
) {
const user = {
emailVerified: false,
Expand All @@ -58,13 +63,38 @@ export async function insertUser(
username,
bio,
};
const password = await bcrypt.hash(originalPassword, 10);
const { insertedId } = await db
.collection('users')
.insertOne({ ...user, password });
user._id = insertedId;
return user;
}

export async function updateUserPasswordByOldPassword(
db,
id,
oldPassword,
newPassword
) {
const user = await db.collection('users').findOne(new ObjectId(id));
if (!user) return false;
const matched = await bcrypt.compare(oldPassword, user.password);
if (!matched) return false;
const password = await bcrypt.hash(newPassword, 10);
await db
.collection('users')
.updateOne({ _id: new ObjectId(id) }, { $set: { password } });
return true;
}

export async function UNSAFE_updateUserPassword(db, id, newPassword) {
const password = await bcrypt.hash(newPassword, 10);
await db
.collection('users')
.updateOne({ _id: new ObjectId(id) }, { $set: { password } });
}

export function dbProjectionUsers(prefix = '') {
return {
[`${prefix}password`]: 0,
Expand Down
2 changes: 1 addition & 1 deletion api-lib/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function sendMail({ from, to, subject, html }) {
});
} catch (e) {
console.error(e);
throw new Error(`Could not send email.`);
throw new Error(`Could not send email: ${e.message}`);
}
}

Expand Down
10 changes: 10 additions & 0 deletions assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
--error: #e00;
--error-dark: #c50000;
--secondary: #666;
--header-background: hsla(0, 0%, 100%, 0.8);
--shadow-smallest: 0px 2px 4px rgba(0, 0, 0, 0.1);
--shadow-medium: 0 8px 30px rgba(0, 0, 0, 0.12);
--shadow-large: 0 30px 60px rgba(0, 0, 0, 0.12);

--link: var(--success);
--nav-height: 48px;
}
Expand All @@ -33,6 +38,11 @@
--accents-3: #444;
--accents-2: #333;
--accents-1: #111;
--secondary: var(--accents-5);
--header-background: rgba(0, 0, 0, 0.5);
--shadow-smallest: 0 0 0 1px var(--accents-2);
--shadow-medium: 0 0 0 1px var(--accents-2);
--shadow-large: 0 0 0 1px var(--accents-2);
}

body {
Expand Down
8 changes: 1 addition & 7 deletions components/Avatar/Avatar.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import styles from './Avatar.module.css';

const Avatar = ({ size, username, url }) => {
if (!url)
return (
<span className={styles.avatar} style={{ width: size, height: size }}>
{username[0]}
</span>
);
return (
<img
className={styles.avatar}
src={url}
src={url || '/images/default_user.jpg'}
alt={username}
width={size}
height={size}
Expand Down
14 changes: 13 additions & 1 deletion components/Button/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { LoadingDots } from '@/components/LoadingDots';
import clsx from 'clsx';
import { forwardRef } from 'react';
import styles from './Button.module.css';

export const Button = forwardRef(function Button(
{ children, type, className, onClick, size, variant = 'invert' },
{
children,
type,
className,
onClick,
size,
variant = 'invert',
loading,
disabled,
},
ref
) {
return (
Expand All @@ -17,7 +27,9 @@ export const Button = forwardRef(function Button(
)}
ref={ref}
onClick={onClick}
disabled={loading || disabled}
>
{loading && <LoadingDots className={styles.loading} />}
<span>{children}</span>
</button>
);
Expand Down
50 changes: 37 additions & 13 deletions components/Button/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@
text-decoration: none;
}

.button[disabled] {
background: var(--accents-1);
color: var(--accents-3);
border-color: var(--accents-2);
cursor: not-allowed;
}

.loading {
margin-right: 8px;
}

.loading span {
background-color: var(--accents-3);
}

/** Type **/

.secondary {
Expand All @@ -26,12 +41,6 @@
--themed-border: var(--accents-2);
}

.secondary.button:hover {
background: var(--background);
color: var(--foreground);
border-color: var(--foreground);
}

.success {
--themed-fg: #fff;
--themed-bg: var(--success);
Expand All @@ -46,41 +55,56 @@

/** Size **/

.small {
height: 32px;
font-size: 0.875rem;
}

.large {
height: 48px;
font-size: 1rem;
}

/** Variants **/

.invert:hover {
.invert:not([disabled]):hover {
background: transparent;
color: var(--themed-bg, var(--foreground));
}

.invert:active {
.invert:not([disabled]):active {
background: var(--accents-2);
}

.invert:not([disabled]).secondary:hover {
color: var(--foreground);
border-color: var(--foreground);
}

.ghost {
background: none;
border-color: transparent;
color: var(--themed-bg);
--lighten-color: hsla(0, 0%, 100%, 0.8);
}

.ghost {
background-image: linear-gradient(
to right,
var(--lighten-color),
var(--lighten-color)
);
}

.ghost:hover {
[data-theme='dark'] .ghost {
--lighten-color: rgba(0, 0, 0, 0.75);
}

.ghost:not([disabled]):hover {
background-color: var(--themed-bg, var(--accents-4));
}

.ghost:focus {
.ghost:not([disabled]):focus {
--lighten-color: hsla(0, 0%, 100%, 0.7);
}

[data-theme='dark'] .ghost:not([disabled]):focus {
--lighten-color: rgba(0, 0, 0, 0.7);
}
Loading

0 comments on commit f2ecbb1

Please sign in to comment.