mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 15:05:35 +00:00
rework sidemenu + fix no categories / links
This commit is contained in:
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
components/Categories/Favorites.tsx
Normal file
36
components/Categories/Favorites.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
47
components/Categories/SideMenu.tsx
Normal file
47
components/Categories/SideMenu.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
components/Categories/UserCard.tsx
Normal file
23
components/Categories/UserCard.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
798
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
28
utils/front.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user