generated from Javan-Odhiambo/nextjs-starter-template
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:Javan-Odhiambo/ShareHub_frontend
Showing
19 changed files
with
385 additions
and
180 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"use client"; | ||
import React, { useEffect, useState } from "react"; | ||
import { useInnovationsFetchManyQuery } from "@/redux/features/innovations/innovationsApiSlice"; | ||
import ProjectCard from "@/components/ui/projectcard"; | ||
import { useInfiniteQuery } from "@tanstack/react-query"; | ||
import { useInView } from "react-intersection-observer"; | ||
import { LoaderCircleIcon } from "lucide-react"; | ||
// * items per page in a response to calculate the total number of paginations needed at the bottom | ||
const ITEMS_PER_PAGE = 5; // Update this to match the number of items per page in your API | ||
|
||
const Home = () => { | ||
const [hasSearchResults, setHasSearchResults] = useState(false); | ||
|
||
const { ref, inView } = useInView(); | ||
|
||
// * fetching all innovations | ||
async function fetchInnovations({ pageParam }: { pageParam: number }) { | ||
const res = await fetch( | ||
`http://localhost:8000/api/innovations/?page=${pageParam}` | ||
); | ||
return res.json(); | ||
} | ||
|
||
const { | ||
data: innovationDataFetch, | ||
status: innovationStatus, | ||
error: innovationError, | ||
fetchNextPage, | ||
isFetchingNextPage, | ||
hasNextPage, | ||
} = useInfiniteQuery({ | ||
queryKey: ["innovations"], | ||
queryFn: fetchInnovations, | ||
initialPageParam: 1, | ||
getNextPageParam: (lastPage, allPages) => { | ||
if (lastPage.next) { | ||
return allPages.length + 1; | ||
} | ||
}, | ||
}); | ||
|
||
const content = innovationDataFetch?.pages?.flatMap((page: any) => { | ||
return page.results.map((innovation: any, index: number) => { | ||
if (page.results.length == index + 1) { | ||
return ( | ||
<ProjectCard | ||
key={innovation.url} | ||
innovation_url={innovation.url} | ||
author_avator_image_url={innovation.author.profile_picture} | ||
author_first_name={innovation.author.first_name} | ||
author_last_name={innovation.author.last_name} | ||
project_title={innovation.title} | ||
project_description={innovation.description} | ||
dashboard_image_url={innovation.dashboard_image} | ||
likes_count={innovation.likes_number} | ||
comments_count={innovation.comments_number} | ||
is_liked={innovation.is_liked} | ||
is_bookmarked={innovation.is_bookmarked} | ||
innerRef={ref} | ||
/> | ||
); | ||
} else { | ||
return ( | ||
<ProjectCard | ||
key={innovation.url} | ||
innovation_url={innovation.url} | ||
author_avator_image_url={innovation.author.profile_picture} | ||
author_first_name={innovation.author.first_name} | ||
author_last_name={innovation.author.last_name} | ||
project_title={innovation.title} | ||
project_description={innovation.description} | ||
dashboard_image_url={innovation.dashboard_image} | ||
likes_count={innovation.likes_number} | ||
comments_count={innovation.comments_number} | ||
is_liked={innovation.is_liked} | ||
is_bookmarked={innovation.is_bookmarked} | ||
/> | ||
); | ||
} | ||
}); | ||
}); | ||
|
||
useEffect(() => { | ||
if (inView && hasNextPage) { | ||
fetchNextPage(); | ||
} | ||
}, [inView, hasNextPage, fetchNextPage]); | ||
|
||
if (innovationStatus === "pending") { | ||
return <p>Loading...</p>; | ||
} | ||
if (innovationStatus === "error") { | ||
return <p>Error: {innovationError?.message}</p>; | ||
} | ||
if (!hasSearchResults) { | ||
return ( | ||
<> | ||
<section className="flex flex-wrap mx-auto gap-4 p-4"> | ||
{content} | ||
</section> | ||
{isFetchingNextPage && ( | ||
<LoaderCircleIcon size={28} className="animate-spin ml-10" /> | ||
)} | ||
</> | ||
); | ||
} | ||
}; | ||
|
||
export default Home; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,33 @@ | ||
import type { Metadata } from "next"; | ||
import { Inter } from "next/font/google"; | ||
import "@/styles/css/globals.css"; | ||
import { ThemeProvider } from 'next-themes' | ||
import { ThemeProvider } from "next-themes"; | ||
import { ReduxPovider } from "@/redux/ReduxProvider"; | ||
import { Toaster } from "@/components/ui/toaster"; | ||
import TenstackProvider from "@/redux/TenstackProvider"; | ||
|
||
const inter = Inter({ subsets: ["latin"] }); | ||
|
||
export const metadata: Metadata = { | ||
title: "Sharehub", | ||
description: "A sharehub nextjs app", | ||
title: "Sharehub", | ||
description: "A sharehub nextjs app", | ||
}; | ||
|
||
export default function RootLayout({ | ||
children, | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode; | ||
children: React.ReactNode; | ||
}>) { | ||
return ( | ||
<html lang="en" suppressHydrationWarning> | ||
<body className={inter.className}> | ||
<ReduxPovider> | ||
<ThemeProvider attribute="class"> | ||
{children} | ||
</ThemeProvider> | ||
</ReduxPovider> | ||
<Toaster/> | ||
</body> | ||
</html> | ||
); | ||
return ( | ||
<html lang="en" suppressHydrationWarning> | ||
<body className={inter.className}> | ||
<ReduxPovider> | ||
<TenstackProvider> | ||
<ThemeProvider attribute="class">{children}</ThemeProvider> | ||
</TenstackProvider> | ||
</ReduxPovider> | ||
<Toaster /> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,106 +1,144 @@ | ||
import React, { useState } from 'react' | ||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' | ||
import { useInnovationsCommentsListQuery } from '@/redux/features/innovations/innovationsApiSlice' | ||
import React from "react"; | ||
import { useInfiniteQuery } from "@tanstack/react-query"; | ||
import { useInView } from "react-intersection-observer"; | ||
import { | ||
EllipsisVertical, | ||
Trash2, | ||
} from "lucide-react"; | ||
import { PaginationDemo } from "@/components/Pagination"; | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuTrigger, | ||
} from "@/components/ui/dropdown-menu"; | ||
import { EllipsisVertical, Trash2 } from "lucide-react"; | ||
import { Separator } from "@/components/ui/separator"; | ||
import CustomAvatar from '@/components/ui/custom-avatar'; | ||
|
||
import CustomAvatar from "@/components/ui/custom-avatar"; | ||
|
||
type CommentsContainerProps = { | ||
innovationID: string | ||
} | ||
const CommentsContainer = ({ innovationID}: CommentsContainerProps) => { | ||
const commentsPerPage = 5; | ||
const [currentPage, setCurrentPage] = useState(1); | ||
|
||
innovationID: string; | ||
}; | ||
|
||
const { | ||
data: commentData, | ||
isLoading: isGettingComments, | ||
error: errorGettingComments, | ||
} = useInnovationsCommentsListQuery({ id: innovationID, page: currentPage }); | ||
console.log(commentData); | ||
const CommentsContainer = ({ innovationID }: CommentsContainerProps) => { | ||
const fetchComments = async ({ pageParam }: { pageParam: number }) => { | ||
const response = await fetch( | ||
`http://localhost:8000/api/innovations/${innovationID}/comments/?page=${pageParam}` | ||
); | ||
if (!response.ok) { | ||
throw new Error("Problem fetching comments"); | ||
} | ||
return response.json(); | ||
}; | ||
|
||
// extracting comments from the request | ||
const { | ||
results: comments, | ||
next: nextCommentPage, | ||
previous: previousCommentPage, | ||
count: totalComments, | ||
} = commentData || {}; | ||
const { | ||
data: commentData, | ||
fetchNextPage, | ||
hasNextPage, | ||
isFetchingNextPage, | ||
isLoading: isGettingComments, | ||
error: errorGettingComments, | ||
} = useInfiniteQuery({ | ||
queryKey: ["comments", innovationID], | ||
queryFn: ({ pageParam = 1 }) => fetchComments({ pageParam }), | ||
initialPageParam: 1, | ||
getNextPageParam: (lastPage, allPages) => { | ||
if (lastPage.next) { | ||
return allPages.length + 1; | ||
} | ||
}, | ||
}); | ||
|
||
const totalPages = | ||
totalComments && Math.ceil(totalComments / commentsPerPage); | ||
|
||
// * handling pagination | ||
const handlePrevious = () => { | ||
if (currentPage > 1) { | ||
setCurrentPage(currentPage - 1); | ||
} | ||
}; | ||
const { ref, inView } = useInView(); | ||
|
||
const handleNext = () => { | ||
if (totalPages && currentPage < totalPages) { | ||
setCurrentPage(currentPage + 1); | ||
} | ||
}; | ||
return isGettingComments ? ( | ||
<div>Loading...</div> | ||
) : errorGettingComments ? ( | ||
<div>Something went wrong</div> | ||
) : ( | ||
|
||
<section className="p-7 space-y-4"> | ||
{comments?.map((comment, index) => ( | ||
<div className="border p-3 rounded-lg shadow-md" key={index}> | ||
<div className="flex items-center justify-between"> | ||
<div className="flex items-center gap-3"> | ||
<CustomAvatar image_url={comment.author.profile_picture} first_name={comment.author.first_name} last_name={comment.author.last_name}></CustomAvatar> | ||
<div> | ||
<p> | ||
{comment.author.first_name} {comment.author.last_name} | ||
</p> | ||
<p className="text-sm">{comment.author.email}</p> | ||
</div> | ||
</div> | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<div className="hover:bg-accent rounded-full p-1"> | ||
<EllipsisVertical /> | ||
</div> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-56"> | ||
{/* <DropdownMenuItem> | ||
<SquarePen className="mr-2 h-4 w-4" /> | ||
// TODO: Comment Editing | ||
<Link href={""}> | ||
<span>Edit</span> | ||
</Link> | ||
</DropdownMenuItem> */} | ||
<DropdownMenuItem className="hover:bg-destructive active:bg-destructive focus:bg-destructive hover:text-white active:text-white focus:text-white"> | ||
<Trash2 className="mr-2 h-4 w-4" /> | ||
<span>Delete</span> | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
<Separator className="my-3" /> | ||
<p>{comment.text}</p> | ||
</div> | ||
))} | ||
<PaginationDemo | ||
currentPage={currentPage} | ||
totalPages={totalPages ?? 0} | ||
onPrevious={handlePrevious} | ||
onNext={handleNext} | ||
/> | ||
</section> | ||
) | ||
} | ||
React.useEffect(() => { | ||
if (inView && hasNextPage) { | ||
fetchNextPage(); | ||
} | ||
}, [inView, hasNextPage, fetchNextPage]); | ||
|
||
return isGettingComments ? ( | ||
<div>Loading...</div> | ||
) : errorGettingComments ? ( | ||
<div>Something went wrong</div> | ||
) : ( | ||
<section className="p-7 space-y-4" ref={ref}> | ||
{commentData?.pages.flatMap((page) => | ||
page.results.map((comment: any, index: number) => { | ||
if (page.results.length === index + 1) { | ||
return ( | ||
<div | ||
className="border p-3 rounded-lg shadow-md" | ||
key={index} | ||
ref={ref} | ||
> | ||
<div className="flex items-center justify-between"> | ||
<div className="flex items-center gap-3"> | ||
<CustomAvatar | ||
image_url={comment.author.profile_picture} | ||
first_name={comment.author.first_name} | ||
last_name={comment.author.last_name} | ||
></CustomAvatar> | ||
<div> | ||
<p> | ||
{comment.author.first_name} {comment.author.last_name} | ||
</p> | ||
<p className="text-sm">{comment.author.email}</p> | ||
</div> | ||
</div> | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<div className="hover:bg-accent rounded-full p-1"> | ||
<EllipsisVertical /> | ||
</div> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-56"> | ||
<DropdownMenuItem className="hover:bg-destructive active:bg-destructive focus:bg-destructive hover:text-white active:text-white focus:text-white"> | ||
<Trash2 className="mr-2 h-4 w-4" /> | ||
<span>Delete</span> | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
<Separator className="my-3" /> | ||
<p>{comment.text}</p> | ||
</div> | ||
); | ||
} else { | ||
return ( | ||
<div className="border p-3 rounded-lg shadow-md" key={index}> | ||
<div className="flex items-center justify-between"> | ||
<div className="flex items-center gap-3"> | ||
<CustomAvatar | ||
image_url={comment.author.profile_picture} | ||
first_name={comment.author.first_name} | ||
last_name={comment.author.last_name} | ||
></CustomAvatar> | ||
<div> | ||
<p> | ||
{comment.author.first_name} {comment.author.last_name} | ||
</p> | ||
<p className="text-sm">{comment.author.email}</p> | ||
</div> | ||
</div> | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<div className="hover:bg-accent rounded-full p-1"> | ||
<EllipsisVertical /> | ||
</div> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-56"> | ||
<DropdownMenuItem className="hover:bg-destructive active:bg-destructive focus:bg-destructive hover:text-white active:text-white focus:text-white"> | ||
<Trash2 className="mr-2 h-4 w-4" /> | ||
<span>Delete</span> | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
<Separator className="my-3" /> | ||
<p>{comment.text}</p> | ||
</div> | ||
); | ||
} | ||
}) | ||
)} | ||
</section> | ||
); | ||
}; | ||
|
||
export default CommentsContainer | ||
export default CommentsContainer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"use client" | ||
import { | ||
QueryClient, | ||
QueryClientProvider, | ||
} from "@tanstack/react-query"; | ||
import React from "react"; | ||
|
||
const queryClient = new QueryClient(); | ||
const TenstackProvider = ({ children }: { children: React.ReactNode }) => { | ||
return ( | ||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
); | ||
}; | ||
|
||
export default TenstackProvider; |