diff --git a/src/App.tsx b/src/App.tsx index b61bb3bbf6d150b6c0f14073da932c9ce76d4c81..75d5b124e039084cef263d1301a882e0cbb744a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,53 +3,33 @@ import React from 'react'; import { BrowserRouter as Router, Routes, - Route, - Navigate, - useLocation + Route } from "react-router-dom"; -import Dashboard from './pages/dashboard'; +import Sell from './pages/sell'; import Login from './pages/login'; +import Main from './components/layout/main'; -import { useAuth } from "./auth/useAuth"; import { AuthProvider } from './auth/AuthProvider'; export default function App() { - // DEV: auto logged in - return ( <Router> <div> <AuthProvider> <Routes> - <Route path="/" element={<h1>Welcome!</h1>} /> - <Route path="/login" element={<Login/>} /> - <Route - path="/dashboard" - element={ - <RequireAuth> - <Dashboard /> - </RequireAuth> - } - /> + <Route index element={<h1>Welcome!</h1>} /> + <Route path="login" element={<Login/>} /> + <Route path="dashboard" element={<Main />}> + <Route index element={<Sell />} /> + <Route path="sell" element={<h1>Sell...</h1>} /> + <Route path="stock" element={<h1>Stock...</h1>} /> + <Route path="settings" element={<h1>Settings...</h1>} /> + </Route> + <Route path="*" element={<h1>No match !</h1>} /> </Routes> </AuthProvider> </div> </Router> ); } - -function RequireAuth({ children }: { children: JSX.Element }) { - let auth = useAuth(); - let location = useLocation(); - - if (!auth.user) { - // Redirect them to the /login page, but save the current location they were - // trying to go to when they were redirected. This allows us to send them - // along to that page after they login, which is a nicer user experience - // than dropping them off on the home page. - return <Navigate to="/login" state={{ from: location }} />; - } - - return children; -} \ No newline at end of file diff --git a/src/auth/RequireAuth.tsx b/src/auth/RequireAuth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d75b98d4a9d9be5baa31a4392320173afb183ddb --- /dev/null +++ b/src/auth/RequireAuth.tsx @@ -0,0 +1,23 @@ +import { + Navigate, + useLocation +} from "react-router-dom"; + +import { useAuth } from "./useAuth"; + +function RequireAuth({ children }: { children: JSX.Element }) { + let auth = useAuth(); + let location = useLocation(); + + if (!auth.user) { + // Redirect them to the /login page, but save the current location they were + // trying to go to when they were redirected. This allows us to send them + // along to that page after they login, which is a nicer user experience + // than dropping them off on the home page. + return <Navigate to="/login" state={{ from: location }} />; + } + + return children; +} + +export { RequireAuth }; \ No newline at end of file diff --git a/src/components/core/navbar.tsx b/src/components/core/navbar.tsx index 579b72046b94f6f2224c41e6ea7f0c2c3941804f..d10fe9e581413567d41bdd7d656e50f4626a3961 100644 --- a/src/components/core/navbar.tsx +++ b/src/components/core/navbar.tsx @@ -8,11 +8,13 @@ import { XIcon } from '@heroicons/react/outline'; +import { Link } from 'react-router-dom'; + const navigation = [ - { name: 'Accueil', href: '/', icon: HomeIcon }, - { name: 'Vendre', href: '/sell', icon: CurrencyEuroIcon }, - { name: 'Gestion des stocks', href: '/stock', icon: ShoppingCartIcon }, - { name: 'Réglages', href: '/settings', icon: CogIcon }, + { name: 'Accueil', href: '/dashboard', icon: HomeIcon }, + { name: 'Vendre', href: '/dashboard/sell', icon: CurrencyEuroIcon }, + { name: 'Gestion des stocks', href: '/dashboard/stock', icon: ShoppingCartIcon }, + { name: 'Réglages', href: '/dashboard/settings', icon: CogIcon }, ]; function classNames(...classes: unknown[]) { @@ -75,9 +77,9 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, path }) => { <div className="mt-5 flex-1 h-0 overflow-y-auto"> <nav className="px-2 space-y-1"> {navigation.map((item) => ( - <a + <Link key={item.name} - href={item.href} + to={item.href} className={classNames( item.href === path ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white', 'group flex items-center px-2 py-2 text-base font-medium rounded-md' @@ -91,7 +93,7 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, path }) => { aria-hidden="true" /> {item.name} - </a> + </Link> ))} </nav> </div> diff --git a/src/components/layout/main.tsx b/src/components/layout/main.tsx index 5ea4cd4a521f32d20123e686019072cde8fe40bc..74977005b3325c250285f3655aadd06a779ca5e9 100644 --- a/src/components/layout/main.tsx +++ b/src/components/layout/main.tsx @@ -4,30 +4,27 @@ import { UserIcon, MenuAlt2Icon, } from '@heroicons/react/outline'; -import { - SearchIcon -} from '@heroicons/react/solid'; import Navbar from '../core/navbar'; import { useAuth } from '../../auth/useAuth'; -import { useNavigate } from 'react-router-dom'; +import { RequireAuth } from '../../auth/RequireAuth'; +import { Outlet, useNavigate } from 'react-router-dom'; interface LayoutProps { - path: string, - handleSearch: any, title?: string, subTitle?: string, - children: any + children?: any }; function classNames(...classes: unknown[]) { return classes.filter(Boolean).join(' '); } -const Main = ({ path, handleSearch, title, subTitle, children }: LayoutProps) => { +const Main = ({ title, subTitle, children }: LayoutProps) => { let auth = useAuth(); let navigate = useNavigate(); + let path = window.location.pathname; const userNavigation = [ { name: 'Se déconnecter', action: () => {return auth.signout(() => navigate("/"))} }, @@ -35,11 +32,8 @@ const Main = ({ path, handleSearch, title, subTitle, children }: LayoutProps) => const [sidebarOpen, setSidebarOpen] = useState(false); - let handleSearchChange = (e) => { - handleSearch(e.target.value); - } - return ( + <RequireAuth> <div className="h-screen flex overflow-hidden bg-gray-100"> <Navbar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} path={path} /> @@ -54,26 +48,7 @@ const Main = ({ path, handleSearch, title, subTitle, children }: LayoutProps) => <MenuAlt2Icon className="h-6 w-6" aria-hidden="true" /> </button> <div className="flex-1 px-4 flex justify-between"> - <div className="flex-1 flex"> - <form className="w-full flex md:ml-0" action="#" method="GET"> - <label htmlFor="search-field" className="sr-only"> - Rechercher - </label> - <div className="relative w-full text-gray-400 focus-within:text-gray-600"> - <div className="absolute inset-y-0 left-0 flex items-center pointer-events-none"> - <SearchIcon className="h-5 w-5" aria-hidden="true" /> - </div> - <input - id="search-field" - className="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm" - placeholder="Rechercher" - type="search" - name="search" - onChange={(e) => {handleSearchChange(e)}} - /> - </div> - </form> - </div> + <div className="flex-1 flex w-full md:ml-0 items-center"><span className="text-xl font-semibold">Dashboard</span></div> <div className="ml-4 flex items-center md:ml-6"> {/* Profile dropdown */} <Menu as="div" className="ml-3 relative"> @@ -125,11 +100,12 @@ const Main = ({ path, handleSearch, title, subTitle, children }: LayoutProps) => <h1 className="text-xl font-normal text-gray-600">{subTitle}</h1> </div> )} - {children} + <Outlet /> </div> </main> </div> </div> + </RequireAuth> ); }; diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx deleted file mode 100644 index 6b313bd1ee3821d56623d9bfe044c3c08eaccdc5..0000000000000000000000000000000000000000 --- a/src/pages/dashboard.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -import Main from "../components/layout/main"; -import SellListItem from "../components/sellListItem"; - -const products = [ - { - name: "Coca Cola", - category: "Boissons", - price: "0,50€", - quantity: "x50", - }, - { - name: "Fanta", - category: "Boissons", - price: "0,50€", - quantity: "x50", - }, - { - name: "Ice Tea", - category: "Boissons", - price: "0,50€", - quantity: "x50", - }, - { - name: "Eau", - category: "Boissons", - price: "0,50€", - quantity: "x50", - }, - { - name: "Eau gazeuse", - category: "Boissons", - price: "0,50€", - quantity: "x50", - }, - { - name: "Sandwich", - category: "Manger", - price: "2€", - quantity: "x30", - }, - { - name: "Pizza", - category: "Manger", - price: "2€", - quantity: "x20", - }, - { - name: "Baguettine", - category: "Manger", - price: "1,50€", - quantity: "x20", - }, - { - name: "Wrap", - category: "Manger", - price: "1,50€", - quantity: "x10", - }, - { - name: "Donut", - category: "Dessert", - price: "1€", - quantity: "x30", - }, -]; - -const Dashboard = () => { - const path = window.location.pathname; - - let [search, setSearch] = useState(""); - let [productsList, setProductsList] = useState(products); - - useEffect(() => { - // Filter products by name with the search term - setProductsList(products.filter(product => { - return product.name.toLowerCase().includes(search.toLowerCase()); - })) - }, [search]); - - let handleSearch = (search: string) => { - setSearch(search); - } - - return ( - <Main path={path} handleSearch={handleSearch} title="Accueil"> - <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> - {[...Array.from((new Set(productsList.map(item => item.category))))].map((category) => (<div> - <h1 className="text-lg font-medium py-2 pt-4">{category}</h1> - <ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-5"> - {productsList.filter((p) => p.category === category).map((product) => ( - <SellListItem product={product} key={product.name} /> - ))} - </ul> - </div>))} - </div> - </Main> - ); -}; - -export default Dashboard; diff --git a/src/pages/sell.tsx b/src/pages/sell.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b667cbce4d12ee1a885e16ebbc430a3940b14271 --- /dev/null +++ b/src/pages/sell.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; +import { CollectionIcon, EmojiSadIcon } from '@heroicons/react/outline'; + +import SellListItem from "../components/sellListItem"; + +const products = [ + { + name: "Coca Cola", + category: "Boissons", + price: "0,50€", + quantity: "x50", + }, + { + name: "Fanta", + category: "Boissons", + price: "0,50€", + quantity: "x50", + }, + { + name: "Ice Tea", + category: "Boissons", + price: "0,50€", + quantity: "x50", + }, + { + name: "Eau", + category: "Boissons", + price: "0,50€", + quantity: "x50", + }, + { + name: "Eau gazeuse", + category: "Boissons", + price: "0,50€", + quantity: "x50", + }, + { + name: "Sandwich", + category: "Manger", + price: "2€", + quantity: "x30", + }, + { + name: "Pizza", + category: "Manger", + price: "2€", + quantity: "x20", + }, + { + name: "Baguettine", + category: "Manger", + price: "1,50€", + quantity: "x20", + }, + { + name: "Wrap", + category: "Manger", + price: "1,50€", + quantity: "x10", + }, + { + name: "Donut", + category: "Dessert", + price: "1€", + quantity: "x30", + }, +]; + +const Sell = () => { + let [search, setSearch] = useState(""); + let [productsList, setProductsList] = useState(products); + + useEffect(() => { + // Filter products by name with the search term + setProductsList(products.filter(product => { + return product.name.toLowerCase().includes(search.toLowerCase()); + })) + }, [search]); + + return ( + <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> + <div className="pb-5"> + <label htmlFor="search" className="block text-sm font-medium text-gray-700">Recherche</label> + <div className="mt-1 relative flex items-center"> + <input type="text" name="search" id="search" value={search} onChange={(e) => {setSearch(e.target.value)}} className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md" /> + </div> + </div> + {productsList.length > 0 && [...Array.from((new Set(productsList.map(item => item.category))))].map((category) => (<div> + <h1 className="text-lg font-medium py-2 pt-4">{category}</h1> + <ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-5"> + {productsList.filter((p) => p.category === category).map((product) => ( + <SellListItem product={product} key={product.name} /> + ))} + </ul> + </div>))} + {productsList.length === 0 && + <div className="text-center w-full mt-10"> + <EmojiSadIcon className="mx-auto h-12 w-12 text-gray-400"/> + <h3 className="mt-2 text-sm font-medium text-gray-900">Aucun produit trouvé !</h3> + <p className="mt-1 text-sm text-gray-500">Essayez de réduire vos critères de recherche.</p> + <div className="mt-6"> + <button + type="button" + onClick={() => setSearch("")} + className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + > + <CollectionIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" /> + Afficher tout + </button> + </div> + </div>} + </div> + ); +}; + +export default Sell;