rework sidemenu + fix no categories / links

This commit is contained in:
Sonny
2022-04-28 18:22:39 +02:00
parent 1ab51979fe
commit 1a5fb2eee4
14 changed files with 309 additions and 853 deletions

View File

@@ -1,37 +1,13 @@
import { signOut } from "next-auth/react"
import { Session } from 'next-auth';
import LinkTag from "next/link";
import Image from "next/image";
import styles from '../../styles/home/categories.module.scss'; import styles from '../../styles/home/categories.module.scss';
import { Category, Link } from '../../types'; import { Category } from '../../types';
interface CategoryProps { interface CategoriesProps {
categories: Category[]; categories: Category[];
favorites: Link[];
handleSelectCategory: (category: Category) => void;
categoryActive: Category; categoryActive: Category;
session: Session; handleSelectCategory: (category: Category) => void;
} }
export default function Categories({ export default function Categories({ categories, categoryActive, handleSelectCategory }: CategoriesProps) {
categories, return (
favorites,
handleSelectCategory,
categoryActive,
session
}: CategoryProps) {
return (<div className={styles['categories-wrapper']}>
<div className={`${styles['block-wrapper']} ${styles['favorites']}`}>
<h4>Favoris</h4>
<ul className={styles['items']}>
{favorites.map((link, key) => (
<LinkFavorite
link={link}
key={key}
/>
))}
</ul>
</div>
<div className={`${styles['block-wrapper']} ${styles['categories']}`}> <div className={`${styles['block-wrapper']} ${styles['categories']}`}>
<h4>Catégories</h4> <h4>Catégories</h4>
<ul className={styles['items']}> <ul className={styles['items']}>
@@ -45,39 +21,6 @@ export default function Categories({
))} ))}
</ul> </ul>
</div> </div>
<div className={styles['controls']}>
<LinkTag href={'/category/create'}>
<a>Créer categorie</a>
</LinkTag>
<LinkTag href={'/link/create'}>
<a>Créer lien</a>
</LinkTag>
</div>
<div className={styles['user-card-wrapper']}>
<div className={styles['user-card']}>
<Image
src={session.user.image}
width={28}
height={28}
alt={`${session.user.name}'s avatar`}
/>
{session.user.name}
</div>
<button onClick={() => signOut()} className={styles['disconnect-btn']}>
Se déconnecter
</button>
</div>
</div>);
}
function LinkFavorite({ link }: { link: Link; }): JSX.Element {
const { name, url, category } = link;
return (
<li className={styles['item']}>
<a href={url} target={'_blank'} rel={'noreferrer'}>
{name} <span className={styles['category']}>- {category.name}</span>
</a>
</li>
) )
} }

View File

@@ -0,0 +1,36 @@
import styles from '../../styles/home/categories.module.scss';
import { Link } from '../../types';
export default function Favorites({ favorites }: { favorites: Link[]; }) {
return (
<div className={`${styles['block-wrapper']} ${styles['favorites']}`}>
<h4>Favoris</h4>
<ul className={styles['items']}>
{favorites.length === 0
? <NoFavLink />
: favorites.map((link, key) => (
<LinkFavorite link={link} key={key} />
))}
</ul>
</div>
)
}
function NoFavLink(): JSX.Element {
return (
<li className={styles['no-fav-link']}>
Aucun favoris
</li>
)
}
function LinkFavorite({ link }: { link: Link; }): JSX.Element {
const { name, url, category } = link;
return (
<li className={styles['item']}>
<a href={url} target={'_blank'} rel={'noreferrer'}>
{name}<span className={styles['category']}> - {category.name}</span>
</a>
</li>
)
}

View File

@@ -1,34 +0,0 @@
import Modal from 'react-modal';
import styles from '../../styles/categories.module.scss';
Modal.setAppElement('#__next');
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
export default function ModalAddCategory({ categories, isOpen, closeModal }) {
function handleAddCategory() {
}
return (
<Modal
className={styles['modal-overlay']}
isOpen={isOpen}
onRequestClose={closeModal}
style={customStyles}
>
<h2>Ajouter une catégorie</h2>
<button onClick={closeModal}>close</button>
</Modal>
);
}

View File

@@ -0,0 +1,47 @@
import { Session } from 'next-auth';
import LinkTag from 'next/link';
import styles from '../../styles/home/categories.module.scss';
import { Category, Link } from '../../types';
import Categories from './Categories';
import Favorites from './Favorites';
import UserCard from './UserCard';
interface SideMenuProps {
categories: Category[];
favorites: Link[];
handleSelectCategory: (category: Category) => void;
categoryActive: Category;
session: Session;
}
export default function SideMenu({
categories,
favorites,
handleSelectCategory,
categoryActive,
session
}: SideMenuProps) {
return (<div className={styles['categories-wrapper']}>
<Favorites favorites={favorites} />
<Categories
categories={categories}
categoryActive={categoryActive}
handleSelectCategory={handleSelectCategory}
/>
<MenuControls />
<UserCard session={session} />
</div>);
}
function MenuControls() {
return (
<div className={styles['controls']}>
<LinkTag href={'/category/create'}>
<a>Créer categorie</a>
</LinkTag>
<LinkTag href={'/link/create'}>
<a>Créer lien</a>
</LinkTag>
</div>
)
}

View File

@@ -0,0 +1,23 @@
import { Session } from 'next-auth';
import { signOut } from 'next-auth/react';
import Image from 'next/image';
import styles from '../../styles/home/categories.module.scss';
export default function UserCard({ session }: { session: Session; }) {
return (
<div className={styles['user-card-wrapper']}>
<div className={styles['user-card']}>
<Image
src={session.user.image}
width={28}
height={28}
alt={`${session.user.name}'s avatar`}
/>
{session.user.name}
</div>
<button onClick={() => signOut()} className={styles['disconnect-btn']}>
Se déconnecter
</button>
</div>
)
}

View File

@@ -1,8 +1,27 @@
import LinkTag from 'next/link';
import styles from '../../styles/home/links.module.scss'; import styles from '../../styles/home/links.module.scss';
import { Category, Link } from '../../types'; import { Category, Link } from '../../types';
export default function Links({ category }: { category: Category; }) { export default function Links({ category }: { category: Category; }) {
if (category === null) {
return (<div className={styles['no-category']}>
<p>Veuillez séléctionner une categorié</p>
<LinkTag href='/category/create'>
<a>ou en créer une</a>
</LinkTag>
</div>)
}
const { name, links } = category; const { name, links } = category;
if (links.length === 0) {
return (<div className={styles['no-link']}>
<p>Aucun lien pour <b>{category.name}</b></p>
<LinkTag href='/link/create'>
<a>Créer un lien</a>
</LinkTag>
</div>)
}
return (<div className={styles['links-wrapper']}> return (<div className={styles['links-wrapper']}>
<h2>{name}<span className={styles['links-count']}> {links.length}</span></h2> <h2>{name}<span className={styles['links-count']}> {links.length}</span></h2>
@@ -19,7 +38,7 @@ function LinkItem({ link }: { link: Link; }) {
const { origin, pathname, search } = new URL(url); const { origin, pathname, search } = new URL(url);
return ( return (
<li className={styles['link']}> <li className={styles['link']} key={Math.random()}>
<a href={url} target={'_blank'} rel={'noreferrer'}> <a href={url} target={'_blank'} rel={'noreferrer'}>
<span className={styles['link-name']}> <span className={styles['link-name']}>
{name}<span className={styles['link-category']}> {category.name}</span> {name}<span className={styles['link-category']}> {category.name}</span>

798
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,13 +10,11 @@
"dependencies": { "dependencies": {
"@prisma/client": "^3.13.0", "@prisma/client": "^3.13.0",
"@reduxjs/toolkit": "^1.8.1", "@reduxjs/toolkit": "^1.8.1",
"bcrypt": "^5.0.1",
"next": "^12.1.5", "next": "^12.1.5",
"next-auth": "^4.0.6", "next-auth": "^4.0.6",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"react": "^18.1.0", "react": "^18.1.0",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-modal": "^3.14.4",
"react-redux": "^8.0.1", "react-redux": "^8.0.1",
"sass": "^1.46.0" "sass": "^1.46.0"
}, },

View File

@@ -1,10 +1,9 @@
import { createRef, useRef, useState } from 'react'; import { useState } from 'react';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import Head from 'next/head' import Head from 'next/head';
import Categories from '../components/Categories/Categories'; import Menu from '../components/Categories/SideMenu';
import Links from '../components/Links/Links';
import { Category, Link } from '../types'; import { Category, Link } from '../types';
@@ -12,6 +11,9 @@ import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
import { store } from '../redux'; import { store } from '../redux';
import { BuildCategory } from '../utils/front';
import Links from '../components/Links/Links';
interface HomeProps { interface HomeProps {
categories: Category[]; categories: Category[];
@@ -34,7 +36,7 @@ export default function Home({ categories, favorites }: HomeProps) {
<title>Superpipo</title> <title>Superpipo</title>
</Head> </Head>
<div className='App'> <div className='App'>
<Categories <Menu
categories={categories} categories={categories}
favorites={favorites} favorites={favorites}
handleSelectCategory={handleSelectCategory} handleSelectCategory={handleSelectCategory}
@@ -57,37 +59,18 @@ export async function getStaticProps() {
return category; return category;
}); });
if (categories.length === 0) {
return {
redirect: {
destination: '/category/create'
}
}
}
return { return {
props: { props: {
categories: JSON.parse(JSON.stringify(categories)), categories: JSON.parse(JSON.stringify(categories)),
favorites: JSON.parse(JSON.stringify(favorites)), favorites: JSON.parse(JSON.stringify(favorites)),
} }
} }
}
export function BuildCategory({ id, name, order, links = [], createdAt, updatedAt }): Category {
return {
id,
name,
links: links.map((link) => BuildLink(link, { categoryId: id, categoryName: name })),
order,
createdAt,
updatedAt
}
}
export function BuildLink({ id, name, url, order, favorite, createdAt, updatedAt }, { categoryId, categoryName }): Link {
return {
id,
name,
url,
category: {
id: categoryId,
name: categoryName
},
order,
favorite,
createdAt,
updatedAt
}
} }

View File

@@ -6,7 +6,7 @@ import Input from '../../components/input';
import styles from '../../styles/create.module.scss'; import styles from '../../styles/create.module.scss';
import { Category } from '../../types'; import { Category } from '../../types';
import { BuildCategory } from '..'; import { BuildCategory } from '../../utils/front';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import Selector from '../../components/selector'; import Selector from '../../components/selector';

View File

@@ -34,6 +34,7 @@ body {
padding: 10px; padding: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
animation: fadein 250ms both;
} }
a { a {
@@ -142,3 +143,13 @@ select:not(.nostyle) {
width: 100%; width: 100%;
} }
} }
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@@ -59,9 +59,26 @@
& .block-wrapper.favorites { & .block-wrapper.favorites {
margin-bottom: 15px; margin-bottom: 15px;
& .items .item {
padding: 0;
display: flex;
align-items: center;
}
& .items .no-fav-link {
user-select: none;
text-align: center;
font-style: italic;
font-size: 0.9em;
color: #bbb;
}
& .items .item a { & .items .item a {
width: 100%;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
padding: 7px 12px;
border: 0 !important;
} }
& .items .item .category { & .items .item .category {
@@ -98,13 +115,14 @@
// User Card // User Card
& .user-card-wrapper { & .user-card-wrapper {
position: relative; position: relative;
user-select: none;
height: fit-content; height: fit-content;
width: 100%; width: 100%;
color: #333; color: #333;
background-color: #fff; background-color: #fff;
& .user-card { & .user-card {
border-right: 1px solid #dadce0; border: 1px solid #dadce0;
padding: 7px 12px; padding: 7px 12px;
display: flex; display: flex;
gap: 10px; gap: 10px;
@@ -122,15 +140,11 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
color: #fff; color: #fff;
background-color: red;
border: 1px solid red;
display: none; display: none;
} }
&:hover .disconnect-btn { &:hover .disconnect-btn {
display: block; display: block;
border: 1px solid darkred;
box-shadow: red 0 0 3px 1px;
} }
} }
} }

View File

@@ -1,3 +1,12 @@
.no-link,
.no-category {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
flex-direction: column;
}
.links-wrapper { .links-wrapper {
height: 100%; height: 100%;
padding: 10px; padding: 10px;
@@ -24,6 +33,7 @@
& .links .link { & .links .link {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
animation: fadein 0.3s both;
& > a { & > a {
height: fit-content; height: fit-content;
@@ -70,3 +80,15 @@
} }
} }
} }
@keyframes fadein {
0% {
transform: translateX(-15px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}

28
utils/front.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Category, Link } from "../types"
export function BuildCategory({ id, name, order, links = [], createdAt, updatedAt }): Category {
return {
id,
name,
links: links.map((link) => BuildLink(link, { categoryId: id, categoryName: name })),
order,
createdAt,
updatedAt
}
}
export function BuildLink({ id, name, url, order, favorite, createdAt, updatedAt }, { categoryId, categoryName }): Link {
return {
id,
name,
url,
category: {
id: categoryId,
name: categoryName
},
order,
favorite,
createdAt,
updatedAt
}
}