Skip to content

Commit

Permalink
feat(GIST-82): add paginate on gists
Browse files Browse the repository at this point in the history
* feat(paginate): working on paginate on gists

* feat(GIST-80): added correct pagination numbers

* feat(GIST-80): added url as source of truth for paginate

* feat(GIST-80): fixed edge case on paginate
  • Loading branch information
Courtcircuits authored Nov 8, 2024
1 parent f494ada commit 07ca56f
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 270 deletions.
39 changes: 24 additions & 15 deletions src/app/(gistLayout)/mygist/page-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { MyGistListFeature } from '@/components/logic/mygist-list-logic'
import { SidebarTrigger } from '@/components/shadcn/sidebar'
import MenuButton from '@/components/ui/menu-button'
import { PaginationComponent } from '@/components/ui/pagination'
import TooltipShortcut, { TooltipShortcutTrigger } from '@/components/ui/tooltip-shortcut'
import { TornadoIcon } from 'lucide-react'
import { PaginationProvider } from "@/components/contexts/pagination";
import { MyGistListFeature } from "@/components/logic/mygist-list-logic";
import { SidebarTrigger } from "@/components/shadcn/sidebar";
import MenuButton from "@/components/ui/menu-button";
import { PaginationComponent } from "@/components/ui/pagination";
import TooltipShortcut, {
TooltipShortcutTrigger,
} from "@/components/ui/tooltip-shortcut";
import { TornadoIcon } from "lucide-react";

interface MyGistPageProps {}

Expand All @@ -12,24 +15,30 @@ export default function MyGistsPage({}: MyGistPageProps) {
<div className="flex flex-col flex-grow h-full p-2">
<div className="py-4 px-6 flex flex-row justify-between items-center rounded-t-lg border-border border-l border-t border-r">
<div className="flex flex-row gap-6 items-center h-full">
<SidebarTrigger className='w-4 h-4' />
<SidebarTrigger className="w-4 h-4" />
<div className="w-[1px] h-2/3 bg-border"></div>
<span>My Gists</span>
</div>
<TooltipShortcut tooltip="Sort your gists" shortcuts={['S']}>
<TooltipShortcut tooltip="Sort your gists" shortcuts={["S"]}>
<TooltipShortcutTrigger>
<MenuButton icon={<TornadoIcon className="w-4 h-4" />} variant={'menu'}>
<MenuButton
icon={<TornadoIcon className="w-4 h-4" />}
variant={"menu"}
>
<span>Sort by</span>
</MenuButton>
</TooltipShortcutTrigger>
</TooltipShortcut>
</div>

<div className="h-[1px] bg-border"></div>
<MyGistListFeature />
<div className="h-[1px] bg-border"></div>
<div className="p-4 rounded-b-lg border-border border-l border-b border-r">
<PaginationComponent />
</div>
<PaginationProvider fromUrl>
<MyGistListFeature />
<div className="h-[1px] bg-border"></div>
<div className="p-4">
<PaginationComponent />
</div>
</PaginationProvider>
</div>
)
);
}
95 changes: 95 additions & 0 deletions src/components/contexts/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
createContext,
ReactNode,
useCallback,
useEffect,
useState,
} from "react";

interface PaginationContextContent {
offset: number;
limit: number;
nb_pages?: number;
setOffset: (offset: number) => void;
setLimit: (limit: number) => void;
setNbPages: (nb_pages: number) => void;
}

const PaginationInitialState = {
offset: 0,
limit: 9,
nb_pages: 0,
setOffset: (offset: number) => {},
setLimit: (limit: number) => {},
setNbPages: (nb_pages: number) => {},
};

export const PaginationContext = createContext<PaginationContextContent>(
PaginationInitialState,
);

export function PaginationProvider({
children,
fromUrl,
}: {
children: ReactNode;
fromUrl: boolean;
}) {
const [offset, setOffset] = useState(PaginationInitialState.offset);
const [limit, setLimit] = useState(PaginationInitialState.limit);
const [nb_pages, setNbPages] = useState(PaginationInitialState.nb_pages);
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();

const checkOffset = useCallback(
(offset: number) => {
if (offset >= 0 && offset <= nb_pages * limit) {
return true;
}
return false;
},
[nb_pages, limit],
);

const setOffsetHandler = useCallback(
(offset: number) => {
if (checkOffset(offset)) {
if (fromUrl) {
const page = Math.floor(offset / limit) + 1;
router.push(`${pathname}?page=${page}`);
}
setOffset(offset);
}
},
[checkOffset, fromUrl, limit, pathname, router],
);

useEffect(() => {
if (!fromUrl) return;
if (searchParams.has("page")) {
const page = parseInt(searchParams.get("page") as string);
const offset = (page - 1) * limit;
console.log("offset", offset);
if (!checkOffset(offset)) return;
setOffset(offset);
}
}, [searchParams, fromUrl, setOffset, limit, setOffsetHandler, checkOffset]);

return (
<PaginationContext.Provider
value={{
offset,
setOffset: setOffsetHandler,
limit,
setLimit,
nb_pages,
setNbPages,
}}
>
{children}
</PaginationContext.Provider>
);
}
14 changes: 13 additions & 1 deletion src/components/logic/mygist-list-logic.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
"use client";

import { useContext, useEffect } from "react";
import MyGistList from "../ui/mygist-list";
import { useDeleteGist, useGists } from "@/lib/queries/gists.queries";
import { PaginationContext } from "../contexts/pagination";

export function MyGistListFeature() {
const { data } = useGists();
const { offset, limit, setNbPages } = useContext(PaginationContext);
const { data, nb_pages } = useGists({
limit,
offset,
});

useEffect(() => {
setNbPages(nb_pages || 0);
}, [nb_pages, setNbPages]);

const { mutate: deleteGist } = useDeleteGist({
onSuccess: (id) => {
console.log(`Deleting gist with ID: ${id}`);
},
});

const handleDeleteGist = (id: string) => {
deleteGist(id);
};
Expand Down
79 changes: 47 additions & 32 deletions src/components/shadcn/button.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,63 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";

const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-light-background hover:text-foreground',
link: 'text-primary text-base underline-offset-4 hover:underline',
icon: 'text-foreground bg-icon hover:bg-icon/80',
menu: 'text-primary-foreground hover:bg-primary hover:text-primary-foreground',
header: 'hover:bg-primary hover:text-foreground',
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-light-background hover:text-foreground",
disabled:
"hover:bg-light-background hover:text-foreground opacity-50 cursor-not-allowed",
link: "text-primary text-base underline-offset-4 hover:underline",
icon: "text-foreground bg-icon hover:bg-icon/80",
menu: "text-primary-foreground hover:bg-primary hover:text-primary-foreground",
header: "hover:bg-primary hover:text-foreground",
},
size: {
default: 'h-10 px-6 py-3',
'no-padding': 'h-10',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
menu: 'h-10 py-2 px-3',
icon: 'h-10 w-10',
default: "h-10 px-6 py-3",
"no-padding": "h-10",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
menu: "h-10 py-2 px-3",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: 'default',
size: 'default',
variant: "default",
size: "default",
},
}
)
},
);

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
asChild?: boolean
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
})
Button.displayName = 'Button'
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";

export { Button, buttonVariants }
export { Button, buttonVariants };
Loading

0 comments on commit 07ca56f

Please sign in to comment.