diff --git a/.gitignore b/.gitignore index 1e913e0..df674d3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ next-env.d.ts .vscode/* .firebase/logs/vsce-debug.log AGENTS.md + +# github +.github/agents/** +.github/copilot-instructions.md \ No newline at end of file diff --git a/app/api/hello/route.ts b/app/api/hello/route.ts deleted file mode 100644 index ff74968..0000000 --- a/app/api/hello/route.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NextResponse } from "next/server"; - -export async function GET() { - return NextResponse.json({ name: "John Doe" }); -} diff --git a/app/communities/page.tsx b/app/communities/page.tsx index 74780a6..195f1df 100644 --- a/app/communities/page.tsx +++ b/app/communities/page.tsx @@ -13,9 +13,8 @@ import React, { useEffect, useMemo } from "react"; import { Community } from "@/types/community"; /** - * Displays the communities page with the top 5 communities. - * Pressing the "See More" button will display the next 5 communities. - * @returns {React.FC} - the communities page with the top 5 communities. + * Lists communities with infinite scroll, grouped by membership state. + * @returns Communities page with moderation, joined, and discovery sections. */ const Communities: React.FC = () => { const { communityStateValue } = useCommunityState(); diff --git a/app/community/[communityId]/CommunityClientPage.tsx b/app/community/[communityId]/CommunityClientPage.tsx index 5cd07cf..6bd67d2 100644 --- a/app/community/[communityId]/CommunityClientPage.tsx +++ b/app/community/[communityId]/CommunityClientPage.tsx @@ -1,19 +1,24 @@ "use client"; import { communityStateAtom } from "@/atoms/communitiesAtom"; -import About from "@/components/community/about/About"; import CreatePostLink from "@/components/community/CreatePostLink"; +import About from "@/components/community/about/About"; import CommunityHeader from "@/components/community/community-header/CommunityHeader"; import PageContent from "@/components/layout/PageContent"; import Posts from "@/components/posts/Posts"; +import { Community } from "@/types/community"; import { useAtom } from "jotai"; import React, { useEffect } from "react"; -import { Community } from "@/types/community"; type CommunityPageProps = { communityData: Community; }; +/** + * Client-side community page wiring header, posts feed, and about sidebar. + * @param communityData - Community data fetched on the server. + * @returns Community layout with feed and management panels. + */ const CommunityClientPage: React.FC = ({ communityData, }) => { @@ -29,7 +34,7 @@ const CommunityClientPage: React.FC = ({ } }, [communityData, setCommunityStateValue]); - const currentCommunity = + const currentCommunity: Community = communityStateValue.currentCommunity?.id === communityData.id ? communityStateValue.currentCommunity : communityData; diff --git a/app/community/[communityId]/comments/[pid]/PostClientPage.tsx b/app/community/[communityId]/comments/[pid]/PostClientPage.tsx index 6b79411..f4d8544 100644 --- a/app/community/[communityId]/comments/[pid]/PostClientPage.tsx +++ b/app/community/[communityId]/comments/[pid]/PostClientPage.tsx @@ -1,6 +1,5 @@ "use client"; -/* eslint-disable react-hooks/exhaustive-deps */ import { communityStateAtom } from "@/atoms/communitiesAtom"; import About from "@/components/community/about/About"; import PageContent from "@/components/layout/PageContent"; @@ -9,17 +8,17 @@ import Comments from "@/components/posts/comments/Comments"; import PostItem from "@/components/posts/post-item/PostItem"; import { auth } from "@/firebase/clientApp"; import useCommunityPermissions from "@/hooks/community/useCommunityPermissions"; +import usePostDeletion from "@/hooks/posts/usePostDeletion"; import usePostState from "@/hooks/posts/usePostState"; import usePostVote from "@/hooks/posts/usePostVote"; -import usePostDeletion from "@/hooks/posts/usePostDeletion"; import usePostVoteSync from "@/hooks/posts/usePostVoteSync"; +import { Community } from "@/types/community"; +import { Post } from "@/types/post"; import { Stack } from "@chakra-ui/react"; import { User } from "firebase/auth"; import { useAtom } from "jotai"; import React, { useEffect } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; -import { Community } from "@/types/community"; -import { Post } from "@/types/post"; type PostPageProps = { communityData: Community; @@ -27,13 +26,10 @@ type PostPageProps = { }; /** - * Displays a single post. - * Contains: - * - PostItem component - * - About component - * - Comments component - * - * @returns {React.FC} - Single post page with all components + * Client page for a single post with voting, deletion, and threaded comments. + * @param communityData - Community context for the post. + * @param postData - Post fetched on the server; may be null if not found. + * @returns Layout with post content on the left and community about panel on the right. */ const PostPage: React.FC = ({ communityData, postData }) => { const { postStateValue, setPostStateValue } = usePostState(); diff --git a/app/community/[communityId]/comments/[pid]/page.tsx b/app/community/[communityId]/comments/[pid]/page.tsx index 2a085fc..36c02c5 100644 --- a/app/community/[communityId]/comments/[pid]/page.tsx +++ b/app/community/[communityId]/comments/[pid]/page.tsx @@ -1,8 +1,13 @@ import { getCommunityData } from "@/lib/community/getCommunityData"; -import PostClientPage from "./PostClientPage"; -import { notFound } from "next/navigation"; import { getPost } from "@/lib/post/getPost"; +import { notFound } from "next/navigation"; +import PostClientPage from "./PostClientPage"; +/** + * Server component that fetches community and post data for the comments route. + * @param params - Route params containing community and post ids. + * @returns Client comment page or an error/not-found fallback. + */ export default async function PostPage({ params, }: { diff --git a/app/community/[communityId]/page.tsx b/app/community/[communityId]/page.tsx index a45289a..5ad08e1 100644 --- a/app/community/[communityId]/page.tsx +++ b/app/community/[communityId]/page.tsx @@ -1,7 +1,12 @@ import { getCommunityData } from "@/lib/community/getCommunityData"; -import CommunityClientPage from "./CommunityClientPage"; import { notFound } from "next/navigation"; +import CommunityClientPage from "./CommunityClientPage"; +/** + * Server component that loads community data and renders the client view. + * @param params - Route params containing the community id. + * @returns Community client page or a not-found/error fallback. + */ export default async function Page({ params, }: { diff --git a/app/community/[communityId]/submit/SubmitPostClientPage.tsx b/app/community/[communityId]/submit/SubmitPostClientPage.tsx index cf32c1c..519101a 100644 --- a/app/community/[communityId]/submit/SubmitPostClientPage.tsx +++ b/app/community/[communityId]/submit/SubmitPostClientPage.tsx @@ -7,16 +7,21 @@ import PageContent from "@/components/layout/PageContent"; import AuthButtons from "@/components/navbar/right-content/AuthButtons"; import NewPostForm from "@/components/posts/new-post-form/NewPostForm"; import { auth } from "@/firebase/clientApp"; +import { Community } from "@/types/community"; import { Box, Stack, Text } from "@chakra-ui/react"; import { useAtom, useSetAtom } from "jotai"; import React, { useEffect } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; -import { Community } from "@/types/community"; type SubmitPostPageProps = { communityData: Community; }; +/** + * Client page for creating a new post inside a community. + * @param communityData - Community where the post will be published. + * @returns Post creation form with sidebar about section. + */ const SubmitPostPage: React.FC = ({ communityData }) => { const [user] = useAuthState(auth); const [communityStateValue, setCommunityStateValue] = diff --git a/app/community/[communityId]/submit/page.tsx b/app/community/[communityId]/submit/page.tsx index e4be41d..dabe4b9 100644 --- a/app/community/[communityId]/submit/page.tsx +++ b/app/community/[communityId]/submit/page.tsx @@ -1,7 +1,12 @@ import { getCommunityData } from "@/lib/community/getCommunityData"; -import SubmitPostClientPage from "./SubmitPostClientPage"; import { notFound } from "next/navigation"; +import SubmitPostClientPage from "./SubmitPostClientPage"; +/** + * Server component that prepares data for the submit-post page of a community. + * @param params - Route params containing the community id. + * @returns Client submit page or an error/not-found fallback. + */ export default async function SubmitPostPage({ params, }: { diff --git a/app/emotion-registry.tsx b/app/emotion-registry.tsx index 5dad156..0b4ab01 100644 --- a/app/emotion-registry.tsx +++ b/app/emotion-registry.tsx @@ -1,10 +1,15 @@ "use client"; -import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; +import { CacheProvider } from "@emotion/react"; import { useServerInsertedHTML } from "next/navigation"; import { useState } from "react"; +/** + * Configures Emotion cache for the App Router and injects styles during SSR. + * @param children - React tree that needs Emotion styling support. + * @returns Cache provider that ensures styles render on both server and client. + */ export default function EmotionRegistry({ children, }: { diff --git a/app/layout.tsx b/app/layout.tsx index 2fc3809..3bceec2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,11 +1,16 @@ -import { Providers } from "./providers"; import { Metadata } from "next"; +import { Providers } from "./providers"; export const metadata: Metadata = { title: "Circus Discussions", description: "A discussion platform", }; +/** + * Root layout wiring global providers and metadata for the App Router. + * @param children - Route content to render inside the provider tree. + * @returns HTML scaffold with Providers applied to the body. + */ export default function RootLayout({ children, }: { diff --git a/app/not-found.tsx b/app/not-found.tsx index b466330..3b6e9d7 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -2,6 +2,10 @@ import { Button, Flex, Stack, Text } from "@chakra-ui/react"; import Link from "next/link"; import React from "react"; +/** + * Fallback page for unknown routes with quick links back to core pages. + * @returns Centered message and navigation buttons. + */ const PageNotFound: React.FC = () => { return ( (defaultModalState); diff --git a/atoms/communitiesAtom.ts b/atoms/communitiesAtom.ts index d4feed7..cd83ede 100644 --- a/atoms/communitiesAtom.ts +++ b/atoms/communitiesAtom.ts @@ -3,21 +3,21 @@ import { Community, CommunitySnippet } from "@/types/community"; /** * Stores the community snippets to track the state of the communities atom. - * @property {CommunitySnippet[]} mySnippets - list of community snippets - * @property {Community} currentCommunity - the community the user is currently in - * @property {boolean} snippetFetched - whether the community snippets have been fetched or not + * @propertymySnippets - list of community snippets + * @property currentCommunity - the community the user is currently in + * @property snippetFetched - whether the community snippets have been fetched or not */ interface CommunityState { - mySnippets: CommunitySnippet[]; // stores a list of community snippets - currentCommunity?: Community; // user is not always in a community hence optional + mySnippets: CommunitySnippet[]; + currentCommunity?: Community; snippetFetched: boolean; } /** * Initially, the array for the community state is empty. * The community snippets have not been fetched initially hence array is empty. - * @property {CommunitySnippet[]} mySnippets - empty array - * @property {boolean} snippetFetched - false by default + * @property mySnippets - empty array + * @property snippetFetched - false by default */ export const defaultCommunityState: CommunityState = { mySnippets: [], @@ -25,11 +25,7 @@ export const defaultCommunityState: CommunityState = { }; /** - * Atom which describes the state of the community state. - * - * @requires CommunityState - state definition - * @requires defaultCommunityState - default state - * - * @see https://jotai.org/docs/core/atom + * Stores the user's community snippets and the active community context. + * @returns Jotai atom tracking membership data and whether snippets are loaded. */ export const communityStateAtom = atom(defaultCommunityState); diff --git a/atoms/directoryMenuAtom.ts b/atoms/directoryMenuAtom.ts index 5e01ee9..3c321fb 100644 --- a/atoms/directoryMenuAtom.ts +++ b/atoms/directoryMenuAtom.ts @@ -4,21 +4,14 @@ import { DirectoryMenuItem } from "@/types/directoryMenu"; /** * Interface which describes the state of the directory menu. - * @property {boolean} isOpen - whether the directory menu is open or not - * @property {DirectoryMenuItem} selectedMenuItem - the menu item that is currently selected + * @property isOpen - whether the directory menu is open or not + * @property selectedMenuItem - the menu item that is currently selected */ interface DirectoryMenuState { isOpen: boolean; selectedMenuItem: DirectoryMenuItem; } -/** - * Default menu item when no community is selected (home page). - * @property {string} displayText - "Home" - * @property {string} link - "/" (home page) - * @property {IconType} icon - TiHome (home icon) - * @property {string} iconColor - "black" - */ export const defaultMenuItem = { displayText: "Home", link: "/", @@ -26,18 +19,13 @@ export const defaultMenuItem = { iconColor: { base: "black", _dark: "white" }, }; -/** - * Default state of the directory menu. - * The directory menu is closed by default. - * @property {boolean} isOpen - false by default - * @property {DirectoryMenuItem} selectedMenuItem - default menu item (home page) - */ export const defaultMenuState: DirectoryMenuState = { isOpen: false, selectedMenuItem: defaultMenuItem, }; /** - * Atom which stores the state of the directory menu. + * Controls the navbar directory dropdown and the currently highlighted item. + * @returns Jotai atom containing open state and selected menu item. */ export const directoryMenuAtom = atom(defaultMenuState); diff --git a/atoms/postsAtom.ts b/atoms/postsAtom.ts index 03189c6..b1ed6ba 100644 --- a/atoms/postsAtom.ts +++ b/atoms/postsAtom.ts @@ -3,13 +3,13 @@ import { Post, PostVote } from "@/types/post"; /** * Represents the base state for the atom. - * @property {Post | null} selectedPost - the post that is currently selected - * @property {Post[]} posts - all the posts - * @property {PostVote[]} postVotes - all the post votes + * @property selectedPost - the post that is currently selected + * @property posts - all the posts + * @property postVotes - all the post votes */ interface PostState { - selectedPost: Post | null; // when user opens a post - posts: Post[]; // all the post + selectedPost: Post | null; + posts: Post[]; postVotes: PostVote[]; } @@ -19,9 +19,9 @@ interface PostState { * - No post is selected * - There are no posts to be displayed * - Posts have not been voted on by the current user - * @property {Post | null} selectedPost - null as no post is selected - * @property {Post[]} posts - empty array as there are no posts - * @property {PostVote[]} postVotes - empty array as posts have not been voted on + * @property selectedPost - null as no post is selected + * @property posts - empty array as there are no posts + * @property postVotes - empty array as posts have not been voted on * * @requires PostState - default state type */ @@ -32,15 +32,7 @@ const defaultPostState: PostState = { }; /** - * Atom which describes the state of the posts. - * Initially: - * - No post is selected - * - There are no posts to be displayed - * - Posts have not been voted on by the current user - * - * @requires PostState - type of the state - * @requires defaultPostState - default state of the atom - * - * @see https://jotai.org/docs/core/atom + * Holds the active post, loaded posts list, and vote cache for the user session. + * @returns Jotai atom that stores post selection and voting state. */ export const postStateAtom = atom(defaultPostState); diff --git a/atoms/savedPostsAtom.ts b/atoms/savedPostsAtom.ts index 03fd5d2..f6dc760 100644 --- a/atoms/savedPostsAtom.ts +++ b/atoms/savedPostsAtom.ts @@ -13,4 +13,8 @@ const defaultSavedPostState: SavedPostState = { fetched: false, }; +/** + * Tracks saved posts modal state and cached posts for the current user. + * @returns Jotai atom with modal visibility, fetch flag, and saved items. + */ export const savedPostStateAtom = atom(defaultSavedPostState); diff --git a/components/layout/GlobalHooks.tsx b/components/layout/GlobalHooks.tsx index 9f2bef0..a03c09b 100644 --- a/components/layout/GlobalHooks.tsx +++ b/components/layout/GlobalHooks.tsx @@ -5,6 +5,10 @@ import useSavedPosts from "@/hooks/posts/useSavedPosts"; import React, { useEffect } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; +/** + * Runs global data bootstrapping for community snippets and saved posts. + * @returns Null render; side effects hydrate atoms based on auth state. + */ const GlobalHooks: React.FC = () => { useCommunitySnippets(); const { fetchSavedPosts, setSavedPostState } = useSavedPosts(); diff --git a/components/layout/Layout.tsx b/components/layout/Layout.tsx index 2d28503..8e23abf 100644 --- a/components/layout/Layout.tsx +++ b/components/layout/Layout.tsx @@ -2,27 +2,14 @@ import React, { ReactNode } from "react"; import Navbar from "../navbar/Navbar"; import GlobalHooks from "./GlobalHooks"; -/** - * Children components that can exist that are rendered. - * These can include React components, pages, etc. - * @param {ReactNode} children - children components in every page - */ interface LayoutProps { children: ReactNode; } /** - * Provides a common layout for the entire application. - * Each page in the application will follow this standard layout. - * Each page will display the navbar component. - * Each page will be different hence different children components can be passed. - * @param {LayoutProps} { children } - children components in every page - * - * @returns {React.FC} - layout for the entire application with navbar at the top - * - * @see https://nextjs.org/docs/basic-features/layouts - * - * @requires Navbar.tsx - navbar at the top of every page + * Wraps pages with global hooks and the site navbar. + * @param children - Page content to display below the navbar. + * @returns Layout shell rendered on every page. */ const Layout: React.FC = ({ children }) => { return ( diff --git a/components/layout/PageContent.tsx b/components/layout/PageContent.tsx index f249735..39369fd 100644 --- a/components/layout/PageContent.tsx +++ b/components/layout/PageContent.tsx @@ -1,25 +1,14 @@ import { Flex } from "@chakra-ui/react"; import React, { ReactNode } from "react"; -/** - * Children components that can exist that are rendered. - * These can include React components, pages, etc. - * @param {ReactNode} children - children components in every page - */ type PageContentProps = { children: ReactNode; }; /** - * Creates a layout for for main contents page. - * The page is separated into 2 sections (array of 2): - * - Left: main content such as the list of posts - * - Right: extra content such as community descriptions, etc - * - * The layout is responsive which means that in mobile screen sizes, - * the right layout will be removed. - * @param {children} children - children components in every page - * @returns page layout + * Two-column responsive layout that expects main content and an optional sidebar. + * @param children - Array-like children where index 0 is main content and index 1 is sidebar. + * @returns Flex container that hides the sidebar on small screens. */ const PageContent: React.FC = ({ children }) => { return ( diff --git a/components/navbar/Navbar.tsx b/components/navbar/Navbar.tsx index 8cbec90..797f026 100644 --- a/components/navbar/Navbar.tsx +++ b/components/navbar/Navbar.tsx @@ -9,28 +9,8 @@ import RightContent from "./right-content/RightContent"; import SearchInput from "./SearchInput"; /** - * Creates a navbar component which contains the following elements: - * - * - Logo which is visible on mobile and desktop sizes - * - Logo name which is visible only on desktop sizes - * - Search bar which is visible on mobile and desktop sizes and resizes dynamically - * - Directory of communities that the user is subscribed to (only displayed when authenticated) - * - * - * Navbar changes depending on whether the user is authenticated. - * If the user is authenticated, it will display the: - * - User menu with name or username - * - Buttons (create post, notifications, messages, etc) - * - Community directory menu which would display all the subscribed communities and create community option - * - * If the user is not authenticated, it will display the: - * - Authentication buttons (log in and sing up) - * - User menu with different options - * @returns {React.FC} - Navbar component - * - * @requires ./right-content - content displaying authentication buttons or actions - * @requires ./SearchInput - Search field - * @requires ./directory - showing community menu button + * Top navigation bar with branding, search, community directory, and auth-aware actions. + * @returns Responsive navbar that updates based on authentication state. */ const Navbar: React.FC = () => { const [user, loading, error] = useAuthState(auth); // will be passed to child components diff --git a/components/navbar/SearchInput.tsx b/components/navbar/SearchInput.tsx index 11eae08..6aff81b 100644 --- a/components/navbar/SearchInput.tsx +++ b/components/navbar/SearchInput.tsx @@ -12,12 +12,8 @@ import { AiOutlineSearch } from "react-icons/ai"; import SearchModal from "./SearchModal"; /** - * Search bar which would allow the user to carry out searches on the site. - * Search bar dynamically resizes depending on the screen size. - * It will use all the available space of the parent component (navbar). - * On mobile screen sizes, the search bar will be displayed as a button which will open the search modal. - * On desktop screen sizes, the search bar will be displayed as a normal input which opens the search modal on click. - * @returns {React.FC} - Search bar + * Navbar search trigger that adapts between a button and input based on screen size. + * @returns Search control and modal opener. */ const SearchInput: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -45,8 +41,9 @@ const SearchInput: React.FC = () => { export default SearchInput; /** - * Displays an input field for the search bar. - * @returns {React.FC} - Search bar + * Read-only search input that opens the modal on click. + * @param onClick - Handler to open the search modal. + * @returns Input group styled for the navbar. */ const SearchBox: React.FC<{ onClick: () => void }> = ({ onClick }) => { return ( diff --git a/components/posts/Posts.tsx b/components/posts/Posts.tsx index d7f49fc..e7f877d 100644 --- a/components/posts/Posts.tsx +++ b/components/posts/Posts.tsx @@ -14,20 +14,14 @@ import { useAuthState } from "react-firebase-hooks/auth"; import PostLoader from "../loaders/post-loader/PostLoader"; import PostItem from "./post-item/PostItem"; -/** - * @param {Community} communityData - Community object from firebase - */ type PostsProps = { communityData: Community; }; /** - * Displays all the posts in a community. - * Displays a list of `PostItem` components. - * While the posts are being fetched, displays a loading skeleton. - * @param {Community} communityData - Community object from firebase - * - * @returns {React.FC} - Posts component + * Renders a community's posts with voting, deletion, and infinite scroll. + * @param communityData - Community context used to scope posts and permissions. + * @returns Stack of post cards with a sentinel for pagination. */ const Posts: React.FC = ({ communityData }) => { const [user] = useAuthState(auth); @@ -42,9 +36,6 @@ const Posts: React.FC = ({ communityData }) => { communityId: communityData.id, }); - /** - * Gets all votes in the community when component mounts (page loads). - */ useEffect(() => { fetchPosts(true); }, [communityData]); diff --git a/components/posts/comments/Comments.tsx b/components/posts/comments/Comments.tsx index 3f54e20..c15e350 100644 --- a/components/posts/comments/Comments.tsx +++ b/components/posts/comments/Comments.tsx @@ -20,12 +20,6 @@ import CommentInput from "./CommentInput"; import CommentItem from "./CommentItem"; import { LuChevronRight } from "react-icons/lu"; -/** - * Required props for Comments component - * @param {User} user - User object from firebase - * @param {Post} selectedPost - Post object from firebase - * @param {string} communityId - id of the community - */ type CommentsProps = { user?: User; selectedPost: Post | null; @@ -34,17 +28,12 @@ type CommentsProps = { }; /** - * Displays all the comments for a post. - * Allows user to create, edit and delete comments. - * - * If there are no comments, displays a message. - * Show loading skeleton while fetching comments. - * If everything is loaded, show the comments. - * @param {User} user - User object from firebase - * @param {Post} selectedPost - Post object from firebase - * @param {string} communityId - id of the community - * - * @returns {React.FC} - Comments component + * Shows a post's comments with threaded replies and CRUD controls. + * @param user - Authenticated user creating or deleting comments. + * @param selectedPost - Post whose comments are displayed. + * @param communityId - Community id used for permissions. + * @param isCommunityAdmin - Whether the viewer can moderate comments. + * @returns Comment tree with input and loading states. */ const Comments: React.FC = ({ user, diff --git a/components/posts/post-item/PostItem.tsx b/components/posts/post-item/PostItem.tsx index 84cd827..fa642c1 100644 --- a/components/posts/post-item/PostItem.tsx +++ b/components/posts/post-item/PostItem.tsx @@ -11,15 +11,6 @@ import PostTitle from "./PostTitle"; import PostBody from "./PostBody"; import PostActions from "./PostActions"; -/** - * @param {Post} post - post object - * @param {boolean} userIsCreator - is the currently logged in user the creator of post - * @param {number} userVoteValue - whether the currently logged in user has voted on the post (1, -1, or 0) - * @param {function} onVote - function to handle voting - * @param {function} onDeletePost - function to handle deleting post - * @param {function} onSelectPost - function to handle selecting post - * @param {boolean} showCommunityImage - whether to show the community image - */ type PostItemProps = { post: Post; userIsCreator: boolean; // is the currently logged in user the creator of post @@ -37,25 +28,16 @@ type PostItemProps = { }; /** - * Component to display a post: - * - Post title - * - Post text - * - Post creator - * - Post community - * - Post vote count - * - Post vote buttons - * - Post delete button (if user is creator or admin) - * - Post select button (if post is not selected) - * - Post community image (if showCommunityImage is true) - * @param {Post} post - post object - * @param {boolean} userIsCreator - is the currently logged in user the creator of post - * @param {boolean} userIsAdmin - is the currently logged in user an admin of the community - * @param {number} userVoteValue - whether the currently logged in user has voted on the post (1, -1, or 0) - * @param {function} onVote - function to handle voting - * @param {function} onDeletePost - function to handle deleting post - * @param {function} onSelectPost - function to handle selecting post - * @param {boolean} showCommunityImage - whether to show the community image - * @returns {React.FC} - card displaying post + * Card that displays post metadata, content preview, votes, and action buttons. + * @param post - Post data to render. + * @param userIsCreator - Whether the viewer authored the post. + * @param userIsAdmin - Whether the viewer can moderate the post. + * @param userVoteValue - Current user's vote value for this post. + * @param onVote - Handler invoked when a vote button is clicked. + * @param onDeletePost - Handler to delete the post. + * @param onSelectPost - Optional handler to open the post details view. + * @param showCommunityImage - Whether to show the community avatar. + * @returns Interactive post card component. */ const PostItem: React.FC = ({ post, @@ -75,18 +57,8 @@ const PostItem: React.FC = ({ const { onSavePost, isPostSaved } = useSavedPosts(); const isSaved = isPostSaved(post.id!); - /** - * If there is no selected post then post is already selected - */ const singlePostPage = !onSelectPost; - /** - * Will call the `handleDelete` from prop (usePosts hook). - * This function provides the error handling for the delete functionality. - * Each component may choose to the error handling differently. - * Core functionality is shared. - * @param {React.MouseEvent} event - click event on delete button to prevent from post being selected - */ const handleDelete = async ( event: React.MouseEvent ) => { @@ -122,10 +94,6 @@ const PostItem: React.FC = ({ } }; - /** - * Added functionality to share a post by copying the link to the post to the clipboard. - * Router will check base URL to copy the correct link depending on the name of the site. - */ const getPostLink = () => { const baseUrl = `${window.location.protocol}//${window.location.host}`; return `${baseUrl}/community/${post.communityId}/comments/${post.id}`; diff --git a/hooks/admin/useAddAdmin.ts b/hooks/admin/useAddAdmin.ts index 23e8d32..753b799 100644 --- a/hooks/admin/useAddAdmin.ts +++ b/hooks/admin/useAddAdmin.ts @@ -5,6 +5,14 @@ import { useSetAtom } from "jotai"; import { Dispatch, SetStateAction, useCallback } from "react"; import { Community } from "@/types/community"; +/** + * Adds a community admin and syncs the local community state. + * @param communityId - Community to update. + * @param newUser - User being promoted. + * @param communityImageURL - Optional image URL stored with the snippet. + * @param updateAdmins - Optional setter to update local admin lists. + * @returns Callback that performs the add operation. + */ const useAddAdmin = () => { const setCommunityStateValue = useSetAtom(communityStateAtom); diff --git a/hooks/admin/useAdminList.ts b/hooks/admin/useAdminList.ts index 11d1830..33a9002 100644 --- a/hooks/admin/useAdminList.ts +++ b/hooks/admin/useAdminList.ts @@ -2,6 +2,12 @@ import { useCallback, useState } from "react"; import { AdminUser } from "@/types/adminUser"; import { fetchCommunityAdmins } from "@/lib/community/fetchCommunityAdmins"; +/** + * Loads and stores the list of admins for a community, including the creator. + * @param creatorId - Creator uid to always include. + * @param adminIds - Optional admin id array from the community document. + * @returns Admin list, setter, loading flag, and loader function. + */ const useAdminList = () => { const [admins, setAdmins] = useState([]); const [loading, setLoading] = useState(false); diff --git a/hooks/admin/useAdminSearch.ts b/hooks/admin/useAdminSearch.ts index edeb507..bc73aed 100644 --- a/hooks/admin/useAdminSearch.ts +++ b/hooks/admin/useAdminSearch.ts @@ -3,6 +3,12 @@ import { AdminUser } from "@/types/adminUser"; import { findUserByEmail } from "@/lib/community/findUserByEmail"; import { searchUsersByEmail } from "@/lib/community/searchUsersByEmail"; +/** + * Provides helpers to search for potential admin users by email. + * @param emailQuery - Partial email string for search. + * @param email - Exact email to resolve to a user. + * @returns Functions to search many users or fetch a single user. + */ const useAdminSearch = () => { const searchUsers = useCallback(async (emailQuery: string) => { try { diff --git a/hooks/admin/useRemoveAdmin.ts b/hooks/admin/useRemoveAdmin.ts index 009ceca..ce6d3cd 100644 --- a/hooks/admin/useRemoveAdmin.ts +++ b/hooks/admin/useRemoveAdmin.ts @@ -5,6 +5,13 @@ import { useSetAtom } from "jotai"; import { Dispatch, SetStateAction, useCallback } from "react"; import { Community } from "@/types/community"; +/** + * Removes an admin from a community and updates local community state. + * @param communityId - Community to update. + * @param userId - User id to remove from admin list. + * @param updateAdmins - Optional setter to sync local admin arrays. + * @returns Callback that performs the removal. + */ const useRemoveAdmin = () => { const setCommunityStateValue = useSetAtom(communityStateAtom); diff --git a/hooks/comments/useCommentList.ts b/hooks/comments/useCommentList.ts index 5bcb752..3bb8e79 100644 --- a/hooks/comments/useCommentList.ts +++ b/hooks/comments/useCommentList.ts @@ -5,6 +5,11 @@ import { Post } from "@/types/post"; import useCustomToast from "@/hooks/useCustomToast"; import { Comment } from "../../types/comment"; +/** + * Loads comments for the selected post and keeps them in local state. + * @param selectedPost - Post whose comments should be fetched. + * @returns Comment list, setter, loading flag, and a reload function. + */ const useCommentList = (selectedPost: Post | null) => { const showToast = useCustomToast(); const [comments, setComments] = useState([]); diff --git a/hooks/comments/useCreateComment.ts b/hooks/comments/useCreateComment.ts index 3102fe6..46d10b4 100644 --- a/hooks/comments/useCreateComment.ts +++ b/hooks/comments/useCreateComment.ts @@ -15,6 +15,13 @@ import { useSetAtom } from "jotai"; import useCustomToast from "@/hooks/useCustomToast"; import { Comment } from "../../types/comment"; +/** + * Creates a new comment or reply on the selected post and updates counts. + * @param user - Authenticated user authoring the comment. + * @param commentText - Text body of the comment. + * @param parentId - Optional parent comment id for threaded replies. + * @returns Comment creation handler and loading flag. + */ const useCreateComment = ( selectedPost: Post | null, setComments: Dispatch> diff --git a/hooks/comments/useDeleteComment.ts b/hooks/comments/useDeleteComment.ts index 1727190..d723775 100644 --- a/hooks/comments/useDeleteComment.ts +++ b/hooks/comments/useDeleteComment.ts @@ -6,6 +6,11 @@ import { useSetAtom } from "jotai"; import useCustomToast from "@/hooks/useCustomToast"; import { Comment } from "../../types/comment"; +/** + * Deletes a comment and its replies while syncing counts on the parent post. + * @param comment - Comment to remove along with its descendants. + * @returns Delete handler and the id of the comment currently being deleted. + */ const useDeleteComment = ( comments: Comment[], setComments: Dispatch> diff --git a/hooks/community/useCommunitiesFeed.ts b/hooks/community/useCommunitiesFeed.ts index 8f4bc9d..15b2764 100644 --- a/hooks/community/useCommunitiesFeed.ts +++ b/hooks/community/useCommunitiesFeed.ts @@ -18,6 +18,12 @@ type UseCommunitiesFeedProps = { isPagination?: boolean; }; +/** + * Loads communities ordered by member count, with optional pagination support. + * @param limitValue - Number of communities to fetch per page. + * @param isPagination - Whether to enable fetching more on demand. + * @returns Community list, loading flag, pagination status, and fetch function. + */ const useCommunitiesFeed = ({ limitValue = 10, isPagination = false, diff --git a/hooks/community/useCommunityImage.ts b/hooks/community/useCommunityImage.ts index ff242ed..002696d 100644 --- a/hooks/community/useCommunityImage.ts +++ b/hooks/community/useCommunityImage.ts @@ -7,6 +7,11 @@ import { useState } from "react"; import useCustomToast from "../useCustomToast"; import { Community } from "@/types/community"; +/** + * Uploads or removes a community image while syncing snippets and local state. + * @param communityData - Community whose image is being managed. + * @returns Handlers to update or delete the image along with an uploading flag. + */ const useCommunityImage = (communityData: Community) => { const setCommunityStateValue = useSetAtom(communityStateAtom); const showToast = useCustomToast(); diff --git a/hooks/community/useCommunityMembers.ts b/hooks/community/useCommunityMembers.ts index e7b8a5d..c76b8f9 100644 --- a/hooks/community/useCommunityMembers.ts +++ b/hooks/community/useCommunityMembers.ts @@ -4,6 +4,11 @@ import { useCallback, useState } from "react"; import { fetchCommunityMembers } from "@/lib/community/fetchCommunityMembers"; import { CommunityMember } from "@/types/communityMember"; +/** + * Fetches and caches the members of a community for modal displays. + * @param communityId - Target community to query. + * @returns Member list, loading and error flags, plus a loader function. + */ const useCommunityMembers = () => { const [members, setMembers] = useState([]); const [loading, setLoading] = useState(false); diff --git a/hooks/community/useCommunityMembershipActions.tsx b/hooks/community/useCommunityMembershipActions.tsx index 8ede1ac..263701b 100644 --- a/hooks/community/useCommunityMembershipActions.tsx +++ b/hooks/community/useCommunityMembershipActions.tsx @@ -6,6 +6,12 @@ import useJoinCommunity from "./useJoinCommunity"; import useLeaveCommunity from "./useLeaveCommunity"; import { Community } from "@/types/community"; +/** + * Centralizes join/leave actions for community buttons and gates them on auth. + * @param communityData - Community the user wants to join or leave. + * @param isJoined - Whether the user is already a member. + * @returns Handler that joins or leaves and a loading flag combining both flows. + */ const useCommunityMembershipActions = () => { const [user] = useAuthState(auth); const setAuthModalState = useSetAtom(authModalStateAtom); diff --git a/hooks/community/useCommunityPermissions.tsx b/hooks/community/useCommunityPermissions.tsx index ff6d47f..7279989 100644 --- a/hooks/community/useCommunityPermissions.tsx +++ b/hooks/community/useCommunityPermissions.tsx @@ -2,6 +2,11 @@ import { Community } from "@/types/community"; import { auth } from "@/firebase/clientApp"; import { useAuthState } from "react-firebase-hooks/auth"; +/** + * Calculates permission flags for the current user within a community. + * @param communityData - Community to check against. + * @returns Boolean flags for creator, admin, and admin-management rights. + */ const useCommunityPermissions = (communityData?: Community) => { const [user] = useAuthState(auth); diff --git a/hooks/community/useCommunityPrivacy.ts b/hooks/community/useCommunityPrivacy.ts index d1b8f3a..a0efc0f 100644 --- a/hooks/community/useCommunityPrivacy.ts +++ b/hooks/community/useCommunityPrivacy.ts @@ -5,6 +5,11 @@ import { doc, updateDoc } from "firebase/firestore"; import { useSetAtom } from "jotai"; import useCustomToast from "../useCustomToast"; +/** + * Updates a community's privacy type and mirrors the change in local state. + * @param communityData - Community whose privacy setting is being changed. + * @returns Handler that writes the new privacy type to Firestore. + */ const useCommunityPrivacy = (communityData: Community) => { const setCommunityStateValue = useSetAtom(communityStateAtom); const showToast = useCustomToast(); diff --git a/hooks/community/useCommunitySnippets.ts b/hooks/community/useCommunitySnippets.ts index 8ba0708..593e10c 100644 --- a/hooks/community/useCommunitySnippets.ts +++ b/hooks/community/useCommunitySnippets.ts @@ -7,6 +7,10 @@ import { useEffect, useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import useCustomToast from "../useCustomToast"; +/** + * Loads and caches the current user's community snippets to drive menus and permissions. + * @returns Loading and error flags; side effects populate the community atom. + */ export const useCommunitySnippets = () => { const [user] = useAuthState(auth); const setCommunityStateValue = useSetAtom(communityStateAtom); diff --git a/hooks/community/useCommunityState.ts b/hooks/community/useCommunityState.ts index d5f47e3..d79e5c5 100644 --- a/hooks/community/useCommunityState.ts +++ b/hooks/community/useCommunityState.ts @@ -1,6 +1,10 @@ import { communityStateAtom } from "@/atoms/communitiesAtom"; import { useAtom } from "jotai"; +/** + * Convenience hook for reading and mutating the global community atom. + * @returns Current community state and setter. + */ const useCommunityState = () => { const [communityStateValue, setCommunityStateValue] = useAtom(communityStateAtom); diff --git a/hooks/community/useCreateCommunity.ts b/hooks/community/useCreateCommunity.ts index fb68f5b..9d7b583 100644 --- a/hooks/community/useCreateCommunity.ts +++ b/hooks/community/useCreateCommunity.ts @@ -5,6 +5,12 @@ import { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import useCustomToast from "../useCustomToast"; +/** + * Creates a new community with validation and a creator snippet transaction. + * @param communityName - Desired community id. + * @param communityType - Privacy type for the community. + * @returns Handler to run creation plus loading and error state. + */ export const useCreateCommunity = () => { const [user] = useAuthState(auth); const [loading, setLoading] = useState(false); diff --git a/hooks/community/useDeleteCommunity.ts b/hooks/community/useDeleteCommunity.ts index ce0cfdd..c2efd33 100644 --- a/hooks/community/useDeleteCommunity.ts +++ b/hooks/community/useDeleteCommunity.ts @@ -15,6 +15,11 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import useCustomToast from "../useCustomToast"; +/** + * Deletes a community and all related posts, comments, votes, and snippets. + * @param communityData - Community to remove. + * @returns Handler that performs the cascade delete and a loading flag. + */ const useDeleteCommunity = (communityData: Community) => { const router = useRouter(); const showToast = useCustomToast(); diff --git a/hooks/community/useJoinCommunity.tsx b/hooks/community/useJoinCommunity.tsx index 5915488..f07a8c1 100644 --- a/hooks/community/useJoinCommunity.tsx +++ b/hooks/community/useJoinCommunity.tsx @@ -7,6 +7,11 @@ import { useSetAtom } from "jotai"; import { useAuthState } from "react-firebase-hooks/auth"; import useCustomToast from "../useCustomToast"; +/** + * Adds the current user to a community and updates member counts and snippets. + * @param communityData - Community the user wants to join. + * @returns Join handler plus loading and error state. + */ const useJoinCommunity = () => { const [user] = useAuthState(auth); const setCommunityStateValue = useSetAtom(communityStateAtom); diff --git a/hooks/community/useLeaveCommunity.tsx b/hooks/community/useLeaveCommunity.tsx index b0279f4..10434db 100644 --- a/hooks/community/useLeaveCommunity.tsx +++ b/hooks/community/useLeaveCommunity.tsx @@ -6,6 +6,11 @@ import { useSetAtom } from "jotai"; import { useAuthState } from "react-firebase-hooks/auth"; import useCustomToast from "../useCustomToast"; +/** + * Removes the current user from a community and decrements its member count. + * @param communityId - Community id to leave. + * @returns Leave handler plus loading and error state. + */ const useLeaveCommunity = () => { const [user] = useAuthState(auth); const setCommunityStateValue = useSetAtom(communityStateAtom); diff --git a/hooks/posts/useCallCreatePost.tsx b/hooks/posts/useCallCreatePost.tsx index e4a5ea5..de0f628 100644 --- a/hooks/posts/useCallCreatePost.tsx +++ b/hooks/posts/useCallCreatePost.tsx @@ -5,6 +5,10 @@ import { useRouter, useParams } from "next/navigation"; import { useAuthState } from "react-firebase-hooks/auth"; import useDirectory from "../useDirectory"; +/** + * Handles clicks on "Create Post" by gating on auth and routing to the right submit page. + * @returns Click handler that opens auth modal, navigates, or toggles the directory menu. + */ const useCallCreatePost = () => { const router = useRouter(); const params = useParams(); diff --git a/hooks/posts/useCreatePost.ts b/hooks/posts/useCreatePost.ts index 8a6d7d9..ee701c4 100644 --- a/hooks/posts/useCreatePost.ts +++ b/hooks/posts/useCreatePost.ts @@ -14,6 +14,15 @@ import { firestore, storage } from "@/firebase/clientApp"; import { Post } from "@/types/post"; import useCustomToast from "../useCustomToast"; +/** + * Creates a new post, uploads an optional image, and notifies the user. + * @param user - Authenticated user creating the post. + * @param communityId - Community where the post belongs. + * @param communityImageURL - Optional community icon to store with the post. + * @param postData - Title and body content for the post. + * @param selectedFile - Optional base64 image to upload. + * @returns Handler to submit a post plus loading and error flags. + */ const useCreatePost = () => { const router = useRouter(); const showToast = useCustomToast(); diff --git a/hooks/posts/usePostDeletion.ts b/hooks/posts/usePostDeletion.ts index 759126a..9982be3 100644 --- a/hooks/posts/usePostDeletion.ts +++ b/hooks/posts/usePostDeletion.ts @@ -15,6 +15,11 @@ import { deleteObject, ref } from "firebase/storage"; import { useAtom, useSetAtom } from "jotai"; import React from "react"; +/** + * Deletes posts along with their assets and related saved entries while keeping state in sync. + * @param setPostStateValue - Setter for updating post state after deletion attempts. + * @returns Handler to delete a post and the current post state snapshot. + */ const usePostDeletion = ( setPostStateValue: React.Dispatch< React.SetStateAction<{ diff --git a/hooks/posts/usePostSelection.ts b/hooks/posts/usePostSelection.ts index 54a374d..8222a48 100644 --- a/hooks/posts/usePostSelection.ts +++ b/hooks/posts/usePostSelection.ts @@ -1,6 +1,11 @@ import { Post, PostVote } from "@/types/post"; import { useRouter } from "next/navigation"; +/** + * Tracks the currently selected post and navigates to its comment page. + * @param setPostStateValue - Setter for updating the selected post in global state. + * @returns Handler to select a post and push the comment route. + */ const usePostSelection = ( setPostStateValue: React.Dispatch< React.SetStateAction<{ diff --git a/hooks/posts/usePostState.ts b/hooks/posts/usePostState.ts index 832311e..49dcc92 100644 --- a/hooks/posts/usePostState.ts +++ b/hooks/posts/usePostState.ts @@ -1,6 +1,10 @@ import { postStateAtom } from "@/atoms/postsAtom"; import { useAtom } from "jotai"; +/** + * Convenience hook for accessing and updating the global post atom. + * @returns Current post state and setter for mutations. + */ const usePostState = () => { const [postStateValue, setPostStateValue] = useAtom(postStateAtom); diff --git a/hooks/posts/usePostVote.tsx b/hooks/posts/usePostVote.tsx index 1dfbc50..0c12c8a 100644 --- a/hooks/posts/usePostVote.tsx +++ b/hooks/posts/usePostVote.tsx @@ -25,6 +25,12 @@ type SetPostState = React.Dispatch< }> >; +/** + * Manages voting interactions on posts and keeps local vote state aligned with Firestore. + * @param postStateValue - Current post state containing posts and cached votes. + * @param setPostStateValue - Setter used to update posts, votes, and selected post. + * @returns Handlers to vote on a post, load votes for a set of posts, or fetch a single post. + */ const usePostVote = ( postStateValue: { selectedPost: Post | null; diff --git a/hooks/posts/usePostVoteSync.ts b/hooks/posts/usePostVoteSync.ts index 78b3522..33a14b9 100644 --- a/hooks/posts/usePostVoteSync.ts +++ b/hooks/posts/usePostVoteSync.ts @@ -15,6 +15,11 @@ type SetPostState = React.Dispatch< }> >; +/** + * Keeps the local post vote cache in sync with the signed-in user's community votes. + * @param setPostStateValue - Setter for the post atom to store fetched votes. + * @returns Subscribes to auth and community changes to refresh vote data. + */ const usePostVoteSync = (setPostStateValue: SetPostState) => { const [user] = useAuthState(auth); const currentCommunity = useAtomValue(communityStateAtom).currentCommunity; diff --git a/hooks/posts/usePostsFeed.ts b/hooks/posts/usePostsFeed.ts index 1a5c915..1d24876 100644 --- a/hooks/posts/usePostsFeed.ts +++ b/hooks/posts/usePostsFeed.ts @@ -24,6 +24,13 @@ type UsePostsFeedProps = { isGenericHome?: boolean; }; +/** + * Fetches paginated posts for a community or the generic home feed with infinite scroll support. + * @param communityId - Single community id to scope the feed. + * @param communityIds - List of community ids to aggregate into the feed. + * @param isGenericHome - Whether to sort by vote status for the default home view. + * @returns Loading flag, sentinel ref, no-more-posts flag, and a fetch function. + */ const usePostsFeed = ({ communityId, communityIds, diff --git a/hooks/posts/useSavedPosts.tsx b/hooks/posts/useSavedPosts.tsx index 7a6d0d1..2ef48f9 100644 --- a/hooks/posts/useSavedPosts.tsx +++ b/hooks/posts/useSavedPosts.tsx @@ -15,6 +15,12 @@ import { useEffect, useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import useCustomToast from "../useCustomToast"; +/** + * Manages a user's saved posts collection and related UI state. + * @param post - Post to save or unsave. + * @param postId - Identifier of the saved post entry to remove or check. + * @returns Saved post state, loading flag, and handlers to fetch, toggle, or check saves. + */ const useSavedPosts = () => { const [user] = useAuthState(auth); const [savedPostState, setSavedPostState] = useAtom(savedPostStateAtom); diff --git a/hooks/useCustomToast.ts b/hooks/useCustomToast.ts index 074eadd..405a3dd 100644 --- a/hooks/useCustomToast.ts +++ b/hooks/useCustomToast.ts @@ -1,12 +1,6 @@ import { createToaster } from "@chakra-ui/react"; import { useCallback } from "react"; -/** - * Interface for the options of the toast. - * @property {string} title - title of the toast - * @property {string} description - description of the toast - * @property {"success" | "error" | "warning" | "info"} status - status of the toast - */ interface CustomToastOptions { title: string; description?: string; @@ -19,14 +13,11 @@ export const toaster = createToaster({ }); /** - * Displays a toast with the given options. - * Depending on the status, the toast will have a different color. - * There are 4 types of status: success, error, warning, info. - * @param {string} title - title of the toast - * @param {string} description - description of the toast - * @param {"success" | "error" | "warning" | "info"} status - status of the toast - * - * @returns {function} - function which shows a toast + * Returns a memoized helper to show consistent Chakra toasts. + * @param title - Heading shown in the toast. + * @param description - Optional supporting text. + * @param status - One of success, error, warning, or info to pick styling. + * @returns Function that triggers a toast with standard options. */ const useCustomToast = () => { const showToast = useCallback( diff --git a/hooks/useDirectory.tsx b/hooks/useDirectory.tsx index 5238bff..de5908f 100644 --- a/hooks/useDirectory.tsx +++ b/hooks/useDirectory.tsx @@ -8,11 +8,8 @@ import { useEffect } from "react"; import { IoPeopleCircleOutline } from "react-icons/io5"; /** - * Hook for managing the directory menu from various components. - * Functionality includes: - * - Selecting a community from the directory menu - * - Toggling the directory menu open or closed - * @returns {DirectoryMenuState} directoryState - object containing the current directory state + * Manages the navbar directory menu, syncing it with routing and community context. + * @returns Current directory state plus helpers to toggle the menu and pick items. */ const useDirectory = () => { const [directoryState, setDirectoryState] = useAtom(directoryMenuAtom); @@ -21,19 +18,17 @@ const useDirectory = () => { const communityStateValue = useAtomValue(communityStateAtom); /** - * Allows the user to select a menu item from the directory menu. - * If the user is already on the page that the menu item links to, then the menu will close. - * If the user is not on the page that the menu item links to, then the user will be redirected to the page. - * @param {DirectoryMenuItem} menuItem - object representing the menu item that was selected + * Updates the selected menu item and routes to the chosen page. + * @param menuItem - Item the user clicked in the directory list. + * @returns Closes the menu if it was open and navigates to the link. */ const onSelectMenuItem = (menuItem: DirectoryMenuItem) => { - // updates the selected menu item on state setDirectoryState((prev) => ({ ...prev, selectedMenuItem: menuItem, - })); // set the selected menu item on state + })); - router.push(menuItem.link); // redirect the user to the page + router.push(menuItem.link); if (directoryState.isOpen) { setDirectoryOpen(false); } @@ -41,7 +36,7 @@ const useDirectory = () => { /** * Toggles the directory menu open or closed. - * If the menu is open, then the menu will close. + * @param isOpen - Whether the menu should be open. */ const setDirectoryOpen = (isOpen: boolean) => { setDirectoryState((prev) => ({ diff --git a/hooks/useIntersectionObserver.ts b/hooks/useIntersectionObserver.ts index 27dda96..9595cc7 100644 --- a/hooks/useIntersectionObserver.ts +++ b/hooks/useIntersectionObserver.ts @@ -1,5 +1,10 @@ import { useEffect, useRef, useState } from "react"; +/** + * Observes when a sentinel element enters the viewport to drive infinite lists. + * @param options - IntersectionObserver options passed to the browser API. + * @returns Ref to attach to the sentinel element and a flag for intersection state. + */ export const useIntersectionObserver = (options?: IntersectionObserverInit) => { const [isIntersecting, setIsIntersecting] = useState(false); const ref = useRef(null); diff --git a/hooks/useSearch.tsx b/hooks/useSearch.tsx index 422f1b3..da1a43a 100644 --- a/hooks/useSearch.tsx +++ b/hooks/useSearch.tsx @@ -11,6 +11,11 @@ import { firestore } from "@/firebase/clientApp"; import { Community } from "@/types/community"; import { Post } from "@/types/post"; +/** + * Client-side search hook that preloads public communities and recent posts. + * @param searchTerm - Text input from the search field. + * @returns Filtered communities and posts along with a loading flag. + */ const useSearch = (searchTerm: string) => { const [results, setResults] = useState<{ communities: Community[]; diff --git a/hooks/useSelectFile.tsx b/hooks/useSelectFile.tsx index b70d098..d1489a5 100644 --- a/hooks/useSelectFile.tsx +++ b/hooks/useSelectFile.tsx @@ -2,27 +2,15 @@ import React, { useState } from "react"; import useCustomToast from "./useCustomToast"; /** - * Hook provides functionality to select a file. - * The file size limit is 10MB and only allows image file types. - * The file being uploaded must be an image. - * The file must be within the specified dimensions. - * @param {number} maxHeight - maximum height of the image - * @param {number} maxWidth - maximum width of the image - * - * @returns {string} selectedFile - the selected file after upload - * @returns {() => void} setSelectedFile - function to set the selected file - * @returns {() => void} onSelectFile - function to select a file from user's system + * Validates and converts a chosen image into a base64 string under size and dimension limits. + * @param maxHeight - Maximum allowed image height. + * @param maxWidth - Maximum allowed image width. + * @returns Selected image data plus helpers to update or handle file input changes. */ const useSelectFile = (maxHeight: number, maxWidth: number) => { const [selectedFile, setSelectedFile] = useState(); const showToast = useCustomToast(); - /** - * Allows user to select a file. - * The file size limit is 10MB and only allows image file types. - * The file being uploaded must be an image. - * @param {React.ChangeEvent} event - event object - */ const onSelectFile = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; // get the first file const maxImageSize = 10; // 10MB diff --git a/hooks/useUserProfile.ts b/hooks/useUserProfile.ts index df2adfd..2ad141d 100644 --- a/hooks/useUserProfile.ts +++ b/hooks/useUserProfile.ts @@ -10,6 +10,12 @@ import { useState } from "react"; import { useAuthState, useUpdateProfile } from "react-firebase-hooks/auth"; import useCustomToast from "./useCustomToast"; +/** + * Handles profile updates and keeps post state in sync with new user info. + * @param selectedFile - Base64 image string to upload as the new avatar. + * @param userName - New display name to apply across comments and posts. + * @returns Actions for updating image or name plus a loading flag. + */ const useUserProfile = () => { const [user] = useAuthState(auth); const [updateProfile, updating, error] = useUpdateProfile(auth); diff --git a/lib/community/addCommunityAdmin.ts b/lib/community/addCommunityAdmin.ts index b56d9ad..8f58c12 100644 --- a/lib/community/addCommunityAdmin.ts +++ b/lib/community/addCommunityAdmin.ts @@ -4,10 +4,10 @@ import { arrayUnion, doc, increment, runTransaction } from "firebase/firestore"; /** * Adds a user as an admin to a community. * Creates a community snippet for the user if they aren't a member yet. - * @param communityId - The community ID - * @param userId - The user ID to make admin - * @param communityImageURL - The community image URL (for snippet creation) - * @returns Promise + * @param communityId - Community to update. + * @param userId - User to promote. + * @param communityImageURL - Image URL stored on the snippet if needed. + * @returns Resolves when the transaction completes. */ export const addCommunityAdmin = async ( communityId: string, diff --git a/lib/community/fetchCommunityAdmins.ts b/lib/community/fetchCommunityAdmins.ts index 993ea13..d845f66 100644 --- a/lib/community/fetchCommunityAdmins.ts +++ b/lib/community/fetchCommunityAdmins.ts @@ -6,9 +6,9 @@ import { AdminUser } from "../../types/adminUser"; /** * Fetches all admins for a community. * Includes the community creator and all additional admins. - * @param creatorId - The UID of the community creator - * @param adminIds - Array of admin UIDs (not including the creator) - * @returns Promise - Array of admin users + * @param creatorId - UID of the community creator. + * @param adminIds - Admin UIDs stored on the community document. + * @returns Array of admin users pulled from Firestore. */ export const fetchCommunityAdmins = async ( creatorId: string, diff --git a/lib/community/fetchCommunityMembers.ts b/lib/community/fetchCommunityMembers.ts index e28bc45..d8ccc21 100644 --- a/lib/community/fetchCommunityMembers.ts +++ b/lib/community/fetchCommunityMembers.ts @@ -3,8 +3,9 @@ import { collection, doc, getDoc, getDocs } from "firebase/firestore"; import { CommunityMember } from "@/types/communityMember"; /** - * Fetches all users that belong to a community by checking the - * community snippets stored under each user. + * Fetches users that hold a snippet for the target community. + * @param communityId - Community id to match in each user's snippets. + * @returns Sorted list of members with email and display name. */ export const fetchCommunityMembers = async ( communityId: string diff --git a/lib/community/findUserByEmail.ts b/lib/community/findUserByEmail.ts index d804c9b..792e99d 100644 --- a/lib/community/findUserByEmail.ts +++ b/lib/community/findUserByEmail.ts @@ -5,8 +5,8 @@ import { AdminUser } from "../../types/adminUser"; /** * Finds a user by their exact email address. - * @param email - The exact email to search for - * @returns Promise - The user if found, null otherwise + * @param email - Exact email to search for. + * @returns Matching user when found, otherwise null. */ export const findUserByEmail = async ( email: string diff --git a/lib/community/getCommunityData.ts b/lib/community/getCommunityData.ts index 824c9d8..f1deb28 100644 --- a/lib/community/getCommunityData.ts +++ b/lib/community/getCommunityData.ts @@ -2,6 +2,11 @@ import { firestore } from "@/firebase/clientApp"; import { doc, getDoc } from "firebase/firestore"; import safeJsonStringify from "safe-json-stringify"; +/** + * Retrieves community data by id with JSON-safe serialization. + * @param communityId - Id of the community to fetch. + * @returns Community object or null if it does not exist. + */ export async function getCommunityData(communityId: string) { try { const communityDocRef = doc(firestore, "communities", communityId); diff --git a/lib/community/removeCommunityAdmin.ts b/lib/community/removeCommunityAdmin.ts index a097217..1766121 100644 --- a/lib/community/removeCommunityAdmin.ts +++ b/lib/community/removeCommunityAdmin.ts @@ -4,9 +4,9 @@ import { arrayRemove, doc, getDoc, writeBatch } from "firebase/firestore"; /** * Removes a user as an admin from a community. * Keeps them as a member but removes admin privileges. - * @param communityId - The community ID - * @param userId - The user ID to remove as admin - * @returns Promise + * @param communityId - Community identifier. + * @param userId - User to demote. + * @returns Resolves when admin role has been removed. */ export const removeCommunityAdmin = async ( communityId: string, diff --git a/lib/community/searchUsersByEmail.ts b/lib/community/searchUsersByEmail.ts index 490fc31..ae0a028 100644 --- a/lib/community/searchUsersByEmail.ts +++ b/lib/community/searchUsersByEmail.ts @@ -6,8 +6,8 @@ import { AdminUser } from "../../types/adminUser"; /** * Searches for users by email. * Returns up to 5 matching users. - * @param emailQuery - The email search term - * @returns Promise - Array of matching users + * @param emailQuery - Email search term typed by the admin. + * @returns Array of matching users with id and profile info. */ export const searchUsersByEmail = async ( emailQuery: string diff --git a/lib/post/getPost.ts b/lib/post/getPost.ts index 7a3424e..dfb53dd 100644 --- a/lib/post/getPost.ts +++ b/lib/post/getPost.ts @@ -3,6 +3,11 @@ import { doc, getDoc } from "firebase/firestore"; import safeJsonStringify from "safe-json-stringify"; import { Post } from "@/types/post"; +/** + * Fetches a post by id from Firestore and returns a JSON-safe object. + * @param postId - Identifier of the post to retrieve. + * @returns Post data or null when missing or on error. + */ export async function getPost(postId: string) { try { const postDocRef = doc(firestore, "posts", postId); diff --git a/lib/validation.ts b/lib/validation.ts index 83da101..46c40ef 100644 --- a/lib/validation.ts +++ b/lib/validation.ts @@ -1,3 +1,9 @@ +/** + * Validates password strength and match for the signup form. + * @param form.password - Raw password input. + * @param form.confirmPassword - Confirmation field to compare. + * @returns Error message when invalid, otherwise null. + */ export const validateSignupForm = (form: { password: string; confirmPassword: string; diff --git a/types/adminUser.ts b/types/adminUser.ts index 2192568..0708d87 100644 --- a/types/adminUser.ts +++ b/types/adminUser.ts @@ -1,3 +1,6 @@ +/** + * Represents a user eligible for admin roles within a community. + */ export type AdminUser = { uid: string; email: string; diff --git a/types/authModal.ts b/types/authModal.ts index 47b8c31..b781085 100644 --- a/types/authModal.ts +++ b/types/authModal.ts @@ -1,3 +1,6 @@ +/** + * Stores UI state for the authentication modal and its active screen. + */ export interface AuthModalState { open: boolean; view: "login" | "signup" | "resetPassword"; diff --git a/types/comment.ts b/types/comment.ts index 407cb0d..0176ae8 100644 --- a/types/comment.ts +++ b/types/comment.ts @@ -1,5 +1,8 @@ import { Timestamp } from "firebase/firestore"; +/** + * Comment document for a post, including optional parent for threaded replies. + */ export type Comment = { id: string; creatorId: string; diff --git a/types/community.ts b/types/community.ts index 58e6efb..f1428c9 100644 --- a/types/community.ts +++ b/types/community.ts @@ -1,5 +1,9 @@ import { Timestamp } from "firebase/firestore"; +/** + * Firestore community document used to render pages and determine permissions. + * Tracks ownership, privacy, member count, and optional branding. + */ export interface Community { id: string; creatorId: string; @@ -10,6 +14,10 @@ export interface Community { adminIds?: string[]; } +/** + * Lightweight user-scoped record that links a user to a community. + * Used for navigation menus, permissions, and quick access to membership info. + */ export interface CommunitySnippet { communityId: string; isAdmin?: boolean; diff --git a/types/communityMember.ts b/types/communityMember.ts index fe01a63..7e16ad9 100644 --- a/types/communityMember.ts +++ b/types/communityMember.ts @@ -1,3 +1,6 @@ +/** + * Represents a user returned when listing community members or admins. + */ export type CommunityMember = { uid: string; email: string; diff --git a/types/directoryMenu.ts b/types/directoryMenu.ts index 573866d..a65e421 100644 --- a/types/directoryMenu.ts +++ b/types/directoryMenu.ts @@ -1,5 +1,8 @@ import { IconType } from "react-icons"; +/** + * Item used in the navbar directory dropdown to route between communities or home. + */ export type DirectoryMenuItem = { displayText: string; link: string; diff --git a/types/post.ts b/types/post.ts index 0c0b079..e20eca2 100644 --- a/types/post.ts +++ b/types/post.ts @@ -1,5 +1,9 @@ import { Timestamp } from "firebase/firestore"; +/** + * Shape of a post document stored in Firestore and consumed by the UI. + * Represents the core content, metadata, and optional image for a community post. + */ export type Post = { id?: string; communityId: string; @@ -14,6 +18,10 @@ export type Post = { createTime: Timestamp; }; +/** + * Represents a user's vote record on a post for syncing client and server state. + * Stored per user to track whether they upvoted or downvoted a given post. + */ export type PostVote = { id: string; postId: string; diff --git a/types/savedPost.ts b/types/savedPost.ts index 5a40d9a..4c05504 100644 --- a/types/savedPost.ts +++ b/types/savedPost.ts @@ -1,3 +1,6 @@ +/** + * Minimal saved post entry stored under a user's document for quick retrieval. + */ export type SavedPost = { id: string; postId: string;