mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
28
src/components/ButtonLink.tsx
Normal file
28
src/components/ButtonLink.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import Link from "next/link";
|
||||
import { CSSProperties, ReactNode } from "react";
|
||||
|
||||
export default function ButtonLink({
|
||||
href = "#",
|
||||
onClick,
|
||||
children,
|
||||
style = {},
|
||||
className = "",
|
||||
}: {
|
||||
href?: string;
|
||||
onClick?: (...args: any) => any;
|
||||
children: ReactNode;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
}) {
|
||||
const handleClick = (event) => {
|
||||
if (!href || href === "#") {
|
||||
event.preventDefault();
|
||||
}
|
||||
onClick && onClick();
|
||||
};
|
||||
return (
|
||||
<Link href={href} onClick={handleClick} style={style} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -3,8 +3,6 @@ import Link from "next/link";
|
||||
|
||||
import MessageManager from "components/MessageManager/MessageManager";
|
||||
|
||||
import styles from "styles/create.module.scss";
|
||||
|
||||
interface FormProps {
|
||||
title: string;
|
||||
|
||||
@@ -39,29 +37,23 @@ export default function Form({
|
||||
return (
|
||||
<>
|
||||
<NextSeo title={title} />
|
||||
<div className={`App ${styles["create-app"]}`}>
|
||||
<h2>{title}</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{children}
|
||||
<button
|
||||
type="submit"
|
||||
className={classBtnConfirm}
|
||||
disabled={!canSubmit}
|
||||
>
|
||||
{textBtnConfirm}
|
||||
</button>
|
||||
</form>
|
||||
{!disableHomeLink && (
|
||||
<Link href={categoryId ? `/?categoryId=${categoryId}` : "/"}>
|
||||
← Revenir à l'accueil
|
||||
</Link>
|
||||
)}
|
||||
<MessageManager
|
||||
info={infoMessage}
|
||||
error={errorMessage}
|
||||
success={successMessage}
|
||||
/>
|
||||
</div>
|
||||
<h2>{title}</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{children}
|
||||
<button type="submit" className={classBtnConfirm} disabled={!canSubmit}>
|
||||
{textBtnConfirm}
|
||||
</button>
|
||||
</form>
|
||||
{!disableHomeLink && (
|
||||
<Link href={categoryId ? `/?categoryId=${categoryId}` : "/"}>
|
||||
← Revenir à l'accueil
|
||||
</Link>
|
||||
)}
|
||||
<MessageManager
|
||||
info={infoMessage}
|
||||
error={errorMessage}
|
||||
success={successMessage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { motion } from "framer-motion";
|
||||
import LinkTag from "next/link";
|
||||
|
||||
import { Category, Link } from "types";
|
||||
@@ -6,15 +7,24 @@ import EditItem from "components/QuickActions/EditItem";
|
||||
import RemoveItem from "components/QuickActions/RemoveItem";
|
||||
import LinkItem from "./LinkItem";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import ButtonLink from "components/ButtonLink";
|
||||
import CreateItem from "components/QuickActions/CreateItem";
|
||||
import QuickActionSearch from "components/QuickActions/Search";
|
||||
import { RxHamburgerMenu } from "react-icons/rx";
|
||||
import styles from "./links.module.scss";
|
||||
|
||||
export default function Links({
|
||||
category,
|
||||
toggleFavorite,
|
||||
isMobile,
|
||||
openMobileModal,
|
||||
openSearchModal,
|
||||
}: {
|
||||
category: Category;
|
||||
toggleFavorite: (linkId: Link["id"]) => void;
|
||||
isMobile: boolean;
|
||||
openMobileModal: () => void;
|
||||
openSearchModal: () => void;
|
||||
}) {
|
||||
if (category === null) {
|
||||
return (
|
||||
@@ -29,13 +39,28 @@ export default function Links({
|
||||
return (
|
||||
<div className={styles["links-wrapper"]}>
|
||||
<h2 className={styles["category-header"]}>
|
||||
<span className={styles["category-name"]}>
|
||||
<span
|
||||
className={styles["category-name"]}
|
||||
style={{ display: "flex", alignItems: "center", gap: ".25em" }}
|
||||
>
|
||||
{isMobile && (
|
||||
<ButtonLink
|
||||
style={{
|
||||
display: "flex",
|
||||
}}
|
||||
onClick={openMobileModal}
|
||||
>
|
||||
<RxHamburgerMenu size={"1.5em"} style={{ marginRight: ".5em" }} />
|
||||
</ButtonLink>
|
||||
)}
|
||||
{name}
|
||||
{links.length > 0 && (
|
||||
<span className={styles["links-count"]}> — {links.length}</span>
|
||||
)}
|
||||
</span>
|
||||
<span className={styles["category-controls"]}>
|
||||
<QuickActionSearch openSearchModal={openSearchModal} />
|
||||
<CreateItem type="link" categoryId={category.id} />
|
||||
<EditItem type="category" id={id} />
|
||||
<RemoveItem type="category" id={id} />
|
||||
</span>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
.links-wrapper {
|
||||
height: 100%;
|
||||
height: calc(100% - 54px); // FIXME: eurk
|
||||
min-width: 0;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
||||
@@ -48,4 +48,14 @@
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.modal-container {
|
||||
max-height: calc(100% - 2em);
|
||||
width: calc(100% - 2em);
|
||||
min-width: unset;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import LinkTag from "next/link";
|
||||
import { GrAdd } from "react-icons/gr";
|
||||
import { IoAddOutline } from "react-icons/io5";
|
||||
|
||||
import { Category } from "types";
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function CreateItem({
|
||||
onClick,
|
||||
}: {
|
||||
type: "category" | "link";
|
||||
categoryId: Category["id"];
|
||||
categoryId?: Category["id"];
|
||||
onClick?: (event: any) => void; // FIXME: find good event type
|
||||
}) {
|
||||
return (
|
||||
@@ -21,7 +21,7 @@ export default function CreateItem({
|
||||
className={styles["action"]}
|
||||
onClick={onClick && onClick}
|
||||
>
|
||||
<GrAdd />
|
||||
<IoAddOutline />
|
||||
</LinkTag>
|
||||
);
|
||||
}
|
||||
|
||||
16
src/components/QuickActions/Search.tsx
Normal file
16
src/components/QuickActions/Search.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import ButtonLink from "components/ButtonLink";
|
||||
import { BsSearch } from "react-icons/bs";
|
||||
|
||||
import styles from "./quickactions.module.scss";
|
||||
|
||||
export default function QuickActionSearch({
|
||||
openSearchModal,
|
||||
}: {
|
||||
openSearchModal: () => void;
|
||||
}) {
|
||||
return (
|
||||
<ButtonLink className={styles["action"]} onClick={openSearchModal}>
|
||||
<BsSearch />
|
||||
</ButtonLink>
|
||||
);
|
||||
}
|
||||
@@ -19,11 +19,13 @@ export default function SearchModal({
|
||||
handleSelectCategory,
|
||||
categories,
|
||||
items,
|
||||
noHeader = true,
|
||||
}: {
|
||||
close: () => void;
|
||||
handleSelectCategory: (category: Category) => void;
|
||||
categories: Category[];
|
||||
items: SearchItem[];
|
||||
noHeader?: boolean;
|
||||
}) {
|
||||
const autoFocusRef = useAutoFocus();
|
||||
|
||||
@@ -98,7 +100,7 @@ export default function SearchModal({
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal title="Rechercher" close={close} noHeader padding={"0"}>
|
||||
<Modal close={close} noHeader={noHeader} padding={"0"}>
|
||||
<form onSubmit={handleSubmit} className={styles["search-form"]}>
|
||||
<div className={styles["search-input-wrapper"]}>
|
||||
<label htmlFor="search">
|
||||
|
||||
38
src/components/SideMenu/NavigationLinks.tsx
Normal file
38
src/components/SideMenu/NavigationLinks.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import PATHS from "constants/paths";
|
||||
import { SideMenuProps } from "./SideMenu";
|
||||
|
||||
import ButtonLink from "components/ButtonLink";
|
||||
import styles from "./sidemenu.module.scss";
|
||||
|
||||
export default function NavigationLinks({
|
||||
categoryActive,
|
||||
openSearchModal,
|
||||
}: {
|
||||
categoryActive: SideMenuProps["categoryActive"];
|
||||
openSearchModal: SideMenuProps["openSearchModal"];
|
||||
}) {
|
||||
const handleOpenSearchModal = (event) => {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
};
|
||||
return (
|
||||
<div className={styles["menu-controls"]}>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink onClick={openSearchModal}>Rechercher</ButtonLink>
|
||||
<kbd>S</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink href={PATHS.CATEGORY.CREATE}>Créer categorie</ButtonLink>
|
||||
<kbd>C</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink
|
||||
href={`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`}
|
||||
>
|
||||
Créer lien
|
||||
</ButtonLink>
|
||||
<kbd>L</kbd>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +1,63 @@
|
||||
import LinkTag from "next/link";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
import BlockWrapper from "components/BlockWrapper/BlockWrapper";
|
||||
import Categories from "./Categories/Categories";
|
||||
import NavigationLinks from "./NavigationLinks";
|
||||
import Favorites from "./Favorites/Favorites";
|
||||
import UserCard from "./UserCard/UserCard";
|
||||
|
||||
import * as Keys from "constants/keys";
|
||||
import { Category, Link } from "types";
|
||||
import PATHS from "constants/paths";
|
||||
|
||||
import styles from "./sidemenu.module.scss";
|
||||
|
||||
interface SideMenuProps {
|
||||
export interface SideMenuProps {
|
||||
categories: Category[];
|
||||
favorites: Link[];
|
||||
handleSelectCategory: (category: Category) => void;
|
||||
categoryActive: Category;
|
||||
openSearchModal: () => void;
|
||||
isModalShowing: boolean;
|
||||
}
|
||||
|
||||
export default function SideMenu({
|
||||
categories,
|
||||
favorites,
|
||||
handleSelectCategory,
|
||||
categoryActive,
|
||||
openSearchModal,
|
||||
isModalShowing = false,
|
||||
}: SideMenuProps) {
|
||||
useHotkeys(
|
||||
Keys.ARROW_UP,
|
||||
() => {
|
||||
const currentCategoryIndex = categories.findIndex(
|
||||
({ id }) => id === categoryActive.id
|
||||
);
|
||||
if (currentCategoryIndex === -1 || currentCategoryIndex === 0) return;
|
||||
|
||||
handleSelectCategory(categories[currentCategoryIndex - 1]);
|
||||
},
|
||||
{ enabled: !isModalShowing }
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
Keys.ARROW_DOWN,
|
||||
() => {
|
||||
const currentCategoryIndex = categories.findIndex(
|
||||
({ id }) => id === categoryActive.id
|
||||
);
|
||||
if (
|
||||
currentCategoryIndex === -1 ||
|
||||
currentCategoryIndex === categories.length - 1
|
||||
)
|
||||
return;
|
||||
|
||||
handleSelectCategory(categories[currentCategoryIndex + 1]);
|
||||
},
|
||||
{ enabled: !isModalShowing }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles["side-menu"]}>
|
||||
<BlockWrapper>
|
||||
@@ -37,7 +71,7 @@ export default function SideMenu({
|
||||
/>
|
||||
</BlockWrapper>
|
||||
<BlockWrapper>
|
||||
<MenuControls
|
||||
<NavigationLinks
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={openSearchModal}
|
||||
/>
|
||||
@@ -48,36 +82,3 @@ export default function SideMenu({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuControls({
|
||||
categoryActive,
|
||||
openSearchModal,
|
||||
}: {
|
||||
categoryActive: SideMenuProps["categoryActive"];
|
||||
openSearchModal: SideMenuProps["openSearchModal"];
|
||||
}) {
|
||||
const handleOpenSearchModal = (event) => {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
};
|
||||
return (
|
||||
<div className={styles["menu-controls"]}>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={"/#"} onClick={handleOpenSearchModal}>
|
||||
Rechercher
|
||||
</LinkTag>
|
||||
<kbd>S</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={PATHS.CATEGORY.CREATE}>Créer categorie</LinkTag>
|
||||
<kbd>C</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`}>
|
||||
Créer lien
|
||||
</LinkTag>
|
||||
<kbd>L</kbd>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/hooks/useMediaQuery.tsx
Normal file
25
src/hooks/useMediaQuery.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState<boolean>(getMediaMatches(query));
|
||||
|
||||
const handleMediaChange = () => setMatches(getMediaMatches(query));
|
||||
|
||||
useEffect(() => {
|
||||
const matchMedia = window.matchMedia(query);
|
||||
handleMediaChange();
|
||||
|
||||
matchMedia.addEventListener("change", handleMediaChange);
|
||||
return () => matchMedia.removeEventListener("change", handleMediaChange);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function getMediaMatches(query: string): boolean {
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -9,14 +9,14 @@ import TextBox from "components/TextBox";
|
||||
|
||||
import PATHS from "constants/paths";
|
||||
import useAutoFocus from "hooks/useAutoFocus";
|
||||
import { redirectWithoutClientCache } from "utils/client";
|
||||
import { HandleAxiosError } from "utils/front";
|
||||
|
||||
import getUserCategoriesCount from "lib/category/getUserCategoriesCount";
|
||||
import getUser from "lib/user/getUser";
|
||||
import styles from "styles/create.module.scss";
|
||||
import { redirectWithoutClientCache } from "utils/client";
|
||||
import { HandleAxiosError } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function CreateCategory({ categoriesCount }: { categoriesCount: number }) {
|
||||
const autoFocusRef = useAutoFocus();
|
||||
const router = useRouter();
|
||||
@@ -52,7 +52,7 @@ function CreateCategory({ categoriesCount }: { categoriesCount: number }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-category-create">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Créer une catégorie"
|
||||
errorMessage={error}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Category } from "types";
|
||||
import { HandleAxiosError } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import styles from "styles/create.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function EditCategory({ category }: { category: Category }) {
|
||||
const autoFocusRef = useAutoFocus();
|
||||
@@ -52,7 +52,7 @@ function EditCategory({ category }: { category: Category }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-category-edit">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Modifier une catégorie"
|
||||
errorMessage={error}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Category } from "types";
|
||||
import { HandleAxiosError } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import styles from "styles/create.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function RemoveCategory({ category }: { category: Category }) {
|
||||
const router = useRouter();
|
||||
@@ -51,7 +51,7 @@ function RemoveCategory({ category }: { category: Category }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-category-remove">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Supprimer une catégorie"
|
||||
categoryId={category.id.toString()}
|
||||
|
||||
@@ -3,18 +3,23 @@ import { useRouter } from "next/router";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
import BlockWrapper from "components/BlockWrapper/BlockWrapper";
|
||||
import ButtonLink from "components/ButtonLink";
|
||||
import Links from "components/Links/Links";
|
||||
import Modal from "components/Modal/Modal";
|
||||
import PageTransition from "components/PageTransition";
|
||||
import SearchModal from "components/SearchModal/SearchModal";
|
||||
import Categories from "components/SideMenu/Categories/Categories";
|
||||
import SideMenu from "components/SideMenu/SideMenu";
|
||||
import UserCard from "components/SideMenu/UserCard/UserCard";
|
||||
|
||||
import * as Keys from "constants/keys";
|
||||
import PATHS from "constants/paths";
|
||||
import { useMediaQuery } from "hooks/useMediaQuery";
|
||||
import useModal from "hooks/useModal";
|
||||
import { Category, Link, SearchItem } from "types";
|
||||
|
||||
import getUserCategories from "lib/category/getUserCategories";
|
||||
import getUser from "lib/user/getUser";
|
||||
import { Category, Link, SearchItem } from "types";
|
||||
import { pushStateVanilla } from "utils/link";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
@@ -25,7 +30,10 @@ interface HomePageProps {
|
||||
|
||||
function Home(props: HomePageProps) {
|
||||
const router = useRouter();
|
||||
const modal = useModal();
|
||||
const searchModal = useModal();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const mobileModal = useModal();
|
||||
|
||||
const [categories, setCategories] = useState<Category[]>(props.categories);
|
||||
const [categoryActive, setCategoryActive] = useState<Category | null>(
|
||||
@@ -99,18 +107,20 @@ function Home(props: HomePageProps) {
|
||||
const handleSelectCategory = (category: Category) => {
|
||||
setCategoryActive(category);
|
||||
pushStateVanilla(`${PATHS.HOME}?categoryId=${category.id}`);
|
||||
mobileModal.close();
|
||||
};
|
||||
|
||||
const areHokeysEnabled = { enabled: !searchModal.isShowing };
|
||||
useHotkeys(
|
||||
Keys.OPEN_SEARCH_KEY,
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
modal.open();
|
||||
searchModal.open();
|
||||
},
|
||||
{ enabled: !modal.isShowing }
|
||||
areHokeysEnabled
|
||||
);
|
||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, modal.close, {
|
||||
enabled: modal.isShowing,
|
||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, searchModal.close, {
|
||||
enabled: searchModal.isShowing,
|
||||
enableOnFormTags: ["INPUT"],
|
||||
});
|
||||
|
||||
@@ -119,66 +129,63 @@ function Home(props: HomePageProps) {
|
||||
() => {
|
||||
router.push(`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`);
|
||||
},
|
||||
{
|
||||
enabled: !modal.isShowing,
|
||||
}
|
||||
areHokeysEnabled
|
||||
);
|
||||
useHotkeys(
|
||||
Keys.OPEN_CREATE_CATEGORY_KEY,
|
||||
() => {
|
||||
router.push("/category/create");
|
||||
},
|
||||
{
|
||||
enabled: !modal.isShowing,
|
||||
}
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
Keys.ARROW_UP,
|
||||
() => {
|
||||
const currentCategoryIndex = categories.findIndex(
|
||||
({ id }) => id === categoryActive.id
|
||||
);
|
||||
if (currentCategoryIndex === -1 || currentCategoryIndex === 0) return;
|
||||
|
||||
handleSelectCategory(categories[currentCategoryIndex - 1]);
|
||||
},
|
||||
{ enabled: !modal.isShowing }
|
||||
);
|
||||
useHotkeys(
|
||||
Keys.ARROW_DOWN,
|
||||
() => {
|
||||
const currentCategoryIndex = categories.findIndex(
|
||||
({ id }) => id === categoryActive.id
|
||||
);
|
||||
if (
|
||||
currentCategoryIndex === -1 ||
|
||||
currentCategoryIndex === categories.length - 1
|
||||
)
|
||||
return;
|
||||
|
||||
handleSelectCategory(categories[currentCategoryIndex + 1]);
|
||||
},
|
||||
{ enabled: !modal.isShowing }
|
||||
areHokeysEnabled
|
||||
);
|
||||
|
||||
return (
|
||||
<PageTransition className="App">
|
||||
<SideMenu
|
||||
categories={categories}
|
||||
favorites={favorites}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={modal.open}
|
||||
{isMobile ? (
|
||||
<>
|
||||
<UserCard />
|
||||
<AnimatePresence>
|
||||
{mobileModal.isShowing && (
|
||||
<Modal close={mobileModal.close}>
|
||||
<BlockWrapper style={{ minHeight: "0", flex: "1" }}>
|
||||
<ButtonLink href={PATHS.CATEGORY.CREATE}>
|
||||
Créer categorie
|
||||
</ButtonLink>
|
||||
<Categories
|
||||
categories={categories}
|
||||
categoryActive={categoryActive}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
/>
|
||||
</BlockWrapper>
|
||||
</Modal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<SideMenu
|
||||
categories={categories}
|
||||
favorites={favorites}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={searchModal.open}
|
||||
isModalShowing={searchModal.isShowing}
|
||||
/>
|
||||
)}
|
||||
<Links
|
||||
category={categoryActive}
|
||||
toggleFavorite={toggleFavorite}
|
||||
isMobile={isMobile}
|
||||
openMobileModal={mobileModal.open}
|
||||
openSearchModal={searchModal.open}
|
||||
/>
|
||||
<Links category={categoryActive} toggleFavorite={toggleFavorite} />
|
||||
<AnimatePresence>
|
||||
{modal.isShowing && (
|
||||
{searchModal.isShowing && (
|
||||
<SearchModal
|
||||
close={modal.close}
|
||||
close={searchModal.close}
|
||||
categories={categories}
|
||||
items={itemsSearch}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
noHeader={!isMobile}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Category, Link } from "types";
|
||||
import { HandleAxiosError, IsValidURL } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import styles from "styles/create.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function CreateLink({ categories }: { categories: Category[] }) {
|
||||
const autoFocusRef = useAutoFocus();
|
||||
@@ -64,7 +64,7 @@ function CreateLink({ categories }: { categories: Category[] }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-link-create">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Créer un lien"
|
||||
categoryId={categoryIdQuery}
|
||||
|
||||
@@ -9,17 +9,16 @@ import PageTransition from "components/PageTransition";
|
||||
import Selector from "components/Selector";
|
||||
import TextBox from "components/TextBox";
|
||||
|
||||
import PATHS from "constants/paths";
|
||||
import useAutoFocus from "hooks/useAutoFocus";
|
||||
import getUserCategories from "lib/category/getUserCategories";
|
||||
import getUserLink from "lib/link/getUserLink";
|
||||
import getUser from "lib/user/getUser";
|
||||
import { Category, Link } from "types";
|
||||
import { HandleAxiosError, IsValidURL } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import getUserCategories from "lib/category/getUserCategories";
|
||||
import getUserLink from "lib/link/getUserLink";
|
||||
|
||||
import PATHS from "constants/paths";
|
||||
import getUser from "lib/user/getUser";
|
||||
import styles from "styles/create.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function EditLink({
|
||||
link,
|
||||
@@ -85,7 +84,7 @@ function EditLink({
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-link-edit">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Modifier un lien"
|
||||
errorMessage={error}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Link } from "types";
|
||||
import { HandleAxiosError } from "utils/front";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import styles from "styles/create.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
function RemoveLink({ link }: { link: Link }) {
|
||||
const router = useRouter();
|
||||
@@ -46,7 +46,7 @@ function RemoveLink({ link }: { link: Link }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTransition className="page-link-remove">
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<FormLayout
|
||||
title="Supprimer un lien"
|
||||
categoryId={link.category.id.toString()}
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
import { Provider } from "next-auth/providers";
|
||||
import { getProviders, signIn } from "next-auth/react";
|
||||
import { NextSeo } from "next-seo";
|
||||
import Link from "next/link";
|
||||
|
||||
import MessageManager from "components/MessageManager/MessageManager";
|
||||
import PageTransition from "components/PageTransition";
|
||||
|
||||
import PATHS from "constants/paths";
|
||||
import getUser from "lib/user/getUser";
|
||||
import { getSession } from "utils/session";
|
||||
|
||||
import getUser from "lib/user/getUser";
|
||||
import styles from "styles/login.module.scss";
|
||||
import styles from "styles/form.module.scss";
|
||||
|
||||
interface SignInProps {
|
||||
providers: Provider[];
|
||||
}
|
||||
export default function SignIn({ providers }: SignInProps) {
|
||||
return (
|
||||
<>
|
||||
<PageTransition className={styles["form-container"]}>
|
||||
<NextSeo title="Authentification" />
|
||||
<div className="App">
|
||||
<div className={styles["wrapper"]}>
|
||||
<h2>Se connecter</h2>
|
||||
<MessageManager />
|
||||
<div className={styles["providers"]}>
|
||||
{Object.values(providers).map(({ name, id }) => (
|
||||
<button
|
||||
onClick={() => signIn(id, { callbackUrl: PATHS.HOME })}
|
||||
key={id}
|
||||
>
|
||||
Continuer avec {name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Link href={PATHS.HOME}>← Revenir à l'accueil</Link>
|
||||
</div>
|
||||
<h2>Se connecter</h2>
|
||||
<MessageManager info="Authentification requise pour utiliser ce service" />
|
||||
<div className={styles["providers"]}>
|
||||
{Object.values(providers).map(({ name, id }) => (
|
||||
<button
|
||||
onClick={() => signIn(id, { callbackUrl: PATHS.HOME })}
|
||||
key={id}
|
||||
>
|
||||
Continuer avec {name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
@import "keyframes.scss";
|
||||
@import "colors.scss";
|
||||
|
||||
.create-app {
|
||||
.form-container {
|
||||
height: fit-content;
|
||||
width: 680px;
|
||||
margin-top: 150px;
|
||||
width: 768px;
|
||||
margin-top: 10em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
gap: 0.75em;
|
||||
|
||||
& h2 {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
& form {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
gap: 0.5em;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& .input-field {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
gap: 0.25em;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.create-app {
|
||||
@media (max-width: 768px) {
|
||||
.form-container {
|
||||
width: 100%;
|
||||
margin-top: 5em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
color: $black;
|
||||
background-color: $light-grey;
|
||||
font-family: "Poppins", sans-serif;
|
||||
@@ -198,3 +198,9 @@ kbd {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.App {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user