diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..37f4f0a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +.next +next-env.d.ts +node_modules +yarn.lock +package-lock.json +public \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 3db2835..b1fb199 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,5 +5,17 @@ "standard", "plugin:tailwindcss/recommended", "prettier" - ] + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "settings": { + "react": { + "version": "detect" + } + } } diff --git a/.gitignore b/.gitignore index f254efd..641c9ca 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,10 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -# vscode -.vscode +# IDE settings +/.idea/ +/.fleet/ +/.vscode/ # env .env diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1fb48e0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.next +next-env.d.ts +node_modules +yarn.lock +package-lock.json +public diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..cdbc624 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": false, + "tabWidth": 2, + "useTabs": false, + "printWidth": 80 +} diff --git a/README.md b/README.md index 5108a9e..bcefbe5 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ VC/Vine Community developed using Next.js 14+ with a redesigned look transformed šŸ‘‰ **Form Management with React Hook Form**: Efficient management of forms with React Hook Form for a streamlined user input experience. -and many more, including code architecture and reusability +and many more, including code architecture and reusability ## šŸ¤ø Quick Start @@ -133,7 +133,7 @@ NEXT_CLERK_WEBHOOK_SECRET= NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= ``` -Replace the placeholder values with your actual credentials. You can obtain these credentials by signing up for the corresponding websites on [MongoDB](https://www.mongodb.com/), [Clerk](https://clerk.com/), and [Uploadthing](https://uploadthing.com/). +Replace the placeholder values with your actual credentials. You can obtain these credentials by signing up for the corresponding websites on [MongoDB](https://www.mongodb.com/), [Clerk](https://clerk.com/), and [Uploadthing](https://uploadthing.com/). **Running the Project** @@ -155,19 +155,19 @@ Open [http://localhost:3000](http://localhost:3000) in your browser to view the // Resource: https://docs.svix.com/receiving/verifying-payloads/why // It's a good practice to verify webhooks. Above article shows why we should do it -import { Webhook, WebhookRequiredHeaders } from "svix"; -import { headers } from "next/headers"; +import { Webhook, WebhookRequiredHeaders } from "svix" +import { headers } from "next/headers" -import { IncomingHttpHeaders } from "http"; +import { IncomingHttpHeaders } from "http" -import { NextResponse } from "next/server"; +import { NextResponse } from "next/server" import { addMemberToCommunity, createCommunity, deleteCommunity, removeUserFromCommunity, updateCommunityInfo, -} from "@/lib/actions/community.actions"; +} from "@/lib/actions/community.actions" // Resource: https://clerk.com/docs/integration/webhooks#supported-events // Above document lists the supported events @@ -177,47 +177,46 @@ type EventType = | "organizationMembership.created" | "organizationMembership.deleted" | "organization.updated" - | "organization.deleted"; + | "organization.deleted" type Event = { - data: Record[]>; - object: "event"; - type: EventType; -}; + data: Record[]> + object: "event" + type: EventType +} export const POST = async (request: Request) => { - const payload = await request.json(); - const header = headers(); + const payload = await request.json() + const header = headers() const heads = { "svix-id": header.get("svix-id"), "svix-timestamp": header.get("svix-timestamp"), "svix-signature": header.get("svix-signature"), - }; + } // Activitate Webhook in the Clerk Dashboard. // After adding the endpoint, you'll see the secret on the right side. - const wh = new Webhook(process.env.NEXT_CLERK_WEBHOOK_SECRET || ""); + const wh = new Webhook(process.env.NEXT_CLERK_WEBHOOK_SECRET || "") - let evnt: Event | null = null; + let evnt: Event | null = null try { evnt = wh.verify( JSON.stringify(payload), heads as IncomingHttpHeaders & WebhookRequiredHeaders - ) as Event; + ) as Event } catch (err) { - return NextResponse.json({ message: err }, { status: 400 }); + return NextResponse.json({ message: err }, { status: 400 }) } - const eventType: EventType = evnt?.type!; + const eventType: EventType = evnt?.type! // Listen organization creation event if (eventType === "organization.created") { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/CreateOrganization // Show what evnt?.data sends from above resource - const { id, name, slug, logo_url, image_url, created_by } = - evnt?.data ?? {}; + const { id, name, slug, logo_url, image_url, created_by } = evnt?.data ?? {} try { // @ts-ignore @@ -229,15 +228,15 @@ export const POST = async (request: Request) => { logo_url || image_url, "org bio", created_by - ); + ) - return NextResponse.json({ message: "User created" }, { status: 201 }); + return NextResponse.json({ message: "User created" }, { status: 201 }) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } @@ -247,19 +246,19 @@ export const POST = async (request: Request) => { if (eventType === "organizationInvitation.created") { try { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Invitations#operation/CreateOrganizationInvitation - console.log("Invitation created", evnt?.data); + console.log("Invitation created", evnt?.data) return NextResponse.json( { message: "Invitation created" }, { status: 201 } - ); + ) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } @@ -268,23 +267,23 @@ export const POST = async (request: Request) => { try { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Memberships#operation/CreateOrganizationMembership // Show what evnt?.data sends from above resource - const { organization, public_user_data } = evnt?.data; - console.log("created", evnt?.data); + const { organization, public_user_data } = evnt?.data + console.log("created", evnt?.data) // @ts-ignore - await addMemberToCommunity(organization.id, public_user_data.user_id); + await addMemberToCommunity(organization.id, public_user_data.user_id) return NextResponse.json( { message: "Invitation accepted" }, { status: 201 } - ); + ) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } @@ -293,20 +292,20 @@ export const POST = async (request: Request) => { try { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Memberships#operation/DeleteOrganizationMembership // Show what evnt?.data sends from above resource - const { organization, public_user_data } = evnt?.data; - console.log("removed", evnt?.data); + const { organization, public_user_data } = evnt?.data + console.log("removed", evnt?.data) // @ts-ignore - await removeUserFromCommunity(public_user_data.user_id, organization.id); + await removeUserFromCommunity(public_user_data.user_id, organization.id) - return NextResponse.json({ message: "Member removed" }, { status: 201 }); + return NextResponse.json({ message: "Member removed" }, { status: 201 }) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } @@ -315,20 +314,20 @@ export const POST = async (request: Request) => { try { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/UpdateOrganization // Show what evnt?.data sends from above resource - const { id, logo_url, name, slug } = evnt?.data; - console.log("updated", evnt?.data); + const { id, logo_url, name, slug } = evnt?.data + console.log("updated", evnt?.data) // @ts-ignore - await updateCommunityInfo(id, name, slug, logo_url); + await updateCommunityInfo(id, name, slug, logo_url) - return NextResponse.json({ message: "Member removed" }, { status: 201 }); + return NextResponse.json({ message: "Member removed" }, { status: 201 }) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } @@ -337,26 +336,26 @@ export const POST = async (request: Request) => { try { // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/DeleteOrganization // Show what evnt?.data sends from above resource - const { id } = evnt?.data; - console.log("deleted", evnt?.data); + const { id } = evnt?.data + console.log("deleted", evnt?.data) // @ts-ignore - await deleteCommunity(id); + await deleteCommunity(id) return NextResponse.json( { message: "Organization deleted" }, { status: 201 } - ); + ) } catch (err) { - console.log(err); + console.log(err) return NextResponse.json( { message: "Internal Server Error" }, { status: 500 } - ); + ) } } -}; +} ``` @@ -365,15 +364,15 @@ export const POST = async (request: Request) => { community.actions.ts ```typescript -"use server"; +"use server" -import { FilterQuery, SortOrder } from "mongoose"; +import { FilterQuery, SortOrder } from "mongoose" -import Community from "../models/community.model"; -import Thread from "../models/thread.model"; -import User from "../models/user.model"; +import Community from "../models/community.model" +import Thread from "../models/thread.model" +import User from "../models/user.model" -import { connectToDB } from "../mongoose"; +import { connectToDB } from "../mongoose" export async function createCommunity( id: string, @@ -384,13 +383,13 @@ export async function createCommunity( createdById: string // Change the parameter name to reflect it's an id ) { try { - connectToDB(); + connectToDB() // Find the user with the provided unique id - const user = await User.findOne({ id: createdById }); + const user = await User.findOne({ id: createdById }) if (!user) { - throw new Error("User not found"); // Handle the case if the user with the id is not found + throw new Error("User not found") // Handle the case if the user with the id is not found } const newCommunity = new Community({ @@ -400,25 +399,25 @@ export async function createCommunity( image, bio, createdBy: user._id, // Use the mongoose ID of the user - }); + }) - const createdCommunity = await newCommunity.save(); + const createdCommunity = await newCommunity.save() // Update User model - user.communities.push(createdCommunity._id); - await user.save(); + user.communities.push(createdCommunity._id) + await user.save() - return createdCommunity; + return createdCommunity } catch (error) { // Handle any errors - console.error("Error creating community:", error); - throw error; + console.error("Error creating community:", error) + throw error } } export async function fetchCommunityDetails(id: string) { try { - connectToDB(); + connectToDB() const communityDetails = await Community.findOne({ id }).populate([ "createdBy", @@ -427,19 +426,19 @@ export async function fetchCommunityDetails(id: string) { model: User, select: "name username image _id id", }, - ]); + ]) - return communityDetails; + return communityDetails } catch (error) { // Handle any errors - console.error("Error fetching community details:", error); - throw error; + console.error("Error fetching community details:", error) + throw error } } export async function fetchCommunityPosts(id: string) { try { - connectToDB(); + connectToDB() const communityPosts = await Community.findById(id).populate({ path: "threads", @@ -460,13 +459,13 @@ export async function fetchCommunityPosts(id: string) { }, }, ], - }); + }) - return communityPosts; + return communityPosts } catch (error) { // Handle any errors - console.error("Error fetching community posts:", error); - throw error; + console.error("Error fetching community posts:", error) + throw error } } @@ -476,53 +475,50 @@ export async function fetchCommunities({ pageSize = 20, sortBy = "desc", }: { - searchString?: string; - pageNumber?: number; - pageSize?: number; - sortBy?: SortOrder; + searchString?: string + pageNumber?: number + pageSize?: number + sortBy?: SortOrder }) { try { - connectToDB(); + connectToDB() // Calculate the number of communities to skip based on the page number and page size. - const skipAmount = (pageNumber - 1) * pageSize; + const skipAmount = (pageNumber - 1) * pageSize // Create a case-insensitive regular expression for the provided search string. - const regex = new RegExp(searchString, "i"); + const regex = new RegExp(searchString, "i") // Create an initial query object to filter communities. - const query: FilterQuery = {}; + const query: FilterQuery = {} // If the search string is not empty, add the $or operator to match either username or name fields. if (searchString.trim() !== "") { - query.$or = [ - { username: { $regex: regex } }, - { name: { $regex: regex } }, - ]; + query.$or = [{ username: { $regex: regex } }, { name: { $regex: regex } }] } // Define the sort options for the fetched communities based on createdAt field and provided sort order. - const sortOptions = { createdAt: sortBy }; + const sortOptions = { createdAt: sortBy } // Create a query to fetch the communities based on the search and sort criteria. const communitiesQuery = Community.find(query) .sort(sortOptions) .skip(skipAmount) .limit(pageSize) - .populate("members"); + .populate("members") // Count the total number of communities that match the search criteria (without pagination). - const totalCommunitiesCount = await Community.countDocuments(query); + const totalCommunitiesCount = await Community.countDocuments(query) - const communities = await communitiesQuery.exec(); + const communities = await communitiesQuery.exec() // Check if there are more communities beyond the current page. - const isNext = totalCommunitiesCount > skipAmount + communities.length; + const isNext = totalCommunitiesCount > skipAmount + communities.length - return { communities, isNext }; + return { communities, isNext } } catch (error) { - console.error("Error fetching communities:", error); - throw error; + console.error("Error fetching communities:", error) + throw error } } @@ -531,40 +527,40 @@ export async function addMemberToCommunity( memberId: string ) { try { - connectToDB(); + connectToDB() // Find the community by its unique id - const community = await Community.findOne({ id: communityId }); + const community = await Community.findOne({ id: communityId }) if (!community) { - throw new Error("Community not found"); + throw new Error("Community not found") } // Find the user by their unique id - const user = await User.findOne({ id: memberId }); + const user = await User.findOne({ id: memberId }) if (!user) { - throw new Error("User not found"); + throw new Error("User not found") } // Check if the user is already a member of the community if (community.members.includes(user._id)) { - throw new Error("User is already a member of the community"); + throw new Error("User is already a member of the community") } // Add the user's _id to the members array in the community - community.members.push(user._id); - await community.save(); + community.members.push(user._id) + await community.save() // Add the community's _id to the communities array in the user - user.communities.push(community._id); - await user.save(); + user.communities.push(community._id) + await user.save() - return community; + return community } catch (error) { // Handle any errors - console.error("Error adding member to community:", error); - throw error; + console.error("Error adding member to community:", error) + throw error } } @@ -573,39 +569,39 @@ export async function removeUserFromCommunity( communityId: string ) { try { - connectToDB(); + connectToDB() - const userIdObject = await User.findOne({ id: userId }, { _id: 1 }); + const userIdObject = await User.findOne({ id: userId }, { _id: 1 }) const communityIdObject = await Community.findOne( { id: communityId }, { _id: 1 } - ); + ) if (!userIdObject) { - throw new Error("User not found"); + throw new Error("User not found") } if (!communityIdObject) { - throw new Error("Community not found"); + throw new Error("Community not found") } // Remove the user's _id from the members array in the community await Community.updateOne( { _id: communityIdObject._id }, { $pull: { members: userIdObject._id } } - ); + ) // Remove the community's _id from the communities array in the user await User.updateOne( { _id: userIdObject._id }, { $pull: { communities: communityIdObject._id } } - ); + ) - return { success: true }; + return { success: true } } catch (error) { // Handle any errors - console.error("Error removing user from community:", error); - throw error; + console.error("Error removing user from community:", error) + throw error } } @@ -616,57 +612,57 @@ export async function updateCommunityInfo( image: string ) { try { - connectToDB(); + connectToDB() // Find the community by its _id and update the information const updatedCommunity = await Community.findOneAndUpdate( { id: communityId }, { name, username, image } - ); + ) if (!updatedCommunity) { - throw new Error("Community not found"); + throw new Error("Community not found") } - return updatedCommunity; + return updatedCommunity } catch (error) { // Handle any errors - console.error("Error updating community information:", error); - throw error; + console.error("Error updating community information:", error) + throw error } } export async function deleteCommunity(communityId: string) { try { - connectToDB(); + connectToDB() // Find the community by its ID and delete it const deletedCommunity = await Community.findOneAndDelete({ id: communityId, - }); + }) if (!deletedCommunity) { - throw new Error("Community not found"); + throw new Error("Community not found") } // Delete all threads associated with the community - await Thread.deleteMany({ community: communityId }); + await Thread.deleteMany({ community: communityId }) // Find all users who are part of the community - const communityUsers = await User.find({ communities: communityId }); + const communityUsers = await User.find({ communities: communityId }) // Remove the community from the 'communities' array for each user const updateUserPromises = communityUsers.map((user) => { - user.communities.pull(communityId); - return user.save(); - }); + user.communities.pull(communityId) + return user.save() + }) - await Promise.all(updateUserPromises); + await Promise.all(updateUserPromises) - return deletedCommunity; + return deletedCommunity } catch (error) { - console.error("Error deleting community: ", error); - throw error; + console.error("Error deleting community: ", error) + throw error } } ``` @@ -789,19 +785,19 @@ export const sidebarLinks = [ route: "/profile", label: "Profile", }, -]; +] export const profileTabs = [ { value: "threads", label: "Threads", icon: "/assets/reply.svg" }, { value: "replies", label: "Replies", icon: "/assets/members.svg" }, { value: "tagged", label: "Tagged", icon: "/assets/tag.svg" }, -]; +] export const communityTabs = [ { value: "threads", label: "Threads", icon: "/assets/reply.svg" }, { value: "members", label: "Members", icon: "/assets/members.svg" }, { value: "requests", label: "Requests", icon: "/assets/request.svg" }, -]; +] ``` @@ -1008,9 +1004,9 @@ const nextConfig = { ignoreBuildErrors: true, }, }, -}; +} -module.exports = nextConfig; +module.exports = nextConfig ``` @@ -1234,7 +1230,7 @@ module.exports = { }, }, plugins: [require("tailwindcss-animate")], -}; +} ``` @@ -1243,21 +1239,21 @@ module.exports = { thread.actions.ts ```typescript -"use server"; +"use server" -import { revalidatePath } from "next/cache"; +import { revalidatePath } from "next/cache" -import { connectToDB } from "../mongoose"; +import { connectToDB } from "../mongoose" -import User from "../models/user.model"; -import Thread from "../models/thread.model"; -import Community from "../models/community.model"; +import User from "../models/user.model" +import Thread from "../models/thread.model" +import Community from "../models/community.model" export async function fetchPosts(pageNumber = 1, pageSize = 20) { - connectToDB(); + connectToDB() // Calculate the number of posts to skip based on the page number and page size. - const skipAmount = (pageNumber - 1) * pageSize; + const skipAmount = (pageNumber - 1) * pageSize // Create a query to fetch the posts that have no parent (top-level threads) (a thread that is not a comment/reply). const postsQuery = Thread.find({ parentId: { $in: [null, undefined] } }) @@ -1279,92 +1275,96 @@ export async function fetchPosts(pageNumber = 1, pageSize = 20) { model: User, select: "_id name parentId image", // Select only _id and username fields of the author }, - }); + }) // Count the total number of top-level posts (threads) i.e., threads that are not comments. const totalPostsCount = await Thread.countDocuments({ parentId: { $in: [null, undefined] }, - }); // Get the total count of posts + }) // Get the total count of posts - const posts = await postsQuery.exec(); + const posts = await postsQuery.exec() - const isNext = totalPostsCount > skipAmount + posts.length; + const isNext = totalPostsCount > skipAmount + posts.length - return { posts, isNext }; + return { posts, isNext } } interface Params { - text: string, - author: string, - communityId: string | null, - path: string, + text: string + author: string + communityId: string | null + path: string } -export async function createThread({ text, author, communityId, path }: Params -) { +export async function createThread({ + text, + author, + communityId, + path, +}: Params) { try { - connectToDB(); + connectToDB() const communityIdObject = await Community.findOne( { id: communityId }, { _id: 1 } - ); + ) const createdThread = await Thread.create({ text, author, community: communityIdObject, // Assign communityId if provided, or leave it null for personal account - }); + }) // Update User model await User.findByIdAndUpdate(author, { $push: { threads: createdThread._id }, - }); + }) if (communityIdObject) { // Update Community model await Community.findByIdAndUpdate(communityIdObject, { $push: { threads: createdThread._id }, - }); + }) } - revalidatePath(path); + revalidatePath(path) } catch (error: any) { - throw new Error(`Failed to create thread: ${error.message}`); + throw new Error(`Failed to create thread: ${error.message}`) } } async function fetchAllChildThreads(threadId: string): Promise { - const childThreads = await Thread.find({ parentId: threadId }); + const childThreads = await Thread.find({ parentId: threadId }) - const descendantThreads = []; + const descendantThreads = [] for (const childThread of childThreads) { - const descendants = await fetchAllChildThreads(childThread._id); - descendantThreads.push(childThread, ...descendants); + const descendants = await fetchAllChildThreads(childThread._id) + descendantThreads.push(childThread, ...descendants) } - return descendantThreads; + return descendantThreads } export async function deleteThread(id: string, path: string): Promise { try { - connectToDB(); + connectToDB() // Find the thread to be deleted (the main thread) - const mainThread = await Thread.findById(id).populate("author community"); + const mainThread = await Thread.findById(id).populate("author community") if (!mainThread) { - throw new Error("Thread not found"); + throw new Error("Thread not found") } // Fetch all child threads and their descendants recursively - const descendantThreads = await fetchAllChildThreads(id); + const descendantThreads = await fetchAllChildThreads(id) // Get all descendant thread IDs including the main thread ID and child thread IDs const descendantThreadIds = [ id, ...descendantThreads.map((thread) => thread._id), - ]; + ] // Extract the authorIds and communityIds to update User and Community models respectively const uniqueAuthorIds = new Set( @@ -1372,38 +1372,38 @@ export async function deleteThread(id: string, path: string): Promise { ...descendantThreads.map((thread) => thread.author?._id?.toString()), // Use optional chaining to handle possible undefined values mainThread.author?._id?.toString(), ].filter((id) => id !== undefined) - ); + ) const uniqueCommunityIds = new Set( [ ...descendantThreads.map((thread) => thread.community?._id?.toString()), // Use optional chaining to handle possible undefined values mainThread.community?._id?.toString(), ].filter((id) => id !== undefined) - ); + ) // Recursively delete child threads and their descendants - await Thread.deleteMany({ _id: { $in: descendantThreadIds } }); + await Thread.deleteMany({ _id: { $in: descendantThreadIds } }) // Update User model await User.updateMany( { _id: { $in: Array.from(uniqueAuthorIds) } }, { $pull: { threads: { $in: descendantThreadIds } } } - ); + ) // Update Community model await Community.updateMany( { _id: { $in: Array.from(uniqueCommunityIds) } }, { $pull: { threads: { $in: descendantThreadIds } } } - ); + ) - revalidatePath(path); + revalidatePath(path) } catch (error: any) { - throw new Error(`Failed to delete thread: ${error.message}`); + throw new Error(`Failed to delete thread: ${error.message}`) } } export async function fetchThreadById(threadId: string) { - connectToDB(); + connectToDB() try { const thread = await Thread.findById(threadId) @@ -1436,12 +1436,12 @@ export async function fetchThreadById(threadId: string) { }, ], }) - .exec(); + .exec() - return thread; + return thread } catch (err) { - console.error("Error while fetching thread:", err); - throw new Error("Unable to fetch thread"); + console.error("Error while fetching thread:", err) + throw new Error("Unable to fetch thread") } } @@ -1451,14 +1451,14 @@ export async function addCommentToThread( userId: string, path: string ) { - connectToDB(); + connectToDB() try { // Find the original thread by its ID - const originalThread = await Thread.findById(threadId); + const originalThread = await Thread.findById(threadId) if (!originalThread) { - throw new Error("Thread not found"); + throw new Error("Thread not found") } // Create the new comment thread @@ -1466,21 +1466,21 @@ export async function addCommentToThread( text: commentText, author: userId, parentId: threadId, // Set the parentId to the original thread's ID - }); + }) // Save the comment thread to the database - const savedCommentThread = await commentThread.save(); + const savedCommentThread = await commentThread.save() // Add the comment thread's ID to the original thread's children array - originalThread.children.push(savedCommentThread._id); + originalThread.children.push(savedCommentThread._id) // Save the updated original thread to the database - await originalThread.save(); + await originalThread.save() - revalidatePath(path); + revalidatePath(path) } catch (err) { - console.error("Error while adding comment:", err); - throw new Error("Unable to add comment"); + console.error("Error while adding comment:", err) + throw new Error("Unable to add comment") } } ``` @@ -1494,11 +1494,12 @@ export async function addCommentToThread( // Resource: https://docs.uploadthing.com/api-reference/react#generatereacthelpers // Copy paste (be careful with imports) -import { generateReactHelpers } from "@uploadthing/react/hooks"; +import { generateReactHelpers } from "@uploadthing/react/hooks" -import type { OurFileRouter } from "@/app/api/uploadthing/core"; +import type { OurFileRouter } from "@/app/api/uploadthing/core" -export const { useUploadThing, uploadFiles } = generateReactHelpers(); +export const { useUploadThing, uploadFiles } = + generateReactHelpers() ``` @@ -1507,37 +1508,37 @@ export const { useUploadThing, uploadFiles } = generateReactHelpersuser.actions.ts ```typescript -"use server"; +"use server" -import { FilterQuery, SortOrder } from "mongoose"; -import { revalidatePath } from "next/cache"; +import { FilterQuery, SortOrder } from "mongoose" +import { revalidatePath } from "next/cache" -import Community from "../models/community.model"; -import Thread from "../models/thread.model"; -import User from "../models/user.model"; +import Community from "../models/community.model" +import Thread from "../models/thread.model" +import User from "../models/user.model" -import { connectToDB } from "../mongoose"; +import { connectToDB } from "../mongoose" export async function fetchUser(userId: string) { try { - connectToDB(); + connectToDB() return await User.findOne({ id: userId }).populate({ path: "communities", model: Community, - }); + }) } catch (error: any) { - throw new Error(`Failed to fetch user: ${error.message}`); + throw new Error(`Failed to fetch user: ${error.message}`) } } interface Params { - userId: string; - username: string; - name: string; - bio: string; - image: string; - path: string; + userId: string + username: string + name: string + bio: string + image: string + path: string } export async function updateUser({ @@ -1549,7 +1550,7 @@ export async function updateUser({ image, }: Params): Promise { try { - connectToDB(); + connectToDB() await User.findOneAndUpdate( { id: userId }, @@ -1561,19 +1562,19 @@ export async function updateUser({ onboarded: true, }, { upsert: true } - ); + ) if (path === "/profile/edit") { - revalidatePath(path); + revalidatePath(path) } } catch (error: any) { - throw new Error(`Failed to create/update user: ${error.message}`); + throw new Error(`Failed to create/update user: ${error.message}`) } } export async function fetchUserPosts(userId: string) { try { - connectToDB(); + connectToDB() // Find all threads authored by the user with the given userId const threads = await User.findOne({ id: userId }).populate({ @@ -1595,11 +1596,11 @@ export async function fetchUserPosts(userId: string) { }, }, ], - }); - return threads; + }) + return threads } catch (error) { - console.error("Error fetching user threads:", error); - throw error; + console.error("Error fetching user threads:", error) + throw error } } @@ -1611,68 +1612,65 @@ export async function fetchUsers({ pageSize = 20, sortBy = "desc", }: { - userId: string; - searchString?: string; - pageNumber?: number; - pageSize?: number; - sortBy?: SortOrder; + userId: string + searchString?: string + pageNumber?: number + pageSize?: number + sortBy?: SortOrder }) { try { - connectToDB(); + connectToDB() // Calculate the number of users to skip based on the page number and page size. - const skipAmount = (pageNumber - 1) * pageSize; + const skipAmount = (pageNumber - 1) * pageSize // Create a case-insensitive regular expression for the provided search string. - const regex = new RegExp(searchString, "i"); + const regex = new RegExp(searchString, "i") // Create an initial query object to filter users. const query: FilterQuery = { id: { $ne: userId }, // Exclude the current user from the results. - }; + } // If the search string is not empty, add the $or operator to match either username or name fields. if (searchString.trim() !== "") { - query.$or = [ - { username: { $regex: regex } }, - { name: { $regex: regex } }, - ]; + query.$or = [{ username: { $regex: regex } }, { name: { $regex: regex } }] } // Define the sort options for the fetched users based on createdAt field and provided sort order. - const sortOptions = { createdAt: sortBy }; + const sortOptions = { createdAt: sortBy } const usersQuery = User.find(query) .sort(sortOptions) .skip(skipAmount) - .limit(pageSize); + .limit(pageSize) // Count the total number of users that match the search criteria (without pagination). - const totalUsersCount = await User.countDocuments(query); + const totalUsersCount = await User.countDocuments(query) - const users = await usersQuery.exec(); + const users = await usersQuery.exec() // Check if there are more users beyond the current page. - const isNext = totalUsersCount > skipAmount + users.length; + const isNext = totalUsersCount > skipAmount + users.length - return { users, isNext }; + return { users, isNext } } catch (error) { - console.error("Error fetching users:", error); - throw error; + console.error("Error fetching users:", error) + throw error } } export async function getActivity(userId: string) { try { - connectToDB(); + connectToDB() // Find all threads created by the user - const userThreads = await Thread.find({ author: userId }); + const userThreads = await Thread.find({ author: userId }) // Collect all the child thread ids (replies) from the 'children' field of each user thread const childThreadIds = userThreads.reduce((acc, userThread) => { - return acc.concat(userThread.children); - }, []); + return acc.concat(userThread.children) + }, []) // Find and return the child threads (replies) excluding the ones created by the same user const replies = await Thread.find({ @@ -1682,12 +1680,12 @@ export async function getActivity(userId: string) { path: "author", model: User, select: "name image _id", - }); + }) - return replies; + return replies } catch (error) { - console.error("Error fetching replies: ", error); - throw error; + console.error("Error fetching replies: ", error) + throw error } } ``` @@ -1698,18 +1696,18 @@ export async function getActivity(userId: string) { utils.ts ```typescript -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" // generated by shadcn export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)) } // created by chatgpt export function isBase64Image(imageData: string) { - const base64Regex = /^data:image\/(png|jpe?g|gif|webp);base64,/; - return base64Regex.test(imageData); + const base64Regex = /^data:image\/(png|jpe?g|gif|webp);base64,/ + return base64Regex.test(imageData) } // created by chatgpt @@ -1718,27 +1716,27 @@ export function formatDateString(dateString: string) { year: "numeric", month: "short", day: "numeric", - }; + } - const date = new Date(dateString); - const formattedDate = date.toLocaleDateString(undefined, options); + const date = new Date(dateString) + const formattedDate = date.toLocaleDateString(undefined, options) const time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", - }); + }) - return `${time} - ${formattedDate}`; + return `${time} - ${formattedDate}` } // created by chatgpt export function formatThreadCount(count: number): string { if (count === 0) { - return "No Threads"; + return "No Threads" } else { - const threadCount = count.toString().padStart(2, "0"); - const threadWord = count === 1 ? "Thread" : "Threads"; - return `${threadCount} ${threadWord}`; + const threadCount = count.toString().padStart(2, "0") + const threadWord = count === 1 ? "Thread" : "Threads" + return `${threadCount} ${threadWord}` } } ``` diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx deleted file mode 100644 index 9acf3d8..0000000 --- a/app/(auth)/layout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type React from 'react' -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' -import { ClerkProvider } from '@clerk/nextjs' -import { dark } from '@clerk/themes' - -import '../globals.css' - -const inter = Inter({ subsets: ['latin'] }) - -export const metadata: Metadata = { - title: 'Entrar', - description: 'Vine Community sign in', -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - - {children} - - - ) -} diff --git a/app/(auth)/onboarding/page.tsx b/app/(auth)/onboarding/page.tsx deleted file mode 100644 index 84b017b..0000000 --- a/app/(auth)/onboarding/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { currentUser } from '@clerk/nextjs' -import { redirect } from 'next/navigation' - -import { fetchUser } from '@/lib/actions/user.actions' -import AccountProfile from '@/components/forms/AccountProfile' - -async function Page() { - const user = await currentUser() - if (!user) return null // to avoid typescript warnings - - const userInfo = await fetchUser(user.id) - if (userInfo?.onboarded) redirect('/') - - const userData = { - id: user.id, - objectId: userInfo?._id, - username: userInfo ? userInfo?.username : user.username, - name: userInfo ? userInfo?.name : user.firstName ?? '', - bio: userInfo ? userInfo?.bio : '', - image: userInfo ? userInfo?.image : user.imageUrl, - } - - return ( -
-

IntegraĆ§Ć£o

-

- Complete o teu perfil agora, para utilizar a Vine Community. -

- -
- -
-
- ) -} - -export default Page diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index 2cc13d4..0000000 --- a/app/(auth)/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SignIn } from "@clerk/nextjs"; - -export default function Page() { - return ; -} diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index 2743945..0000000 --- a/app/(auth)/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SignUp } from "@clerk/nextjs"; - -export default function Page() { - return ; -} diff --git a/app/(root)/activity/page.tsx b/app/(root)/activity/page.tsx deleted file mode 100644 index 8ddf746..0000000 --- a/app/(root)/activity/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Image from 'next/image' -import Link from 'next/link' -import { currentUser } from '@clerk/nextjs' -import { redirect } from 'next/navigation' - -import { fetchUser, getActivity } from '@/lib/actions/user.actions' - -async function Page() { - const user = await currentUser() - if (!user) return null - - const userInfo = await fetchUser(user.id) - if (!userInfo?.onboarded) redirect('/onboarding') - - const activity = await getActivity(userInfo._id) - - return ( - <> -

Actividades

- -
- {activity.length > 0 ? ( - <> - {activity.map((activity) => ( - -
- user_logo -

- {activity.author.name} respondeu a tua thread -

-
- - ))} - - ) : ( -

Nenhuma actividade ainda

- )} -
- - ) -} - -export default Page diff --git a/app/(root)/communities/[id]/page.tsx b/app/(root)/communities/[id]/page.tsx deleted file mode 100644 index 863356b..0000000 --- a/app/(root)/communities/[id]/page.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import Image from 'next/image' -import { currentUser } from '@clerk/nextjs' - -import { communityTabs } from '@/constants' - -import UserCard from '@/components/cards/UserCard' -import ThreadsTab from '@/components/shared/ThreadsTab' -import ProfileHeader from '@/components/shared/ProfileHeader' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' - -import { fetchCommunityDetails } from '@/lib/actions/community.actions' - -async function Page({ params }: { params: { id: string } }) { - const user = await currentUser() - if (!user) return null - - const communityDetails = await fetchCommunityDetails(params.id) - - return ( -
- -
- - - {communityTabs.map((tab) => ( - - {tab.label} -

{tab.label}

- {tab.label === 'Threads' && ( -

- {communityDetails.threads.length} -

- )} -
- ))} -
- - - - -
- {communityDetails.members.map((member: any) => ( - - ))} -
-
- - - -
-
-
- ) -} - -export default Page diff --git a/app/(root)/communities/page.tsx b/app/(root)/communities/page.tsx deleted file mode 100644 index a14fa00..0000000 --- a/app/(root)/communities/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { currentUser } from '@clerk/nextjs' -import { redirect } from 'next/navigation' - -import Searchbar from '@/components/shared/Searchbar' -import Pagination from '@/components/shared/Pagination' -import CommunityCard from '@/components/cards/CommunityCard' - -import { fetchUser } from '@/lib/actions/user.actions' -import { fetchCommunities } from '@/lib/actions/community.actions' - -async function Page({ - searchParams, -}: { - searchParams: { [key: string]: string | undefined } -}) { - const user = await currentUser() - if (!user) return null - - const userInfo = await fetchUser(user.id) - if (!userInfo?.onboarded) redirect('/onboarding') - - const result = await fetchCommunities({ - searchString: searchParams.q, - pageNumber: searchParams?.page ? +searchParams.page : 1, - pageSize: 25, - }) - - return ( - <> -

Comunidades

- -
- -
- -
- {result.communities.length === 0 ? ( -

Nenhum Resultado

- ) : ( - <> - {result.communities.map((community) => ( - - ))} - - )} -
- - - - ) -} - -export default Page diff --git a/app/(root)/create-thread/page.tsx b/app/(root)/create-thread/page.tsx deleted file mode 100644 index 01ba1ef..0000000 --- a/app/(root)/create-thread/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { currentUser } from "@clerk/nextjs"; -import { redirect } from "next/navigation"; - -import PostThread from "@/components/forms/PostThread"; -import { fetchUser } from "@/lib/actions/user.actions"; - -async function Page() { - const user = await currentUser(); - if (!user) return null; - - // fetch organization list created by user - const userInfo = await fetchUser(user.id); - if (!userInfo?.onboarded) redirect("/onboarding"); - - return ( - <> -

Criar Threads

- - - - ); -} - -export default Page; diff --git a/app/(root)/layout.tsx b/app/(root)/layout.tsx deleted file mode 100644 index dfdcfc3..0000000 --- a/app/(root)/layout.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import type React from 'react' -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' -import { ClerkProvider } from '@clerk/nextjs' -import { dark } from '@clerk/themes' - -import '../globals.css' -import LeftSidebar from '@/components/shared/LeftSidebar' -import Bottombar from '@/components/shared/Bottombar' -import RightSidebar from '@/components/shared/RightSidebar' -import Topbar from '@/components/shared/Topbar' - -const inter = Inter({ subsets: ['latin'] }) - -export const metadata: Metadata = { - title: 'Vine Community', - description: 'Uma comunidade de desenvolvedores feita por desenvolvedroes para desnvolvedores', - icons: { - icon: [ - { - media: '(prefers-color-scheme: dark)', - url: '/logo-white.png', - href: '/logo-white.png', - }, - { - media: '(prefers-color-scheme: light)', - url: '/logo-black.png', - href: '/logo-black.png', - }, - ], - }, -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - - - - -
- -
-
{children}
-
- -
- - - - -
- ) -} diff --git a/app/(root)/profile/[id]/page.tsx b/app/(root)/profile/[id]/page.tsx deleted file mode 100644 index bafa457..0000000 --- a/app/(root)/profile/[id]/page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import Image from 'next/image' -import { currentUser } from '@clerk/nextjs' -import { redirect } from 'next/navigation' - -import { profileTabs } from '@/constants' - -import ThreadsTab from '@/components/shared/ThreadsTab' -import ProfileHeader from '@/components/shared/ProfileHeader' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' - -import { fetchUser } from '@/lib/actions/user.actions' - -async function Page({ params }: { params: { id: string } }) { - const user = await currentUser() - if (!user) return null - - const userInfo = await fetchUser(params.id) - if (!userInfo?.onboarded) redirect('/onboarding') - - return ( -
- - -
- - - {profileTabs.map((tab) => ( - - {tab.label} -

{tab.label}

- - {tab.label === 'Threads' && ( -

- {userInfo.threads.length} -

- )} -
- ))} -
- {profileTabs.map((tab) => ( - - - - ))} -
-
-
- ) -} -export default Page diff --git a/app/(root)/profile/edit/page.tsx b/app/(root)/profile/edit/page.tsx deleted file mode 100644 index a4d9277..0000000 --- a/app/(root)/profile/edit/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { currentUser } from "@clerk/nextjs"; -import { redirect } from "next/navigation"; - -import { fetchUser } from "@/lib/actions/user.actions"; -import AccountProfile from "@/components/forms/AccountProfile"; - -// Copy paste most of the code as it is from the /onboarding - -async function Page() { - const user = await currentUser(); - if (!user) return null; - - const userInfo = await fetchUser(user.id); - if (!userInfo?.onboarded) redirect("/onboarding"); - - const userData = { - id: user.id, - objectId: userInfo?._id, - username: userInfo ? userInfo?.username : user.username, - name: userInfo ? userInfo?.name : user.firstName ?? "", - bio: userInfo ? userInfo?.bio : "", - image: userInfo ? userInfo?.image : user.imageUrl, - }; - - return ( - <> -

Editar Perfil

-

FaƧa qualquer alteraĆ§Ć£o

- -
- -
- - ); -} - -export default Page; diff --git a/app/(root)/search/page.tsx b/app/(root)/search/page.tsx deleted file mode 100644 index 547c100..0000000 --- a/app/(root)/search/page.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { redirect } from 'next/navigation' -import { currentUser } from '@clerk/nextjs' - -import UserCard from '@/components/cards/UserCard' -import Searchbar from '@/components/shared/Searchbar' -import Pagination from '@/components/shared/Pagination' - -import { fetchUser, fetchUsers } from '@/lib/actions/user.actions' - -async function Page({ - searchParams, -}: { - searchParams: { [key: string]: string | undefined } -}) { - const user = await currentUser() - if (!user) return null - - const userInfo = await fetchUser(user.id) - if (!userInfo?.onboarded) redirect('/onboarding') - - const result = await fetchUsers({ - userId: user.id, - searchString: searchParams.q, - pageNumber: searchParams?.page ? +searchParams.page : 1, - pageSize: 25, - }) - - return ( -
-

Pesquisa

- - - -
- {result.users.length === 0 ? ( -

Nenhum Resultado

- ) : ( - <> - {result.users.map((person) => ( - - ))} - - )} -
- - -
- ) -} - -export default Page diff --git a/biome.json b/biome.json index 0239822..b71af7d 100644 --- a/biome.json +++ b/biome.json @@ -1,45 +1,45 @@ { - "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "complexity": { - "noStaticOnlyClass": "off" - }, - "suspicious": { - "noExplicitAny": "off" - } - } - }, - "formatter": { - "enabled": true, - "formatWithErrors": false, - "ignore": [], - "attributePosition": "auto", - "indentStyle": "tab", - "indentWidth": 2, - "lineWidth": 120, - "lineEnding": "lf" - }, - "javascript": { - "formatter": { - "arrowParentheses": "always", - "bracketSameLine": false, - "bracketSpacing": true, - "jsxQuoteStyle": "single", - "quoteProperties": "asNeeded", - "semicolons": "asNeeded", - "trailingCommas": "all", - "quoteStyle": "single" - } - }, - "json": { - "formatter": { - "trailingCommas": "none" - } - } + "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noStaticOnlyClass": "off" + }, + "suspicious": { + "noExplicitAny": "off" + } + } + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "ignore": [], + "attributePosition": "auto", + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 120, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "arrowParentheses": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "jsxQuoteStyle": "single", + "quoteProperties": "asNeeded", + "semicolons": "asNeeded", + "trailingCommas": "all", + "quoteStyle": "single" + } + }, + "json": { + "formatter": { + "trailingCommas": "none" + } + } } diff --git a/components.json b/components.json index f62b1bb..4c8fe88 100644 --- a/components.json +++ b/components.json @@ -13,4 +13,4 @@ "components": "@/components", "utils": "@/lib/utils" } -} \ No newline at end of file +} diff --git a/components/cards/ThreadCard.tsx b/components/cards/ThreadCard.tsx deleted file mode 100644 index b508269..0000000 --- a/components/cards/ThreadCard.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import Image from 'next/image' -import Link from 'next/link' - -import { formatDateString } from '@/lib/utils' -import DeleteThread from '../forms/DeleteThread' - -interface Props { - id: string - currentUserId: string - parentId: string | null - content: string - author: { - name: string - image: string - id: string - } - community: { - id: string - name: string - image: string - } | null - createdAt: string - comments: { - author: { - image: string - } - }[] - isComment?: boolean -} - -function ThreadCard({ - id, - currentUserId, - parentId, - content, - author, - community, - createdAt, - comments, - isComment, -}: Props) { - return ( -
-
-
-
- - user_community_image - - -
-
- -
- -

{author.name}

- - -

{content}

- -
-
- heart - - heart - - heart - heart -
- - {isComment && comments.length > 0 && ( - -

- {comments.length} repl{comments.length > 1 ? 'ies' : 'y'} -

- - )} -
-
-
- - -
- - {!isComment && comments.length > 0 && ( -
- {comments.slice(0, 2).map((comment, index) => ( - - key={index} - src={comment.author.image} - alt={`user_${index}`} - width={24} - height={24} - className={`${index !== 0 && '-ml-5'} rounded-full object-cover`} - /> - ))} - - -

- {comments.length} repl{comments.length > 1 ? 'ies' : 'y'} -

- -
- )} - - {!isComment && community && ( - -

- {formatDateString(createdAt)} - {community && ` - ${community.name} Community`} -

- - {community.name} - - )} -
- ) -} - -export default ThreadCard diff --git a/components/cards/UserCard.tsx b/components/cards/UserCard.tsx deleted file mode 100644 index 949e28f..0000000 --- a/components/cards/UserCard.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client' - -import Image from 'next/image' -import { useRouter } from 'next/navigation' - -import { Button } from '../ui/button' - -interface Props { - id: string - name: string - username: string - imgUrl: string - personType: string -} - -function UserCard({ id, name, username, imgUrl, personType }: Props) { - const router = useRouter() - - const isCommunity = personType === 'Community' - - return ( -
-
-
- user_logo -
- -
-

{name}

-

@{username}

-
-
- - -
- ) -} - -export default UserCard diff --git a/components/forms/AccountProfile.tsx b/components/forms/AccountProfile.tsx deleted file mode 100644 index 0678ee2..0000000 --- a/components/forms/AccountProfile.tsx +++ /dev/null @@ -1,189 +0,0 @@ -'use client' - -import type * as z from 'zod' -import Image from 'next/image' -import { useForm } from 'react-hook-form' -import { usePathname, useRouter } from 'next/navigation' -import { type ChangeEvent, useState } from 'react' -import { zodResolver } from '@hookform/resolvers/zod' - -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { Textarea } from '@/components/ui/textarea' - -import { useUploadThing } from '@/lib/uploadthing' -import { isBase64Image } from '@/lib/utils' - -import { UserValidation } from '@/lib/validations/user' -import { updateUser } from '@/lib/actions/user.actions' - -interface Props { - user: { - id: string - objectId: string - username: string - name: string - bio: string - image: string - } - btnTitle: string -} - -const AccountProfile = ({ user, btnTitle }: Props) => { - const router = useRouter() - const pathname = usePathname() - const { startUpload } = useUploadThing('media') - - const [files, setFiles] = useState([]) - - const form = useForm>({ - resolver: zodResolver(UserValidation), - defaultValues: { - profile_photo: user?.image ? user.image : '', - name: user?.name ? user.name : '', - username: user?.username ? user.username : '', - bio: user?.bio ? user.bio : '', - }, - }) - - const onSubmit = async (values: z.infer) => { - const blob = values.profile_photo - - const hasImageChanged = isBase64Image(blob) - if (hasImageChanged) { - const imgRes = await startUpload(files) - - if (imgRes?.[0].fileUrl) { - values.profile_photo = imgRes[0].fileUrl - } - } - - await updateUser({ - name: values.name, - path: pathname, - username: values.username, - userId: user.id, - bio: values.bio, - image: values.profile_photo, - }) - - if (pathname === '/profile/edit') { - router.back() - } else { - router.push('/') - } - } - - const handleImage = (e: ChangeEvent, fieldChange: (value: string) => void) => { - e.preventDefault() - - const fileReader = new FileReader() - - if (e.target.files && e.target.files.length > 0) { - const file = e.target.files[0] - setFiles(Array.from(e.target.files)) - - if (!file.type.includes('image')) return - - fileReader.onload = async (event) => { - const imageDataUrl = event.target?.result?.toString() || '' - fieldChange(imageDataUrl) - } - - fileReader.readAsDataURL(file) - } - } - - return ( -
- - ( - - - {field.value ? ( - profile_icon - ) : ( - profile_icon - )} - - - handleImage(e, field.onChange)} - /> - - - )} - /> - - ( - - Nome - - - - - - )} - /> - - ( - - Username - - - - - - )} - /> - - ( - - Bio - -