From 73b4ff97b2e7658c10435d17e5ac23abb01eed87 Mon Sep 17 00:00:00 2001 From: ireic Date: Thu, 12 Dec 2019 17:26:57 +0100 Subject: [PATCH] Implemented kanban board page with lists of issues --- api/src/controllers/authentication.ts | 7 +- api/src/controllers/projects.ts | 30 +--- api/src/controllers/users.ts | 14 ++ api/src/database/seeds/development/user.ts | 1 + api/src/database/seeds/guestUser.ts | 55 +++++--- api/src/entities/Issue.ts | 8 +- api/src/entities/Project.ts | 9 +- api/src/entities/User.ts | 12 +- api/src/errors/errorHandler.ts | 2 +- api/src/index.ts | 4 +- api/src/middleware/authentication.ts | 7 +- api/src/utils/authToken.ts | 2 +- client/src/components/App/App.jsx | 4 +- client/src/components/App/AppStyles.js | 4 +- client/src/components/App/Authenticate.jsx | 29 ++++ client/src/components/App/BaseStyles.js | 4 +- .../src/components/App/NavbarLeft/Styles.js | 66 +++------ .../src/components/App/NavbarLeft/index.jsx | 28 ++-- client/src/components/App/Routes.jsx | 14 +- client/src/components/App/Toast/Styles.js | 2 +- client/src/components/App/Toast/index.jsx | 81 +++++------ client/src/components/PageNotFound/Styles.js | 23 ---- client/src/components/PageNotFound/index.jsx | 104 -------------- .../Project/Board/Filters/Styles.js | 55 ++++++++ .../Project/Board/Filters/index.jsx | 91 +++++++++++++ .../components/Project/Board/Header/Styles.js | 26 ++++ .../components/Project/Board/Header/index.jsx | 42 ++++++ .../Project/Board/IssueModal/Styles.js | 0 .../Project/Board/IssueModal/index.jsx | 0 .../components/Project/Board/Lists/Styles.js | 80 +++++++++++ .../components/Project/Board/Lists/index.jsx | 92 +++++++++++++ client/src/components/Project/Board/index.jsx | 32 +++++ .../src/components/Project/Sidebar/Styles.js | 56 ++++++++ .../src/components/Project/Sidebar/index.jsx | 45 ++++++ client/src/components/Project/Styles.js | 7 + client/src/components/Project/index.jsx | 24 ++++ client/src/favicon.png | Bin 2223 -> 99678 bytes client/src/shared/assets/icons/jira.svg | 72 ++++------ client/src/shared/assets/icons/jira.ttf | Bin 13256 -> 6720 bytes client/src/shared/assets/icons/jira.woff | Bin 13332 -> 6796 bytes client/src/shared/assets/icons/jira.woff2 | Bin 5316 -> 0 bytes client/src/shared/components/Avatar/Styles.js | 3 +- client/src/shared/components/Avatar/index.jsx | 8 +- client/src/shared/components/Button/Styles.js | 128 +++++++++--------- client/src/shared/components/Button/index.jsx | 32 +++-- .../shared/components/ConfirmModal/index.jsx | 2 +- .../shared/components/DatePicker/Styles.js | 4 +- client/src/shared/components/Icon/index.jsx | 60 ++++---- client/src/shared/components/Input/Styles.js | 24 ++-- client/src/shared/components/Input/index.jsx | 4 +- client/src/shared/components/Logo.jsx | 8 +- client/src/shared/components/Modal/Styles.js | 2 +- .../src/shared/components/PageError/Styles.js | 44 ++++++ .../PageError/assets/background-forest.jpg | Bin 0 -> 564518 bytes .../src/shared/components/PageError/index.jsx | 21 +++ .../shared/components/PageLoader/Styles.js | 2 +- .../src/shared/components/ProjectAvatar.jsx | 120 ++++++++++++++++ .../src/shared/components/Select/Dropdown.jsx | 10 +- client/src/shared/components/Select/Styles.js | 4 +- client/src/shared/components/Select/index.jsx | 4 +- .../src/shared/components/Textarea/Styles.js | 17 +-- .../src/shared/components/Textarea/index.jsx | 2 +- client/src/shared/components/index.js | 2 + client/src/shared/constants/issues.js | 20 +++ client/src/shared/constants/keyCodes.js | 10 +- client/src/shared/hooks/api.js | 71 ++++++++++ client/src/shared/hooks/debounceValue.js | 19 +++ client/src/shared/hooks/deepCompareMemoize.js | 14 ++ client/src/shared/hooks/onEscapeKeyDown.js | 2 +- client/src/shared/utils/api.js | 54 ++++++++ client/src/shared/utils/authToken.js | 3 + client/src/shared/utils/clipboard.js | 8 ++ client/src/shared/utils/styles.js | 70 +++++----- 73 files changed, 1343 insertions(+), 561 deletions(-) create mode 100644 api/src/controllers/users.ts create mode 100644 client/src/components/App/Authenticate.jsx delete mode 100644 client/src/components/PageNotFound/Styles.js delete mode 100644 client/src/components/PageNotFound/index.jsx create mode 100644 client/src/components/Project/Board/Filters/Styles.js create mode 100644 client/src/components/Project/Board/Filters/index.jsx create mode 100644 client/src/components/Project/Board/Header/Styles.js create mode 100644 client/src/components/Project/Board/Header/index.jsx create mode 100644 client/src/components/Project/Board/IssueModal/Styles.js create mode 100644 client/src/components/Project/Board/IssueModal/index.jsx create mode 100644 client/src/components/Project/Board/Lists/Styles.js create mode 100644 client/src/components/Project/Board/Lists/index.jsx create mode 100644 client/src/components/Project/Board/index.jsx create mode 100644 client/src/components/Project/Sidebar/Styles.js create mode 100644 client/src/components/Project/Sidebar/index.jsx create mode 100644 client/src/components/Project/Styles.js create mode 100644 client/src/components/Project/index.jsx delete mode 100755 client/src/shared/assets/icons/jira.woff2 create mode 100644 client/src/shared/components/PageError/Styles.js create mode 100644 client/src/shared/components/PageError/assets/background-forest.jpg create mode 100644 client/src/shared/components/PageError/index.jsx create mode 100644 client/src/shared/components/ProjectAvatar.jsx create mode 100644 client/src/shared/constants/issues.js create mode 100644 client/src/shared/hooks/api.js create mode 100644 client/src/shared/hooks/debounceValue.js create mode 100644 client/src/shared/hooks/deepCompareMemoize.js create mode 100644 client/src/shared/utils/api.js create mode 100644 client/src/shared/utils/authToken.js create mode 100644 client/src/shared/utils/clipboard.js diff --git a/api/src/controllers/authentication.ts b/api/src/controllers/authentication.ts index 005afaf..dceb824 100644 --- a/api/src/controllers/authentication.ts +++ b/api/src/controllers/authentication.ts @@ -1,8 +1,6 @@ import express from 'express'; -import { User } from 'entities'; import { catchErrors } from 'errors'; -import { createEntity } from 'utils/typeorm'; import { signToken } from 'utils/authToken'; import seedGuestUserEntities from 'database/seeds/guestUser'; @@ -10,9 +8,8 @@ const router = express.Router(); router.post( '/authentication/guest', - catchErrors(async (req, res) => { - const user = await createEntity(User, req.body); - await seedGuestUserEntities(user); + catchErrors(async (_req, res) => { + const user = await seedGuestUserEntities(); res.respond({ authToken: signToken({ sub: user.id }), }); diff --git a/api/src/controllers/projects.ts b/api/src/controllers/projects.ts index ab95c07..d8a46e6 100644 --- a/api/src/controllers/projects.ts +++ b/api/src/controllers/projects.ts @@ -2,36 +2,20 @@ import express from 'express'; import { Project } from 'entities'; import { catchErrors } from 'errors'; -import { findEntityOrThrow, updateEntity, deleteEntity, createEntity } from 'utils/typeorm'; +import { findEntityOrThrow, updateEntity } from 'utils/typeorm'; const router = express.Router(); router.get( - '/projects', - catchErrors(async (_req, res) => { - const projects = await Project.find(); - res.respond({ projects }); - }), -); - -router.get( - '/projects/:projectId', + '/project', catchErrors(async (req, res) => { - const project = await findEntityOrThrow(Project, req.params.projectId, { + const project = await findEntityOrThrow(Project, req.currentUser.projectId, { relations: ['users', 'issues', 'issues.comments'], }); res.respond({ project }); }), ); -router.post( - '/projects', - catchErrors(async (req, res) => { - const project = await createEntity(Project, req.body); - res.respond({ project }); - }), -); - router.put( '/projects/:projectId', catchErrors(async (req, res) => { @@ -40,12 +24,4 @@ router.put( }), ); -router.delete( - '/projects/:projectId', - catchErrors(async (req, res) => { - const project = await deleteEntity(Project, req.params.projectId); - res.respond({ project }); - }), -); - export default router; diff --git a/api/src/controllers/users.ts b/api/src/controllers/users.ts new file mode 100644 index 0000000..6d5f57e --- /dev/null +++ b/api/src/controllers/users.ts @@ -0,0 +1,14 @@ +import express from 'express'; + +import { catchErrors } from 'errors'; + +const router = express.Router(); + +router.get( + '/currentUser', + catchErrors((req, res) => { + res.respond({ currentUser: req.currentUser }); + }), +); + +export default router; diff --git a/api/src/database/seeds/development/user.ts b/api/src/database/seeds/development/user.ts index 14a90eb..3f7dadd 100644 --- a/api/src/database/seeds/development/user.ts +++ b/api/src/database/seeds/development/user.ts @@ -4,6 +4,7 @@ import User from 'entities/User'; const generateUser = (data: Partial = {}): Partial => ({ name: faker.company.companyName(), + avatarUrl: faker.image.avatar(), email: faker.internet.email(), ...data, }); diff --git a/api/src/database/seeds/guestUser.ts b/api/src/database/seeds/guestUser.ts index b1a9eca..75187dd 100644 --- a/api/src/database/seeds/guestUser.ts +++ b/api/src/database/seeds/guestUser.ts @@ -1,20 +1,39 @@ +import faker from 'faker'; +import { sample } from 'lodash'; + import { Comment, Issue, Project, User } from 'entities'; import { ProjectCategory } from 'constants/projects'; import { IssueType, IssueStatus, IssuePriority } from 'constants/issues'; import { createEntity } from 'utils/typeorm'; -const seedProject = (user: User): Promise => +const seedUsers = (): Promise => { + const users = [ + createEntity(User, { + email: 'greg@jira.guest', + name: 'Greg the Egg', + avatarUrl: faker.image.avatar(), + }), + createEntity(User, { + email: 'yoda@jira.guest', + name: 'Baby Yoda', + avatarUrl: faker.image.avatar(), + }), + ]; + return Promise.all(users); +}; + +const seedProject = (users: User[]): Promise => createEntity(Project, { - name: 'Project: Hello World', + name: 'singularity 1.0', url: 'https://www.atlassian.com/software/jira', description: 'Plan, track, and manage your agile and software development projects in Jira. Customize your workflow, collaborate, and release great software.', category: ProjectCategory.SOFTWARE, - users: [user], + users, }); const seedIssues = (project: Project): Promise => { - const user = project.users[0]; + const getRandomUser = (): User => sample(project.users) as User; const issues = [ createEntity(Issue, { title: 'This is an issue of type: Task.', @@ -22,9 +41,9 @@ const seedIssues = (project: Project): Promise => { status: IssueStatus.BACKLOG, priority: IssuePriority.LOWEST, estimate: 8, - reporterId: user.id, + reporterId: getRandomUser().id, project, - users: [user], + users: [getRandomUser()], }), createEntity(Issue, { title: "Click on an issue to see what's behind it.", @@ -33,7 +52,7 @@ const seedIssues = (project: Project): Promise => { priority: IssuePriority.LOW, description: 'Nothing in particular.', estimate: 40, - reporterId: user.id, + reporterId: getRandomUser().id, project, }), createEntity(Issue, { @@ -42,9 +61,9 @@ const seedIssues = (project: Project): Promise => { status: IssueStatus.BACKLOG, priority: IssuePriority.MEDIUM, estimate: 15, - reporterId: user.id, + reporterId: getRandomUser().id, project, - users: [user], + users: [getRandomUser()], }), createEntity(Issue, { title: 'You can use markdown for issue descriptions.', @@ -54,9 +73,9 @@ const seedIssues = (project: Project): Promise => { description: "#### Colons can be used to align columns.\n\n| Tables | Are | Cool |\n| ------------- |:-------------:| -----:|\n| col 3 is | right-aligned | |\n| col 2 is | centered | |\n| zebra stripes | are neat | |\n\nThe outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.\n\nMarkdown | Less | Pretty\n--- | --- | ---\n*Still* | `renders` | **nicely**\n1 | 2 | 3", estimate: 4, - reporterId: user.id, + reporterId: getRandomUser().id, project, - users: [user], + users: [getRandomUser()], }), createEntity(Issue, { title: 'You must assign priority from lowest to highest to all issues.', @@ -64,7 +83,7 @@ const seedIssues = (project: Project): Promise => { status: IssueStatus.SELECTED, priority: IssuePriority.HIGHEST, estimate: 15, - reporterId: user.id, + reporterId: getRandomUser().id, project, }), createEntity(Issue, { @@ -73,9 +92,9 @@ const seedIssues = (project: Project): Promise => { status: IssueStatus.SELECTED, priority: IssuePriority.MEDIUM, estimate: 55, - reporterId: user.id, + reporterId: getRandomUser().id, project, - users: [user], + users: [getRandomUser()], }), createEntity(Issue, { title: 'Try leaving a comment on this issue.', @@ -83,7 +102,7 @@ const seedIssues = (project: Project): Promise => { status: IssueStatus.SELECTED, priority: IssuePriority.MEDIUM, estimate: 12, - reporterId: user.id, + reporterId: getRandomUser().id, project, }), ]; @@ -97,10 +116,12 @@ const seedComments = (issue: Issue, user: User): Promise => user, }); -const seedGuestUserEntities = async (user: User): Promise => { - const project = await seedProject(user); +const seedGuestUserEntities = async (): Promise => { + const users = await seedUsers(); + const project = await seedProject(users); const issues = await seedIssues(project); await seedComments(issues[issues.length - 1], project.users[0]); + return users[0]; }; export default seedGuestUserEntities; diff --git a/api/src/entities/Issue.ts b/api/src/entities/Issue.ts index f537f35..74d43a5 100644 --- a/api/src/entities/Issue.ts +++ b/api/src/entities/Issue.ts @@ -41,16 +41,16 @@ class Issue extends BaseEntity { @Column('varchar') priority: IssuePriority; - @Column({ type: 'text', nullable: true }) + @Column('text', { nullable: true }) description: string | null; - @Column({ type: 'integer', nullable: true }) + @Column('integer', { nullable: true }) estimate: number | null; - @Column({ type: 'integer', nullable: true }) + @Column('integer', { nullable: true }) timeSpent: number | null; - @Column({ type: 'integer', nullable: true }) + @Column('integer', { nullable: true }) timeRemaining: number | null; @CreateDateColumn({ type: 'timestamp' }) diff --git a/api/src/entities/Project.ts b/api/src/entities/Project.ts index b8c0664..110e907 100644 --- a/api/src/entities/Project.ts +++ b/api/src/entities/Project.ts @@ -6,8 +6,6 @@ import { CreateDateColumn, UpdateDateColumn, OneToMany, - ManyToMany, - JoinTable, } from 'typeorm'; import is from 'utils/validation'; @@ -32,7 +30,7 @@ class Project extends BaseEntity { @Column('varchar', { nullable: true }) url: string | null; - @Column({ type: 'text', nullable: true }) + @Column('text', { nullable: true }) description: string | null; @Column('varchar') @@ -50,11 +48,10 @@ class Project extends BaseEntity { ) issues: Issue[]; - @ManyToMany( + @OneToMany( () => User, - user => user.projects, + user => user.project, ) - @JoinTable() users: User[]; } diff --git a/api/src/entities/User.ts b/api/src/entities/User.ts index f4435e0..7ed13a1 100644 --- a/api/src/entities/User.ts +++ b/api/src/entities/User.ts @@ -7,6 +7,8 @@ import { UpdateDateColumn, OneToMany, ManyToMany, + ManyToOne, + RelationId, } from 'typeorm'; import is from 'utils/validation'; @@ -28,6 +30,9 @@ class User extends BaseEntity { @Column('varchar') email: string; + @Column('varchar', { length: 2000 }) + avatarUrl: string; + @CreateDateColumn({ type: 'timestamp' }) createdAt: Date; @@ -46,11 +51,14 @@ class User extends BaseEntity { ) issues: Issue[]; - @ManyToMany( + @ManyToOne( () => Project, project => project.users, ) - projects: Project[]; + project: Project; + + @RelationId((user: User) => user.project) + projectId: number; } export default User; diff --git a/api/src/errors/errorHandler.ts b/api/src/errors/errorHandler.ts index 9203b40..fdab456 100644 --- a/api/src/errors/errorHandler.ts +++ b/api/src/errors/errorHandler.ts @@ -17,5 +17,5 @@ export const errorHandler: ErrorRequestHandler = (error, _req, res, _next) => { data: {}, }; - res.status(errorData.status).send({ errors: [errorData] }); + res.status(errorData.status).send({ error: errorData }); }; diff --git a/api/src/index.ts b/api/src/index.ts index e4a57eb..a38bb9a 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -9,6 +9,7 @@ import { authenticateUser } from 'middleware/authentication'; import authenticationRoutes from 'controllers/authentication'; import projectsRoutes from 'controllers/projects'; import issuesRoutes from 'controllers/issues'; +import usersRoutes from 'controllers/users'; import { RouteNotFoundError } from 'errors'; import { errorHandler } from 'errors/errorHandler'; @@ -30,7 +31,7 @@ const initializeExpress = (): void => { app.use((_req, res, next) => { res.respond = (data): void => { - res.status(200).send({ data }); + res.status(200).send(data); }; next(); }); @@ -41,6 +42,7 @@ const initializeExpress = (): void => { app.use('/', projectsRoutes); app.use('/', issuesRoutes); + app.use('/', usersRoutes); app.use((req, _res, next) => next(new RouteNotFoundError(req.originalUrl))); app.use(errorHandler); diff --git a/api/src/middleware/authentication.ts b/api/src/middleware/authentication.ts index ca9d5fe..099c0f8 100644 --- a/api/src/middleware/authentication.ts +++ b/api/src/middleware/authentication.ts @@ -1,7 +1,6 @@ import { Request } from 'express'; import { verifyToken } from 'utils/authToken'; -import { findEntityOrThrow } from 'utils/typeorm'; import { catchErrors, InvalidTokenError } from 'errors'; import { User } from 'entities'; @@ -20,6 +19,10 @@ export const authenticateUser = catchErrors(async (req, _res, next) => { if (!userId) { throw new InvalidTokenError('Authentication token is invalid.'); } - req.currentUser = await findEntityOrThrow(User, userId); + const user = await User.findOne(userId); + if (!user) { + throw new InvalidTokenError('Authentication token is invalid: User not found.'); + } + req.currentUser = user; next(); }); diff --git a/api/src/utils/authToken.ts b/api/src/utils/authToken.ts index b8e1cc8..d55f895 100644 --- a/api/src/utils/authToken.ts +++ b/api/src/utils/authToken.ts @@ -5,7 +5,7 @@ import { InvalidTokenError } from 'errors'; export const signToken = (payload: object, options?: SignOptions): string => jwt.sign(payload, process.env.JWT_SECRET, { - expiresIn: '7 days', + expiresIn: '180 days', ...options, }); diff --git a/client/src/components/App/App.jsx b/client/src/components/App/App.jsx index 5d67ec9..51cea4d 100644 --- a/client/src/components/App/App.jsx +++ b/client/src/components/App/App.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import Toast from './Toast'; -import Routes from './Routes'; import NormalizeStyles from './NormalizeStyles'; import FontStyles from './FontStyles'; import BaseStyles from './BaseStyles'; +import Toast from './Toast'; +import Routes from './Routes'; const App = () => ( <> diff --git a/client/src/components/App/AppStyles.js b/client/src/components/App/AppStyles.js index 67814ef..4bdbdfd 100644 --- a/client/src/components/App/AppStyles.js +++ b/client/src/components/App/AppStyles.js @@ -1,6 +1,8 @@ import styled from 'styled-components'; +import { sizes } from 'shared/utils/styles'; export const Main = styled.main` + position: relative; width: 100%; - padding-left: 75px; + padding-left: ${sizes.appNavBarLeftWidth}px; `; diff --git a/client/src/components/App/Authenticate.jsx b/client/src/components/App/Authenticate.jsx new file mode 100644 index 0000000..5299c5d --- /dev/null +++ b/client/src/components/App/Authenticate.jsx @@ -0,0 +1,29 @@ +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + +import useApi from 'shared/hooks/api'; +import { getStoredAuthToken, storeAuthToken } from 'shared/utils/authToken'; +import { PageLoader } from 'shared/components'; + +const Authenticate = () => { + const [{ data }, createGuestAccount] = useApi.post('/authentication/guest'); + + const history = useHistory(); + + useEffect(() => { + if (!getStoredAuthToken()) { + createGuestAccount(); + } + }, [createGuestAccount]); + + useEffect(() => { + if (data && data.authToken) { + storeAuthToken(data.authToken); + history.push('/'); + } + }, [data, history]); + + return ; +}; + +export default Authenticate; diff --git a/client/src/components/App/BaseStyles.js b/client/src/components/App/BaseStyles.js index 6074be1..07423b1 100644 --- a/client/src/components/App/BaseStyles.js +++ b/client/src/components/App/BaseStyles.js @@ -85,7 +85,7 @@ export default createGlobalStyle` } p { - line-height: 1.6; + line-height: 1.4285; a { ${mixin.link()} } @@ -104,5 +104,5 @@ export default createGlobalStyle` touch-action: manipulation; } - ${mixin.placeholderColor(color.textLightBlue)} + ${mixin.placeholderColor(color.textLight)} `; diff --git a/client/src/components/App/NavbarLeft/Styles.js b/client/src/components/App/NavbarLeft/Styles.js index 1530c47..8f6b202 100644 --- a/client/src/components/App/NavbarLeft/Styles.js +++ b/client/src/components/App/NavbarLeft/Styles.js @@ -6,17 +6,17 @@ import Logo from 'shared/components/Logo'; export const NavLeft = styled.aside` z-index: ${zIndexValues.navLeft}; - position: absolute; + position: fixed; top: 0; left: 0; overflow-x: hidden; - height: 100%; + height: 100vh; width: ${sizes.appNavBarLeftWidth}px; - background: ${color.primary}; + background: ${color.backgroundDarkPrimary}; transition: all 0.1s; ${mixin.hardwareAccelerate} &:hover { - width: 260px; + width: 180px; box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.6); } `; @@ -25,71 +25,43 @@ export const LogoLink = styled(NavLink)` display: block; position: relative; left: 0; - margin: 40px 0 40px; + margin: 20px 0 10px; transition: left 0.1s; - &:before { - display: inline-block; - content: ''; - position: absolute; - top: 0; - right: 0; - height: 50px; - width: 20px; - background: ${color.primary}; - } - ${NavLeft}:hover & { - left: 3px; - &:before { - display: none; - } - } `; export const StyledLogo = styled(Logo)` display: inline-block; - margin-left: 13px; + margin-left: 8px; padding: 10px; ${mixin.clickable} `; -export const IconLink = styled(NavLink)` - display: block; +export const Bottom = styled.div` + position: absolute; + bottom: 20px; + left: 0; + width: 100%; +`; + +export const Item = styled.div` position: relative; width: 100%; - height: 60px; - line-height: 60px; + height: 42px; + line-height: 42px; padding-left: 67px; - color: rgba(255, 255, 255, 0.75); + color: #deebff; transition: color 0.1s; ${mixin.clickable} - &:before { - content: ''; - display: none; - position: absolute; - top: 5px; - right: 0; - height: 50px; - width: 5px; - background: #fff; - border-radius: 6px 0 0 6px; - } - &.active, - &:hover { - color: #fff; - } - &.active:before { - display: inline-block; - } &:hover { background: rgba(255, 255, 255, 0.1); } i { position: absolute; - left: 27px; + left: 18px; } `; -export const LinkText = styled.div` +export const ItemText = styled.div` position: relative; right: 12px; visibility: hidden; diff --git a/client/src/components/App/NavbarLeft/index.jsx b/client/src/components/App/NavbarLeft/index.jsx index da3ef5c..013ab9e 100644 --- a/client/src/components/App/NavbarLeft/index.jsx +++ b/client/src/components/App/NavbarLeft/index.jsx @@ -1,25 +1,27 @@ import React from 'react'; import { Icon } from 'shared/components'; -import { NavLeft, LogoLink, StyledLogo, IconLink, LinkText } from './Styles'; +import { NavLeft, LogoLink, StyledLogo, Bottom, Item, ItemText } from './Styles'; const NavbarLeft = () => ( - - - Projects - - - - Subcontractors - - - - Bids - + + + Search + + + + Create + + + + + Help + + ); diff --git a/client/src/components/App/Routes.jsx b/client/src/components/App/Routes.jsx index 011a60a..efffb48 100644 --- a/client/src/components/App/Routes.jsx +++ b/client/src/components/App/Routes.jsx @@ -1,10 +1,13 @@ import React from 'react'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; import history from 'browserHistory'; -import { Router, Switch, Route } from 'react-router-dom'; -import PageNotFound from 'components/PageNotFound'; +import PageError from 'shared/components/PageError'; + +import Project from 'components/Project'; + import NavbarLeft from './NavbarLeft'; - +import Authenticate from './Authenticate'; import { Main } from './AppStyles'; const Routes = () => ( @@ -12,7 +15,10 @@ const Routes = () => (
- + + + +
diff --git a/client/src/components/App/Toast/Styles.js b/client/src/components/App/Toast/Styles.js index 21a15b8..dd9b966 100644 --- a/client/src/components/App/Toast/Styles.js +++ b/client/src/components/App/Toast/Styles.js @@ -14,7 +14,7 @@ export const StyledToast = styled.div` margin-bottom: 5px; width: 300px; padding: 15px 20px; - border-radius: 4px; + border-radius: 3px; color: #fff; background: ${props => color[props.type]}; cursor: pointer; diff --git a/client/src/components/App/Toast/index.jsx b/client/src/components/App/Toast/index.jsx index 867fe82..e61b161 100644 --- a/client/src/components/App/Toast/index.jsx +++ b/client/src/components/App/Toast/index.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect } from 'react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import pubsub from 'sweet-pubsub'; import { uniqueId } from 'lodash'; @@ -6,57 +6,44 @@ import { uniqueId } from 'lodash'; import { Icon } from 'shared/components'; import { Container, StyledToast, Title, Message } from './Styles'; -class Toast extends Component { - state = { toasts: [] }; +const Toast = () => { + const [toasts, setToasts] = useState([]); - componentDidMount() { - pubsub.on('toast', this.addToast); - } + useEffect(() => { + const addToast = ({ type = 'success', title, message, duration = 5 }) => { + const id = uniqueId(); - componentWillUnmount() { - pubsub.off('toast', this.addToast); - } + setToasts(currentToasts => [...currentToasts, { id, type, title, message }]); - addToast = ({ type = 'success', title, message, duration = 5 }) => { - const id = uniqueId('toast-'); + if (duration) { + setTimeout(() => removeToast(id), duration * 1000); + } + }; + pubsub.on('toast', addToast); + return () => { + pubsub.off('toast', addToast); + }; + }, []); - this.setState(state => ({ - toasts: [...state.toasts, { id, type, title, message }], - })); - - if (duration) { - setTimeout(() => this.removeToast(id), duration * 1000); - } + const removeToast = id => { + setToasts(currentToasts => currentToasts.filter(toast => toast.id !== id)); }; - removeToast = id => { - this.setState(state => ({ - toasts: state.toasts.filter(toast => toast.id !== id), - })); - }; - - render() { - const { toasts } = this.state; - return ( - - - {toasts.map(toast => ( - - this.removeToast(toast.id)} - > - - {toast.title && {toast.title}} - {toast.message && {toast.message}} - - - ))} - - - ); - } -} + return ( + + + {toasts.map(toast => ( + + removeToast(toast.id)}> + + {toast.title && {toast.title}} + {toast.message && {toast.message}} + + + ))} + + + ); +}; export default Toast; diff --git a/client/src/components/PageNotFound/Styles.js b/client/src/components/PageNotFound/Styles.js deleted file mode 100644 index 1d72f9b..0000000 --- a/client/src/components/PageNotFound/Styles.js +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; - -import { color, font } from 'shared/utils/styles'; - -export const Wrapper = styled.div` - margin: 50px auto 0; - max-width: 500px; - padding: 50px 50px 60px; - text-align: center; - border-radius: 4px; - background: ${color.backgroundLight}; -`; - -export const Heading = styled.h1` - ${font.size(60)} -`; - -export const Message = styled.p` - color: ${color.textDark}; - padding: 10px 0 30px; - line-height: 1.35; - ${font.size(20)} -`; diff --git a/client/src/components/PageNotFound/index.jsx b/client/src/components/PageNotFound/index.jsx deleted file mode 100644 index 266c7ba..0000000 --- a/client/src/components/PageNotFound/index.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; - -import { - Button, - ConfirmModal, - Avatar, - DatePicker, - Input, - Modal, - Select, - Textarea, - Spinner, -} from 'shared/components'; -import { Wrapper, Heading, Message } from './Styles'; - -const PageNotFound = () => { - const [dateValue, setDateValue] = useState(null); - const [inputValue, setInputValue] = useState(''); - const [isModalOpen, setModalOpen] = useState(false); - const [selectValue, setSelectValue] = useState(''); - const [selectOptions, setSelectOptions] = useState([ - { label: 'one', value: '1' }, - { label: 'two', value: '2' }, - { label: 'three', value: '3' }, - { label: 'four', value: '4' }, - { label: 'five', value: '5' }, - { label: 'six', value: '6' }, - { label: 'seven', value: '7' }, - { label: 'eight', value: '8' }, - { label: 'nine', value: '9' }, - { label: 'ten', value: '10' }, - ]); - console.log('ha'); - return ( - - 404 - We cannot find the page you are looking for. -
- - } - confirmInput="YAY" - onConfirm={modal => { - console.log('CONFIRMED!'); - modal.close(); - }} - /> - - setInputValue(value)} - /> -