Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add createdAt column to products table #42

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Spinner from "components/Spinner";
import AdminLayout from "layout/AdminLayout";
import Layout from "layout/Layout";
import {
Account,
Expand All @@ -14,6 +15,8 @@ import {
Register,
ResetPassword,
} from "pages";
import { AdminProductList } from "pages/admin";
import Dashboard from "pages/admin/Dashboard";
import { Suspense } from "react";
import { Toaster } from "react-hot-toast";
import { Route, Routes } from "react-router-dom";
Expand All @@ -36,6 +39,15 @@ function App() {
<Route path="/cart/success" element={<Confirmation />} />
<Route path="/orders" element={<Orders />} />
<Route path="/orders/:id/" element={<OrderDetails />} />
<Route path="/admin" element={<AdminLayout />}>
<Route index element={<Dashboard />} />
<Route path="/admin/products" element={<AdminProductList />} />
<Route path="/admin/products/:slug" element={<ProductDetails />} />
<Route path="/admin/orders" element={<Orders />} />
<Route path="/admin/orders/:id" element={<OrderDetails />} />
<Route path="/admin/account" element={<Account />} />
<Route path="*" element={<NotFound />} />
</Route>
</Route>

<Route path="/signup" element={<Register />} />
Expand Down
77 changes: 77 additions & 0 deletions client/src/components/admin/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState } from "react";
// import { SidebarContext } from "../context/SidebarContext";
// import {
// SearchIcon,
// MoonIcon,
// SunIcon,
// BellIcon,
// MenuIcon,
// OutlinePersonIcon,
// OutlineCogIcon,
// OutlineLogoutIcon,
// } from "../icons";
import { Avatar, Dropdown, DropdownItem } from "@windmill/react-ui";
import { useSidebar } from "context/SidebarContext";
import { LogOut, Menu, Settings, User } from "react-feather";

function Header() {
const { toggleSidebar } = useSidebar();

const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);

function handleProfileClick() {
setIsProfileMenuOpen(!isProfileMenuOpen);
}

return (
<header className="z-40 py-4 bg-white shadow-bottom dark:bg-gray-800">
<div className="container flex items-center justify-between h-full px-6 mx-auto text-purple-600 dark:text-purple-300">
{/* <!-- Mobile hamburger --> */}
<button
className="p-1 mr-5 -ml-1 rounded-md lg:hidden focus:outline-none focus:shadow-outline-purple"
onClick={toggleSidebar}
aria-label="Menu"
>
<Menu className="w-6 h-6" aria-hidden="true" />
</button>
<ul className="flex items-center flex-shrink-0 space-x-6">
<li className="relative">
<button
className="rounded-full focus:shadow-outline-purple focus:outline-none"
onClick={handleProfileClick}
aria-label="Account"
aria-haspopup="true"
>
<Avatar
className="align-middle"
src="https://images.unsplash.com/photo-1502378735452-bc7d86632805?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=aa3a807e1bbdfd4364d1f449eaa96d82"
alt=""
aria-hidden="true"
/>
</button>
<Dropdown
align="right"
isOpen={isProfileMenuOpen}
onClose={() => setIsProfileMenuOpen(false)}
>
<DropdownItem tag="a" href="#">
<User className="w-4 h-4 mr-3" aria-hidden="true" />
<span>Profile</span>
</DropdownItem>
<DropdownItem tag="a" href="#">
<Settings className="w-4 h-4 mr-3" aria-hidden="true" />
<span>Settings</span>
</DropdownItem>
<DropdownItem onClick={() => alert("Log out!")}>
<LogOut className="w-4 h-4 mr-3" aria-hidden="true" />
<span>Log out</span>
</DropdownItem>
</Dropdown>
</li>
</ul>
</div>
</header>
);
}

export default Header;
11 changes: 11 additions & 0 deletions client/src/components/admin/sidebar/DesktopSidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SidebarContent from "./MainContent";

function DesktopSidebar(props) {
return (
<aside className="z-30 flex-shrink-0 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 lg:block">
<SidebarContent />
</aside>
);
}

export default DesktopSidebar;
69 changes: 69 additions & 0 deletions client/src/components/admin/sidebar/MainContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Button } from "@windmill/react-ui";
import { Home, Package, ShoppingCart } from "react-feather";
import { NavLink, useLocation } from "react-router-dom";
import SidebarSubmenu from "./SidebarSubmenu";

const routes = [
{
path: "/admin",
icon: Home,
name: "Dashboard",
},
{
path: "/admin/products",
icon: Package,
name: "Products",
},
{
path: "/admin/orders",
icon: ShoppingCart,
name: "Orders",
},
];

function SidebarContent() {
const location = useLocation();

return (
<div className="py-4 text-gray-500 dark:text-gray-400">
<a className="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="#">
PERN Store
</a>
<ul className="mt-6 flex-[2_2_0%]">
{routes.map((route) =>
route.routes ? (
<SidebarSubmenu route={route} key={route.name} />
) : (
<li className="relative px-6 py-3" key={route.name}>
<NavLink
exact
to={route.path}
className="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
activeClassName="text-gray-800 dark:text-gray-100"
>
{location.pathname === route.path && (
<span
className="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
aria-hidden="true"
></span>
)}
<route.icon className="w-5 h-5" aria-hidden="true" />
<span className="ml-4">{route.name}</span>
</NavLink>
</li>
)
)}
</ul>
<div className="px-6 my-6">
<Button>
Create account
<span className="ml-2" aria-hidden="true">
+
</span>
</Button>
</div>
</div>
);
}

export default SidebarContent;
40 changes: 40 additions & 0 deletions client/src/components/admin/sidebar/MobileSidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Backdrop, Transition } from "@windmill/react-ui";
import SidebarContent from "./MainContent";

import { useSidebar } from "context/SidebarContext";

function MobileSidebar() {
const { isSidebarOpen, closeSidebar } = useSidebar();

return (
<Transition show={isSidebarOpen}>
<>
<Transition
enter="transition ease-in-out duration-150"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in-out duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Backdrop onClick={closeSidebar} />
</Transition>

<Transition
enter="transition ease-in-out duration-150"
enterFrom="opacity-0 transform -translate-x-20"
enterTo="opacity-100"
leave="transition ease-in-out duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0 transform -translate-x-20"
>
<aside className="fixed inset-y-0 z-50 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 lg:hidden">
<SidebarContent />
</aside>
</Transition>
</>
</Transition>
);
}

export default MobileSidebar;
61 changes: 61 additions & 0 deletions client/src/components/admin/sidebar/SidebarSubMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState } from "react";
import { Link } from "react-router-dom";
// import { Dropdown } from "../../icons";
import { Transition } from "@windmill/react-ui";
import { ChevronDown } from "react-feather";

// function Icon({ icon, ...props }) {
// const Icon = Icons[icon];
// return <Icon {...props} />;
// }

function SidebarSubmenu({ route }) {
const [isDropdownMenuOpen, setIsDropdownMenuOpen] = useState(false);

function handleDropdownMenuClick() {
setIsDropdownMenuOpen(!isDropdownMenuOpen);
}

return (
<li className="relative px-6 py-3" key={route.name}>
<button
className="inline-flex items-center justify-between w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
onClick={handleDropdownMenuClick}
aria-haspopup="true"
>
<span className="inline-flex items-center">
{/* <Icon className="w-5 h-5" aria-hidden="true" icon={route.icon} /> */}
<span className="ml-4">{route.name}</span>
</span>
<ChevronDown className="w-4 h-4" aria-hidden="true" />
</button>
<Transition
show={isDropdownMenuOpen}
enter="transition-all ease-in-out duration-300"
enterFrom="opacity-25 max-h-0"
enterTo="opacity-100 max-h-xl"
leave="transition-all ease-in-out duration-300"
leaveFrom="opacity-100 max-h-xl"
leaveTo="opacity-0 max-h-0"
>
<ul
className="p-2 mt-2 space-y-2 overflow-hidden text-sm font-medium text-gray-500 rounded-md shadow-inner bg-gray-50 dark:text-gray-400 dark:bg-gray-900"
aria-label="submenu"
>
{route.routes.map((r) => (
<li
className="px-2 py-1 transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
key={r.name}
>
<Link className="w-full" to={r.path}>
{r.name}
</Link>
</li>
))}
</ul>
</Transition>
</li>
);
}

export default SidebarSubmenu;
13 changes: 13 additions & 0 deletions client/src/components/admin/sidebar/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import DesktopSidebar from "./DesktopSidebar";
import MobileSidebar from "./MobileSidebar";

function Sidebar() {
return (
<>
<DesktopSidebar />
<MobileSidebar />
</>
);
}

export default Sidebar;
14 changes: 10 additions & 4 deletions client/src/context/ProductContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ const ProductProvider = ({ children }) => {

useEffect(() => {
setIsLoading(true);
productService.getProducts(page).then((response) => {
setProducts(response.data);
setIsLoading(false);
});
productService
.getProducts(page)
.then((response) => {
setProducts(response.data);
setIsLoading(false);
})
.catch((error) => {
console.log(error);
setIsLoading(false);
});
}, [page]);

return (
Expand Down
36 changes: 36 additions & 0 deletions client/src/context/SidebarContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useMemo, useState } from "react";

export const SidebarContext = React.createContext();

export const SidebarProvider = ({ children }) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);

function toggleSidebar() {
setIsSidebarOpen(!isSidebarOpen);
}

function closeSidebar() {
setIsSidebarOpen(false);
}

const value = useMemo(
() => ({
isSidebarOpen,
toggleSidebar,
closeSidebar,
}),
[isSidebarOpen]
);

return <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>;
};

export const useSidebar = () => {
const context = React.useContext(SidebarContext);

if (context === undefined) {
throw new Error("useSidebar must be used within a SidebarProvider");
}

return context;
};
Empty file.
31 changes: 31 additions & 0 deletions client/src/hooks/useAxios.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import API from "api/axios.config";
import { useEffect, useState } from "react";

const useAxios = (axiosParams, deps = []) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);

const fetchData = async (params) => {
try {
const result = await API.request(params);
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchData(axiosParams);
}, deps);

return {
response,
loading,
error,
};
};

export default useAxios;
Loading