diff --git a/cypress/integration/pages/home/index.spec.ts b/cypress/integration/pages/home/index.spec.ts new file mode 100644 index 0000000..e442870 --- /dev/null +++ b/cypress/integration/pages/home/index.spec.ts @@ -0,0 +1,13 @@ +describe('Dashboard layout', () => { + beforeEach(() => { + cy.visit('/home'); + }); + + it('should find the header', () => { + cy.get('.header').contains('h2', 'Crit'); + }); + + it('should find the side navigation bar', () => { + cy.get('.navigation-bar-aside').contains('span', 'Home'); + }); +}); diff --git a/next.config.js b/next.config.js index 3cb4662..a02e862 100644 --- a/next.config.js +++ b/next.config.js @@ -3,7 +3,11 @@ const nextConfig = { reactStrictMode: true, env: { - BACKEND_API: 'http://localhost:8000' + BACKEND_API: 'http://localhost:8000', + }, + + images: { + domains: ['picsum.photos'], }, async redirects() { @@ -12,8 +16,8 @@ const nextConfig = { source: '/', destination: '/home', permanent: true, - } - ] + }, + ]; }, webpack(config) { @@ -21,10 +25,10 @@ const nextConfig = { test: /\.svg$/i, issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'], - }) + }); - return config + return config; }, -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 464db7d..01e2ca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@headlessui/react": "^1.6.4", "@heroicons/react": "^1.0.6", "@hookform/resolvers": "^2.8.8", "@reduxjs/toolkit": "^1.8.1", @@ -2027,6 +2028,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@headlessui/react": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.4.tgz", + "integrity": "sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, "node_modules/@heroicons/react": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz", @@ -12140,6 +12153,12 @@ "strip-json-comments": "^3.1.1" } }, + "@headlessui/react": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.4.tgz", + "integrity": "sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA==", + "requires": {} + }, "@heroicons/react": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz", diff --git a/package.json b/package.json index ea65493..b855b3b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "jest" }, "dependencies": { + "@headlessui/react": "^1.6.4", "@heroicons/react": "^1.0.6", "@hookform/resolvers": "^2.8.8", "@reduxjs/toolkit": "^1.8.1", diff --git a/pages/home/index.tsx b/pages/home/index.tsx index 2672e77..21fcf7b 100644 --- a/pages/home/index.tsx +++ b/pages/home/index.tsx @@ -1,9 +1,9 @@ -import Layout from '../../src/layouts/base'; +import Layout from '../../src/layouts/dashboard'; import type { ReactElement } from 'react'; const index = () => { - return
index
; + return
; }; index.getLayout = function getLayout(page: ReactElement) { diff --git a/public/icons/logoff.svg b/public/icons/logoff.svg new file mode 100644 index 0000000..5fc1716 --- /dev/null +++ b/public/icons/logoff.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/__tests__/helpers/setPageTitle.test.ts b/src/__tests__/helpers/setPageTitle.test.ts new file mode 100644 index 0000000..91fb193 --- /dev/null +++ b/src/__tests__/helpers/setPageTitle.test.ts @@ -0,0 +1,13 @@ +import React, { useEffect } from 'react'; +import { PageTitle } from '../../helpers/setPageTitle'; + +jest.mock('next/router', () => require('next-router-mock')); + +describe('Setting page title', () => { + it('should return the page title', () => { + const useEffect = jest.spyOn(React, 'useEffect'); + useEffect.mockImplementation((result) => result()); + + expect(PageTitle({ pathname: '/path' })).toEqual('Path'); + }); +}); diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..f986810 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,42 @@ +import Link from 'next/link'; +import { useState } from 'react'; +import HeaderLobby from './HeaderLobby'; +import HeaderUtilities from './HeaderUtilities'; +import CritSVG from '../../public/images/critLogo.svg'; + +const Header = () => { + const [isLoggedIn, setIsLoggedIn] = useState(true); + + return ( +
+
+ +
+ +

Crit

+
+ + {/* Todo :- Add API point to randomize messages */} +
+

Your pretty awesome

+
+
+
+ {isLoggedIn ? ( + <> + + + + ) : ( + + + + )} +
+
+ ); +}; + +export default Header; diff --git a/src/components/HeaderLobby.tsx b/src/components/HeaderLobby.tsx new file mode 100644 index 0000000..366990e --- /dev/null +++ b/src/components/HeaderLobby.tsx @@ -0,0 +1,54 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +const HeaderLobby = () => { + const userList = [ + { + image: 'https://picsum.photos/100', + username: 'username', + }, + { + image: 'https://picsum.photos/100', + username: 'username', + }, + { + image: 'https://picsum.photos/100', + username: 'username', + }, + { + image: 'https://picsum.photos/100', + username: 'username', + }, + { + image: 'https://picsum.photos/100', + username: 'username', + }, + ]; + + return ( +
+ +
+ ); +}; + +export default HeaderLobby; diff --git a/src/components/HeaderUtilities.tsx b/src/components/HeaderUtilities.tsx new file mode 100644 index 0000000..833cdd2 --- /dev/null +++ b/src/components/HeaderUtilities.tsx @@ -0,0 +1,17 @@ +import { BellIcon } from '@heroicons/react/outline'; +import LogOffIcon from '../../public/icons/logoff.svg'; + +const HeaderUtilities = () => { + return ( +
+ + +
+ ); +}; + +export default HeaderUtilities; diff --git a/src/components/NavigationBarAside.tsx b/src/components/NavigationBarAside.tsx new file mode 100644 index 0000000..8cf2bf2 --- /dev/null +++ b/src/components/NavigationBarAside.tsx @@ -0,0 +1,38 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { useState } from 'react'; +import NavigationBarAsideItems from './NavigationBarAsideItems'; + +const NavigationBarAside = () => { + const [isLoggedIn, setIsLoggedIn] = useState(true); + + return ( + + ); +}; + +export default NavigationBarAside; diff --git a/src/components/NavigationBarAsideItems.tsx b/src/components/NavigationBarAsideItems.tsx new file mode 100644 index 0000000..1a62e99 --- /dev/null +++ b/src/components/NavigationBarAsideItems.tsx @@ -0,0 +1,59 @@ +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { HomeIcon, UserCircleIcon, UsersIcon } from '@heroicons/react/solid'; +import { ClockIcon, HashtagIcon, TagIcon } from '@heroicons/react/outline'; + +const NavigationBarAsideItems = () => { + const router = useRouter(); + const navigationItems = [ + { + href: '/home', + svg: , + name: 'Home', + }, + { + href: '/my-profile', + svg: , + name: 'My Profile', + }, + { + href: '/activity-feed', + svg: , + name: 'Activity Feed', + }, + { + href: '/trending', + svg: , + name: 'Trending', + }, + { + href: '/friends', + svg: , + name: 'Friends', + }, + { + href: '/lobbies', + svg: , + name: 'Lobbies', + }, + ]; + + return ( +
    + {navigationItems.map((item, index) => ( + +
  • + {item.svg} + {item.name} +
  • + + ))} +
+ ); +}; + +export default NavigationBarAsideItems; diff --git a/src/components/UtilityWheel.tsx b/src/components/UtilityWheel.tsx new file mode 100644 index 0000000..59e32f7 --- /dev/null +++ b/src/components/UtilityWheel.tsx @@ -0,0 +1,13 @@ +import { ChatIcon } from '@heroicons/react/outline'; + +const UtilityWheel = () => { + return ( + + ); +}; + +export default UtilityWheel; diff --git a/src/helpers/setPageTitle.ts b/src/helpers/setPageTitle.ts index e48963f..cd1d0fc 100644 --- a/src/helpers/setPageTitle.ts +++ b/src/helpers/setPageTitle.ts @@ -1,17 +1,17 @@ -import { useEffect } from "react"; +import { useEffect } from 'react'; -export const PageTitle = (router: {pathname: string}) => { - const currentURL = router.pathname - const pageURL = currentURL.replace(/\//g, "") + "-page" - const routeTitle = currentURL.replace(/\//g, "") +export const PageTitle = (router: { pathname: string }) => { + const currentURL = router.pathname; + const pageURL = currentURL.replace(/\//g, '') + '-page'; + const routeTitle = currentURL.replace(/\//g, ''); useEffect(() => { - document.querySelector("body")!.classList.add(pageURL) - }) + document.querySelector('body')!.classList.add(pageURL); + }); const capitalizeFirstLetter = (URL: string) => { - return URL.charAt(0).toUpperCase() + URL.slice(1) - } + return URL.charAt(0).toUpperCase() + URL.slice(1); + }; - return capitalizeFirstLetter(routeTitle) -} + return capitalizeFirstLetter(routeTitle); +}; diff --git a/src/layouts/authentification.tsx b/src/layouts/authentification.tsx index 0f277e1..c5afdcc 100644 --- a/src/layouts/authentification.tsx +++ b/src/layouts/authentification.tsx @@ -1,21 +1,19 @@ -import Head from "next/head" +import Head from 'next/head'; interface PropsWithChildren { - children: React.ReactNode + children: React.ReactNode; } -const authentification = ({children}: PropsWithChildren) => { +const authentification = ({ children }: PropsWithChildren) => { return ( <> Welcome to CRIT -
- {children} -
+
{children}
- ) -} + ); +}; -export default authentification +export default authentification; diff --git a/src/layouts/base.tsx b/src/layouts/base.tsx deleted file mode 100644 index 0fc8161..0000000 --- a/src/layouts/base.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from 'next/head' - -interface PropsWithChildren { - children: React.ReactNode -} - -const authentification = ({children}: PropsWithChildren) => { - return ( - <> - - Crit - - -
- {children} -
- - ) -} - -export default authentification diff --git a/src/layouts/dashboard.tsx b/src/layouts/dashboard.tsx new file mode 100644 index 0000000..a1b9a37 --- /dev/null +++ b/src/layouts/dashboard.tsx @@ -0,0 +1,30 @@ +import Head from 'next/head'; +import { withRouter, NextRouter } from 'next/router'; +import NavigationBarAside from '../components/NavigationBarAside'; +import Header from '../components/Header'; +import UtilityWheel from '../components/UtilityWheel'; +import { PageTitle } from '../helpers/setPageTitle'; + +interface PropsWithChildren { + children: React.ReactNode; + router: NextRouter; +} + +const dashboard = ({ children, router }: PropsWithChildren) => { + return ( + <> + + CRIT | {PageTitle(router)} + + +
+ +
+ {children} + +
+ + ); +}; + +export default withRouter(dashboard); diff --git a/styles/base/_typography.scss b/styles/base/_typography.scss index a9bea35..ad0a13d 100644 --- a/styles/base/_typography.scss +++ b/styles/base/_typography.scss @@ -8,7 +8,8 @@ label { h1, h2, -h3 { +h3, +li { @apply font-raleway; } diff --git a/styles/components/_header-lobby.scss b/styles/components/_header-lobby.scss new file mode 100644 index 0000000..6701a5c --- /dev/null +++ b/styles/components/_header-lobby.scss @@ -0,0 +1,59 @@ +.header-lobby { + @apply hidden; + + @screen md { + @apply flex flex-row mr-7; + } + + &__users { + @apply hidden; + + @screen lg { + @apply flex flex-row justify-center items-center; + } + } + + &__user { + @apply mr-2.5 relative cursor-pointer; + + &:last-child { + @apply mr-0; + } + + &:before { + @apply absolute left-0 top-0 border-2 border-primary-default rounded-full; + + content: ''; + height: 30px; + width: 30px; + z-index: 1; + } + + &:hover { + .header-lobby__user-hover-name { + @apply block; + } + } + } + + &__user-picture { + @apply flex; + + img, + span { + @apply rounded-full; + } + } + + &__user-hover-name { + @apply hidden absolute rounded-md bg-black-100 px-3 py-1; + + left: 50%; + transform: translateX(-50%); + top: 40px; + + span { + @apply text-white; + } + } +} diff --git a/styles/components/_header-utilities.scss b/styles/components/_header-utilities.scss new file mode 100644 index 0000000..fb2aac1 --- /dev/null +++ b/styles/components/_header-utilities.scss @@ -0,0 +1,49 @@ +.header-utilities { + @apply flex flex-row; + + + &__notifications, + &__logout { + svg { + @apply text-white transition; + } + + &:hover { + svg { + @apply text-primary-default; + } + } + } + + &__logout { + svg { + height: 20px; + width: 20px; + + @screen md { + height: 25px; + width: 25px; + } + } + } + + &__notifications { + @apply mr-5; + + @screen md { + @apply mr-7; + } + + svg { + @apply stroke-1; + + height: 25px; + width: 25px; + + @screen md { + height: 30px; + width: 30px; + } + } + } +} diff --git a/styles/components/_header.scss b/styles/components/_header.scss index e69de29..70184ac 100644 --- a/styles/components/_header.scss +++ b/styles/components/_header.scss @@ -0,0 +1,60 @@ +.header { + @apply flex flex-row pt-6 pr-4 justify-between; + + @screen md { + @apply pt-10 pr-14; + + height: 155px; + } + + &__left { + @apply flex flex-row ml-7; + } + + &__right { + @apply flex flex-row; + + @screen md { + height: 40px; + } + } + + &__login-button { + @apply font-semibold font-raleway uppercase; + + height: 37px; + + @screen md { + width: 105px; + } + } + + &__image { + @apply flex flex-row cursor-pointer; + + height: 40px; + + svg { + height: 40px; + width: 50px; + } + + h2 { + @apply mt-auto leading-none font-semibold uppercase; + } + } + + &__message-of-the-day { + @apply hidden; + + @screen md { + @apply flex; + + height: 40px; + } + + p { + @apply my-auto ml-14 leading-none font-raleway font-medium; + } + } +} diff --git a/styles/components/_navigation-bar-aside-items.scss b/styles/components/_navigation-bar-aside-items.scss new file mode 100644 index 0000000..58c2af9 --- /dev/null +++ b/styles/components/_navigation-bar-aside-items.scss @@ -0,0 +1,50 @@ +.navigation-bar-aside-items { + @apply pt-9; + + @screen md { + @apply pt-16; + } + + &__item { + @apply text-white font-semibold flex flex-row text-sm items-center cursor-pointer mb-8; + + span { + @apply hidden transition; + + @screen md { + @apply block; + } + } + + svg { + @apply stroke-white w-6 h-6 transition; + + fill: transparent; + + @screen md { + @apply mr-3; + } + } + + &:last-child { + @apply mb-0; + } + + &:hover { + span { + @apply text-primary-default; + } + + svg { + @apply stroke-primary-default; + } + } + + &.active { + span, + svg { + @apply text-primary-default stroke-primary-default; + } + } + } +} diff --git a/styles/components/_navigation-bar-aside.scss b/styles/components/_navigation-bar-aside.scss new file mode 100644 index 0000000..b70dc29 --- /dev/null +++ b/styles/components/_navigation-bar-aside.scss @@ -0,0 +1,66 @@ +.navigation-bar-aside { + @apply py-6 w-full h-screen flex flex-col items-center; + + max-width: 70px; + grid-row-start: 1; + grid-row-end: 3; + + @screen md { + @apply py-10 pl-14 block; + + max-width: 215px; + } + + &__logo { + @apply flex flex-row; + + svg { + height: 50px; + width: 40px; + } + + h2 { + @apply mt-auto leading-none font-semibold uppercase; + } + } + + &__profile-block { + @apply relative; + + @screen md { + @apply w-full flex flex-col items-center justify-center py-3.5; + + background-color: rgba(255, 255, 255, .02); + border-radius: 3px; + box-shadow: 0px 0px 10px 5px rgba(0,0,0,0.05); + max-width: 105px; + } + + h3 { + @apply font-semibold text-xs hidden cursor-pointer transition; + + @screen md { + @apply mt-3.5 block; + } + + &:hover { + @apply text-primary-default; + } + } + + &.logged-out { + background-color: transparent; + box-shadow: none; + height: 115px; + } + } + + &__profile-picture { + @apply rounded-full; + + img, + span { + @apply rounded-full; + } + } +} diff --git a/styles/components/_utility-wheel.scss b/styles/components/_utility-wheel.scss new file mode 100644 index 0000000..2c3ac29 --- /dev/null +++ b/styles/components/_utility-wheel.scss @@ -0,0 +1,45 @@ +.utility-wheel { + @apply bg-primary-default fixed rounded-full; + + bottom: -90px; + right: -90px; + height: 150px; + width: 150px; + + @screen md { + bottom: -80px; + right: -80px; + } + + &__chat { + @apply bg-white rounded-full relative transition; + + height: 40px; + width: 40px; + + svg { + @apply m-auto transition; + + height: 25px; + width: 25px; + + @screen md { + height: 35px; + width: 35px; + } + } + + @screen md { + height: 50px; + width: 50px; + } + + &:hover { + @apply bg-primary-default; + + svg { + @apply text-white; + } + } + } +} diff --git a/styles/layouts/_dashboard.scss b/styles/layouts/_dashboard.scss new file mode 100644 index 0000000..cc7a9a2 --- /dev/null +++ b/styles/layouts/_dashboard.scss @@ -0,0 +1,11 @@ +.dashboard { + @apply grid; + + grid-template-columns: 70px 1fr; + grid-template-rows: 76px 1fr; + + @screen md { + grid-template-columns: 215px 1fr; + grid-template-rows: 115px 1fr; + } +} diff --git a/styles/styles.scss b/styles/styles.scss index 7ea9023..9cf2c99 100644 --- a/styles/styles.scss +++ b/styles/styles.scss @@ -13,12 +13,18 @@ // Layouts @import "./layouts/base"; +@import "./layouts/dashboard"; // Screens @import "./screens/home"; // Components @import "./components/header"; +@import "./components/header-utilities"; +@import "./components/header-lobby"; +@import "./components/utility-wheel"; +@import "./components/navigation-bar-aside"; +@import "./components/navigation-bar-aside-items"; @import "./components/footer"; @import "./components/registration"; @import "./components/registration-form";