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 (
+
+ );
+};
+
+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 (
+
+
+ {userList.map((user, index) => (
+
+ -
+
+
+
+
+ {user.username}
+
+
+
+ ))}
+
+
+ );
+};
+
+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";