Skip to content

Commit

Permalink
feat: add dashboard and orders page
Browse files Browse the repository at this point in the history
  • Loading branch information
= committed Jul 9, 2024
1 parent 8bdae8d commit 6a9a23a
Show file tree
Hide file tree
Showing 33 changed files with 1,548 additions and 161 deletions.
Binary file added public/hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/api/get-managed-restaurant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
import {api} from "@/lib/axios.ts";

export interface GetManagedRestaurantsResponse {
export interface GetManagedRestaurantResponse {
name: string
id: string
createdAt: Date | null
Expand All @@ -18,6 +18,6 @@ export interface GetManagedRestaurantsResponse {
}

export async function getManagedRestaurant() {
const response = await api.get<GetManagedRestaurantsResponse>('/managed-restaurant')
const response = await api.get<GetManagedRestaurantResponse>('/managed-restaurant')
return response.data
}
2 changes: 1 addition & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function App() {
return (
<HelmetProvider>
<ThemeProvider storageKey={"hamburgershop-theme"} defaultTheme={"system"}>
<Helmet titleTemplate={"%s | hamburger.shop"}/>
<Helmet titleTemplate={"%s | Hamburger Shop"}/>
<Toaster richColors/>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router}/>
Expand Down
90 changes: 89 additions & 1 deletion src/components/account-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,93 @@
import {useNavigate} from "react-router-dom";
import {useMutation, useQuery} from "@tanstack/react-query";
import {getManagedRestaurant} from "@/api/get-managed-restaurant.ts";
import {signOut} from "@/api/sign-out.ts";
import {Dialog, DialogTrigger} from "@/components/ui/dialog.tsx";
import {
DropdownMenu,
DropdownMenuContent, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuSeparator,
DropdownMenuTrigger
} from "@/components/ui/dropdown-menu.tsx";
import {Button} from "@/components/ui/button.tsx";
import {Skeleton} from "@/components/ui/skeleton.tsx";
import {Building, ChevronDown, LogOut} from "lucide-react";
import {getProfile} from "@/api/get-profile.ts";
import {StoreProfileDialog} from "@/components/store-profile-dialog.tsx";

export function AccountMenu() {
const navigate = useNavigate()

const {data: profile, isLoading: isLoadingProfile} = useQuery({
queryKey: ["profile"],
queryFn: getProfile,
staleTime: Infinity,
})

const {data: managedRestaurant, isLoading: isLoadingManagedRestaurant} = useQuery({
queryKey: ["managed-restaurant"],
queryFn: getManagedRestaurant,
staleTime: Infinity,
})

const {mutateAsync: signOutFn, isPending: isSigningOUt} = useMutation({
mutationFn: signOut,
onSuccess: () => {
navigate("/sign-in", {replace: true})
}
})

return (
<div>Hello world</div>
<Dialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant={"outline"}
className={"flex items-center gap-2 select-none"}
>
{isLoadingManagedRestaurant ? (
<Skeleton className={"h-4 w-40"}/>
) : (
managedRestaurant?.name
)}
<ChevronDown className={"h-4 w-4"}/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align={"end"} className={"w-56"}>
<DropdownMenuLabel className={"flex flex-col"}>
{isLoadingProfile ? (
<div className={"space-y-1.5"}>
<Skeleton className={"h-4 w-32"}/>
<Skeleton className={"h-3 w-24"}/>
</div>
) : (
<>
<span>{profile?.name}</span>
<span className={"text-xs font-normal text-muted-foreground"}>
{profile?.email}
</span>
</>
)}
</DropdownMenuLabel>

<DropdownMenuSeparator/>

<DialogTrigger asChild>
<DropdownMenuItem>
<Building className={"w-4 h-4 mr-2"}/>
<span>Perfil da loja</span>
</DropdownMenuItem>
</DialogTrigger>

<DropdownMenuItem asChild className={"text-rose-500 dark:text-rose-400"} disabled={isSigningOUt}>
<button className={"w-full"} onClick={() => signOutFn()}>
<LogOut className={"w-4 h-4 mr-2"}/>
<span>Sair da conta</span>
</button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<StoreProfileDialog/>
</Dialog>
)
}
5 changes: 3 additions & 2 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {Home, UtensilsCrossed} from "lucide-react";
import {FaHamburger} from "react-icons/fa";
import {Separator} from "@/components/ui/separator.tsx";
import {NavLink} from "@/components/nav-link.tsx";
import {Home, UtensilsCrossed} from "lucide-react";
import {ThemeToggle} from "@/components/theme/theme-toggle.tsx";
import {AccountMenu} from "@/components/account-menu.tsx";

export function Header() {
return (
<div className={"border-b"}>
<div className={"flex h-16 items-center gap-6 px-6"}>
<FaHamburger className={"h-6 w-6"}/>
<FaHamburger className={"h-6 w-6 text-orange-400"}/>

<Separator orientation={"vertical"} className={"h-6"}/>

Expand Down
14 changes: 12 additions & 2 deletions src/components/nav-link.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
export function NavLink() {
import {Link, LinkProps, useLocation} from "react-router-dom";

export type NavLinksProps = LinkProps

export function NavLink(props: Readonly<NavLinksProps>) {
const {pathname} = useLocation()

return (
<div>Hello world</div>
<Link
data-current={pathname === props.to}
className={"flex items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground data-[current=true]:text-foreground"}
{...props}
/>
)
}
29 changes: 27 additions & 2 deletions src/components/order-status.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
export function OrderStatus() {
export type OrderStatus =
| "pending"
| "canceled"
| "processing"
| "delivering"
| "delivered"

interface OrderStatusProps {
status: OrderStatus
}

const orderStatusMap: Record<OrderStatus, string> = {
pending: "Pendente",
canceled: "Cancelado",
delivered: "Entregue",
delivering: "Em entrega",
processing: "Em preparo",
}

export function OrderStatus({status}: OrderStatusProps) {
return (
<div>Hello world</div>
<div className={"flex items-center gap-2"}>
{status === "pending" && (
<span data-testid={"badge"} className={"h-2 w-2 rounded-full bg-slate-400"}></span>
)}

<span className={"font-medium text-muted-foreground"}>{orderStatusMap[status]}</span>
</div>
)
}
47 changes: 45 additions & 2 deletions src/components/pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
export function Pagination() {
import {Button} from "@/components/ui/button.tsx";
import {ChevronLeft, ChevronRight} from "lucide-react";

export interface PaginationProps {
pageIndex: number
totalCount: number
perPage: number
onPageChange: (pageIndex: number) => Promise<void> | void
}

export function Pagination({pageIndex, perPage, totalCount, onPageChange}:PaginationProps) {
const pages: number = Math.ceil(totalCount / perPage) || 1
return (
<div>Hello world</div>
<div className={"flex items-center justify-between"}>
<span className={"text-sm text-muted-foreground"}>
Total de {totalCount} item(s)
</span>

<div className={"flex items-center gap-6 lg:gap-8"}>
<div className={"text-sm font-medium"}>
Página {pageIndex + 1} de {pages}
</div>
<div className={"flex items-center gap-2"}>

<Button onClick={() => onPageChange(0)} variant={"outline"} className={"h-8 w-8 p-0"} disabled={pageIndex === 0}>
<ChevronLeft className={"h-4 w-4"}/>
<span className={"sr-only"}>Primeira página</span>
</Button>

<Button onClick={() => onPageChange(pageIndex - 1)} variant={"outline"} className={"h-8 w-8 p-0"} disabled={pageIndex === 0}>
<ChevronLeft className={"h-4 w-4"}/>
<span className={"sr-only"}>Página anterior</span>
</Button>

<Button onClick={() => onPageChange(pageIndex + 1)} variant={"outline"} className={"h-8 w-8 p-0"} disabled={pages <= pageIndex + 1}>
<ChevronRight className={"h-4 w-4"}/>
<span className={"sr-only"}>Próxima página</span>
</Button>

<Button onClick={() => onPageChange(pages - 1)} variant={"outline"} className={"h-8 w-8 p-0"} disabled={pages <= pageIndex + 1}>
<ChevronRight className={"h-4 w-4"}/>
<span className={"sr-only"}>Última página</span>
</Button>
</div>
</div>
</div>
)
}
110 changes: 108 additions & 2 deletions src/components/store-profile-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,111 @@
export function storeProfileDialog() {
import {z} from "zod";
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
import {getManagedRestaurant, GetManagedRestaurantResponse} from "@/api/get-managed-restaurant.ts";
import {useForm} from "react-hook-form";
import {zodResolver} from "@hookform/resolvers/zod";
import {updateProfile} from "@/api/update-profile.ts";
import {toast} from "sonner";
import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from "@/components/ui/dialog.tsx";
import {Label} from "@/components/ui/label.tsx";
import {Input} from "@/components/ui/input.tsx";
import {Textarea} from "@/components/ui/textarea.tsx";
import {Button} from "@/components/ui/button.tsx";

const storeProfileSchema = z.object({
name: z.string().min(1),
description: z.string().nullable(),
})

type StoreProfileSchema = z.infer<typeof storeProfileSchema>

export function StoreProfileDialog() {
const queryClient = useQueryClient()

const {data: managedRestaurant} = useQuery({
queryKey: ["managed-restaurant"],
queryFn: getManagedRestaurant,
staleTime: Infinity,
})

const {register, handleSubmit, formState: {isSubmitting}} = useForm<StoreProfileSchema>({
resolver: zodResolver(storeProfileSchema), values: {
name: managedRestaurant?.name ?? '', description: managedRestaurant?.description ?? '',
}
})

function updateManagedRestaurantCache(
{name, description}: StoreProfileSchema) {
const cached = queryClient.getQueryData<GetManagedRestaurantResponse>(["managed-restaurant"])
if (cached) {
queryClient.setQueryData<GetManagedRestaurantResponse>(["managed-restaurant"], {
...cached,
name,
description,
})
}
return {cached}
}


const {mutateAsync: updateProfileFn} = useMutation({
mutationFn: updateProfile,
onMutate({name, description}) {
const {cached} = updateManagedRestaurantCache({name, description})
return {previousRestaurant: cached}
},
onError(_, _var, context) {
if (context?.previousRestaurant) {
updateManagedRestaurantCache(context.previousRestaurant)
}
}
})

async function handleUpdateProfile(data: StoreProfileSchema) {
try {
await updateProfileFn({
name: data.name,
description: data.description,
})
toast.success("Perfil atualizado com sucesso!")
}catch {
toast.error("Falha ao atualizar o perfil, tente novamente!")
}
}

return (
<div>Hello world</div>
<DialogContent>
<DialogHeader>
<DialogTitle>Perfil da loja</DialogTitle>
<DialogDescription>Atualize as informações do seu restaurante visíveis ao seu cliente</DialogDescription>
</DialogHeader>

<form onSubmit={handleSubmit(handleUpdateProfile)}>
<div className={"space-y-4 py-4"}>
<div className={"grid grid-cols-4 items-center gap-4"}>
<Label className={"text-right"} htmlFor={"name"}>Nome</Label>
<Input className={"col-span-3"} id={"name"} {...register("name")}/>
</div>

<div className={"grid grid-cols-4 items-center gap-4"}>
<Label className={"text-right"} htmlFor={"description"}>Descrição</Label>
<Textarea className={"col-span-3"} id={"description"} {...register("description")}/>
</div>
</div>

<DialogFooter>
<DialogClose asChild>
<Button type={"button"} variant={"ghost"}>Cancelar</Button>
</DialogClose>
<Button type={"submit"} variant={"success"} disabled={isSubmitting}>Salvar</Button>
</DialogFooter>
</form>
</DialogContent>
)
}
6 changes: 5 additions & 1 deletion src/components/theme/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export function ThemeProvider({
root.classList.remove("light", "dark")

if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme:dark)").matches ? "dark" : "light"
const systemTheme = window.matchMedia("(prefers-color-scheme:dark)")
.matches
? "dark"
: "light"

root.classList.add(systemTheme)
return
Expand All @@ -51,6 +54,7 @@ export function ThemeProvider({
setTheme(theme)
},
}

return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
Expand Down
Loading

0 comments on commit 6a9a23a

Please sign in to comment.