Skip to content

Commit

Permalink
feat: fixing pagination issues
Browse files Browse the repository at this point in the history
  • Loading branch information
webtaken committed Nov 27, 2023
1 parent f1ee6c9 commit 1cf1668
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 69 deletions.
3 changes: 2 additions & 1 deletion gptube/components/dashboard/button-new-analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import { VideoPreview } from './video-preview'

export function ButtonNewAnalysis() {
const { user } = useAuth()
// eslint-disable-next-line prefer-const
let { handleChange, email, showEmail, url } = useForm({
url: '',
email: user?.email || '',
showEmail: false,
})

const { handleAnalysis, isLoading, dataAnalysis } = useDashboardAnalysis()
const { handleAnalysis, isLoading } = useDashboardAnalysis()

const [debouncedUrl] = useDebounce(url, 500)
const modalAnalysis = useDisclosure()
Expand Down
6 changes: 4 additions & 2 deletions gptube/components/dashboard/card-video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function CardVideo({ snippet, createdAt, videoId }: ModelsYoutubeVideoDas
const imgCard = thumbnails?._default?.url

return (
<article className="flex items-center gap-4 p-4 rounded-md shadow-sm">
<article className="flex items-center gap-4 p-4 rounded-md shadow-sm border">
<section>
{imgCard ? (
<img alt={title} className="object-cover object-center w-32 rounded" src={imgCard} />
Expand All @@ -29,7 +29,9 @@ export function CardVideo({ snippet, createdAt, videoId }: ModelsYoutubeVideoDas
{createdAt ? (
<>
<Dot />
<small>{formatDate(createdAt, 'MMM/DD')}</small>
<small title={formatDate(createdAt, 'DD MMM, YYYY (HH:mm A)')}>
{formatDate(createdAt, 'MMM/DD')}
</small>
</>
) : null}
</div>
Expand Down
35 changes: 22 additions & 13 deletions gptube/components/dashboard/dashboard-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Card, CardBody } from '@nextui-org/card'
import { Pagination, Spinner } from '@nextui-org/react'

import { useVideosAnalyzed } from '@/hooks/use-videos-analyzed'
import { DEFAULT_PAGE_PAGINATION } from '@/constants/general.constants'

import { FilterVideoAnalysis } from './filter-video-analysis'
import { ButtonNewAnalysis } from './button-new-analysis'
Expand All @@ -10,50 +11,58 @@ import { CardVideo } from './card-video'

export function DashboardUI() {
const {
count,
videos,
isLoading: isLoadingVideos,
isError: isErrorVideos,
handleChangePage,
page,
totalPages,
isFetching,
} = useVideosAnalyzed()

return (
<main className="max-w-screen-lg px-6 py-6 mx-auto space-y-6">
<main className="max-w-screen-lg py-6 mx-auto space-y-6">
<header className="flex justify-end">
<ButtonNewAnalysis />
</header>
<section className="flex gap-10">
<aside>
<Card className="bg-white w-80" radius="sm" shadow="sm">
<Card className="bg-white w-72" radius="sm" shadow="sm">
<CardBody>
<FilterVideoAnalysis />
</CardBody>
</Card>
</aside>
<aside className="flex-1">
<Card className="min-h-[66.5dvh]" radius="sm">
<Card radius="sm">
<CardBody className="space-y-6">
<section>
{isLoadingVideos ? (
<div className="flex">
<Spinner className="mx-auto" color="default" />
</div>
) : (
{isLoadingVideos || isFetching ? (
<div className="flex justify-center">
<Spinner color="default" label="Loading videos..." />
</div>
) : (
<div className="px-4">
<p className="text-sm py-2 text-center">
Total videos analyzed are <span className="font-bold">{count || 0}</span>
</p>
<div className="space-y-2">
{!videos || videos.length === 0 ? <NotVideoFound /> : null}
<section className="space-y-4">
{videos?.map(video => <CardVideo key={video.videoId} {...video} />)}
</section>
</div>
)}
</section>
</div>
)}
{videos != null && videos.length > 0 ? (
<div className="flex justify-center">
<Pagination
isCompact
showControls
classNames={{
cursor: 'text-white',
}}
color="success"
initialPage={page}
initialPage={DEFAULT_PAGE_PAGINATION}
isDisabled={isFetching || isErrorVideos}
total={totalPages}
onChange={handleChangePage}
Expand Down
3 changes: 2 additions & 1 deletion gptube/components/dashboard/filter-video-analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ export function FilterVideoAnalysis() {
return (
<div className="flex flex-col gap-4">
<Input
isDisabled
label="Filter videos analyzed"
labelPlacement="outside"
size="lg"
size="md"
startContent={<Search className="w-4 h-4" />}
variant="underlined"
/>
Expand Down
2 changes: 1 addition & 1 deletion gptube/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const KEY_ANALYSIS_LANDING = 'gptube-analysis'
export const KEY_ANALYSIS_LANDING = 'gptube-analysis'
2 changes: 1 addition & 1 deletion gptube/constants/general.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export const DEFAULT_PAGE_PAGINATION = 1

export const DEFAULT_PAGE_SIZE_PAGINATION = 10

export const CONTACT_SUPPORT_X = '@node_srojas1'
export const CONTACT_SUPPORT_X = '@node_srojas1'
66 changes: 33 additions & 33 deletions gptube/hooks/use-dashboard-analysis.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
import type { ModelsYoutubeAnalyzerReqBody } from "@/gptube-api";
import type { FormEvent } from "react";
import type { ModelsYoutubeAnalyzerReqBody } from '@/gptube-api'

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'
import toast from 'react-hot-toast'

import { apiClient } from "@/gptube-api";
import { getAnalysisLandingFromCache } from "@/services/get-analysis-landing-from-cache.service";
import { extractYTVideoID } from "@/utils";

import { videoQueryKeys } from "./video-query-keys";
import { apiClient } from '@/gptube-api'
import {
DEFAULT_PAGE_PAGINATION,
DEFAULT_PAGE_SIZE_PAGINATION,
CONTACT_SUPPORT_X,
} from "@/constants/general.constants";
} from '@/constants/general.constants'

import { videoQueryKeys } from './video-query-keys'

export function useDashboardAnalysis() {
const queryClient = useQueryClient();
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (data: ModelsYoutubeAnalyzerReqBody) => {
if (!data.email || data.email === "") {
toast.success("Analysis started, please wait...");
if (!data.email || data.email === '') {
toast.success('Analysis started, please wait...')
} else {
toast.success("Analysis started, the results will be sent to your email.");
toast.success('Analysis started, the results will be sent to your email.')
}

return apiClient.apiYoutubeAnalysisPost({
video: data,
});
})
},
onSuccess: (data) => {
onSuccess: data => {
queryClient.invalidateQueries(
videoQueryKeys.videosAnalyzed({
// TODO: implement authentication
userId: "1",
userId: '1',
page: DEFAULT_PAGE_PAGINATION,
pageSize: DEFAULT_PAGE_SIZE_PAGINATION,
}),
{ exact: true },
);
queryClient.setQueryData(videoQueryKeys.videoAnalysis(data.videoId || ""), data);
)
queryClient.setQueryData(videoQueryKeys.videoAnalysis(data.videoId || ''), data)
},
onError: (error) => {
onError: error => {
if (error instanceof Error) {
toast.error(error.message);
return;
toast.error(error.message)

return
}
toast.error(
`Something went wrong. Please try again later or contact ${CONTACT_SUPPORT_X} on X.`,
);
)
},
});
})

const handleAnalysis = async (userId: string, videoId: string, email?: string) => {
const data: ModelsYoutubeAnalyzerReqBody = {
userId: userId,
videoId: videoId,
};
if (email && email !== "") {
data["email"] = email;
}
await mutation.mutateAsync(data);
};

if (email && email !== '') {
data.email = email
}
await mutation.mutateAsync(data)
}

useEffect(() => {
if (mutation.error instanceof Error) {
toast.error(mutation.error.message);
toast.error(mutation.error.message)
}
}, [mutation.error, mutation.isError]);
}, [mutation.error, mutation.isError])

return {
handleAnalysis,
isLoading: mutation.isLoading,
dataAnalysis: mutation.data,
};
}
}
3 changes: 2 additions & 1 deletion gptube/hooks/use-videos-analyzed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useVideosAnalyzed() {
})

const totalVideos = query.data?.count ?? 0
const totalPages = totalVideos / pageSize
const totalPages = Math.ceil(totalVideos / pageSize)

useEffect(() => {
if (query.error instanceof Error) {
Expand All @@ -46,6 +46,7 @@ export function useVideosAnalyzed() {
}

return {
count: query.data?.count,
videos: query.data?.results,
handleChangePage,
totalPages,
Expand Down
22 changes: 11 additions & 11 deletions gptube/hooks/video-query-keys.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export const videoQueryKeys = {
videoPreview: (videoURL: string) => ["video-preview", videoURL] as const,
videoAnalysis: (videoId: string) => ["video-analysis", videoId] as const,
videoPreview: (videoURL: string) => ['video-preview', videoURL] as const,
videoAnalysis: (videoId: string) => ['video-analysis', videoId] as const,
videosAnalyzed: (args: { userId: string; page: number; pageSize: number }) =>
["videos-analyzed", { ...args }] as const,
videoStats: (args: { userId: string; videoId: string }) => ["video-stats", { ...args }] as const,
['videos-analyzed', { ...args }] as const,
videoStats: (args: { userId: string; videoId: string }) => ['video-stats', { ...args }] as const,
videoNegativeComments: (args: {
userId: string;
videoId: string;
page: number;
pageSize: number;
}) => ["video-negative-comments", { ...args }] as const,
videoLanding: () => ["video-landing"] as const,
};
userId: string
videoId: string
page: number
pageSize: number
}) => ['video-negative-comments', { ...args }] as const,
videoLanding: () => ['video-landing'] as const,
}
11 changes: 6 additions & 5 deletions gptube/utils/validations.utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { YOUTUBE_URL_REGEX } from "@/constants/youtube.constants";
import { YOUTUBE_URL_REGEX } from '@/constants/youtube.constants'

const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i;
const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i

export function isValidYoutubeUrl(url: string) {
if (url.length === 0) return false;
return url.match(YOUTUBE_URL_REGEX) !== null;
if (url.length === 0) return false

return url.match(YOUTUBE_URL_REGEX) !== null
}

export function isValidEmail(email: string) {
return EMAIL_REGEX.exec(email) !== null;
return EMAIL_REGEX.exec(email) !== null
}

0 comments on commit 1cf1668

Please sign in to comment.