From ee203c4c471c0ad5b7c8891cbd64f2f2d0834974 Mon Sep 17 00:00:00 2001 From: curvesy Date: Sat, 11 Jan 2025 20:52:55 +0330 Subject: [PATCH] Add handler for reaction button --- app/actions/reactToProject.ts | 34 +++++++ app/components/Feed/Meta.tsx | 114 +++++++++++++++++----- app/components/Feed/Project.tsx | 15 +-- app/components/Feed/types.ts | 3 + app/components/ProjectDetails/Actions.tsx | 2 +- app/config/endpoints.ts | 1 + 6 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 app/actions/reactToProject.ts diff --git a/app/actions/reactToProject.ts b/app/actions/reactToProject.ts new file mode 100644 index 0000000..c78cd5e --- /dev/null +++ b/app/actions/reactToProject.ts @@ -0,0 +1,34 @@ +'use server'; + +import {ENDPOINTS} from '@/app/config/endpoints'; +import {apiClient} from '@/app/utils/apiClient'; + +export const reactToProject = async (projectId: number, hasReaction: boolean) => { + try { + const method = hasReaction ? 'DELETE' : 'POST'; + const url = hasReaction + ? `${ENDPOINTS.REACTIONS}/${projectId}` + : `${ENDPOINTS.REACTIONS}`; + + const res = await apiClient(url, { + method, + ...(method === 'POST' && { + body: JSON.stringify({ + data: { + emoji: '👍', + entity_type: 'project', + project: projectId, + }, + }), + }), + }); + + + // Success: Return true for both POST and DELETE + return { success: true, data: res.data }; + } catch (error) { + console.error('Error during reaction operation:', error); + return { success: false, error: 'Failed to toggle reaction' }; + } + }; + \ No newline at end of file diff --git a/app/components/Feed/Meta.tsx b/app/components/Feed/Meta.tsx index 19a3437..abe0d74 100644 --- a/app/components/Feed/Meta.tsx +++ b/app/components/Feed/Meta.tsx @@ -1,39 +1,103 @@ -import React, {FC} from 'react'; +'use client'; -import {Span, View} from '@/app/components/Polyfills'; -import {Icon} from '@/app/components/Elements'; -import {MetaProps, FeedType, ProjectStatus} from './types'; +import React, { FC, useState, useCallback } from 'react'; +import { Span, TouchableOpacity, View } from '@/app/components/Polyfills'; +import { Icon } from '@/app/components/Elements'; +import { MetaProps, FeedType, ProjectStatus } from './types'; +import { reactToProject } from '@/app/actions/reactToProject'; -const Meta: FC = ({reactionCount, backers, type, status}) => { - let statusTag = 'Support'; - if (status === ProjectStatus.Failed) { - statusTag = 'Failed'; - } - if (status === ProjectStatus.Successful) { - statusTag = 'Successful'; - } +const Meta: FC = ({ + projectId, + reactionCount = 0, + hasReaction = false, + backers, + type, + status, +}) => { + const [currentReactionCount, setCurrentReactionCount] = useState(reactionCount); + const [hasUserReacted, setHasUserReacted] = useState(hasReaction); + const [isLoading, setIsLoading] = useState(false); + + const statusTag = + status === ProjectStatus.Failed + ? 'Failed' + : status === ProjectStatus.Successful + ? 'Successful' + : 'Support'; + + const toggleReaction = useCallback(async () => { + if (isLoading) return; + + // Optimistically update UI + const nextReactionState = !hasUserReacted; + const nextReactionCount = nextReactionState + ? currentReactionCount + 1 + : currentReactionCount - 1; + + setHasUserReacted(nextReactionState); + setCurrentReactionCount(nextReactionCount); + setIsLoading(true); + + try { + const result = await reactToProject(projectId, hasUserReacted); + + // Revert UI if API fails + if (!result.success) { + setHasUserReacted(hasUserReacted); + setCurrentReactionCount(currentReactionCount); + } + } catch (error) { + console.error('Error toggling reaction:', error); + + // Revert UI on error + setHasUserReacted(hasUserReacted); + setCurrentReactionCount(currentReactionCount); + } finally { + setIsLoading(false); + } + }, [currentReactionCount, hasUserReacted, isLoading, projectId]); return ( - - - - {reactionCount && reactionCount > 0 ? ( - {reactionCount} - ) : null} - + + {/* Reaction Section */} + + + + - {type === FeedType.Project && reactionCount && reactionCount > 0 ? ( - + {currentReactionCount > 0 && ( + + {currentReactionCount} + + )} + + {/* Backers Section */} + {type === FeedType.Project && currentReactionCount > 0 && ( + {`${backers} fans`} - ) : null} + )} + {/* Status Section */} + className={`${ + statusTag === 'Successful' + ? 'text-assureStrong' + : statusTag === 'Failed' + ? 'text-warnStrong' + : '' + } text-neutralPure rounded-xl`} + > {statusTag} diff --git a/app/components/Feed/Project.tsx b/app/components/Feed/Project.tsx index 59c6453..415f9c9 100644 --- a/app/components/Feed/Project.tsx +++ b/app/components/Feed/Project.tsx @@ -7,11 +7,10 @@ import Meta from './Meta'; import {Routes} from '@/app/config/routes'; const Project: FC = ({data}) => { - const {documentId, name, summary, owner, reaction_count, type} = data; + const {documentId, id, name, summary, owner, reaction_count, type, has_reaction} = data; // const image = data?.length // ? {uri: `${API_URL}${data[0].formats.thumbnail.url}`} // : avatar; - return ( @@ -19,13 +18,15 @@ const Project: FC = ({data}) => {

{name}

{summary} -
+
); }; diff --git a/app/components/Feed/types.ts b/app/components/Feed/types.ts index e980b73..a7e1e21 100644 --- a/app/components/Feed/types.ts +++ b/app/components/Feed/types.ts @@ -85,6 +85,7 @@ export type Project = ProjectAttrs & { documentId: string; current_funding: string; reaction_count: number; + has_reaction: boolean; users_permissions_user: User; images: Images; owner: Owner; @@ -124,7 +125,9 @@ export interface LinkToProjectProps { } export interface MetaProps { + projectId: number; reactionCount?: number; + hasReaction?: boolean; backers?: number; type: FeedType; status?: ProjectStatus; diff --git a/app/components/ProjectDetails/Actions.tsx b/app/components/ProjectDetails/Actions.tsx index 02ff150..f9d77b8 100644 --- a/app/components/ProjectDetails/Actions.tsx +++ b/app/components/ProjectDetails/Actions.tsx @@ -18,7 +18,7 @@ import {ActionsProps} from './types'; const Actions: FC = async ({owner, project, refresh}) => { const account = await getUserAccount(); - const ownerId = Number(project.users_permissions_user.id); + const ownerId = Number(project?.users_permissions_user?.id); const accountId = account?.id; const projectId = project?.documentId; const status = project?.project_status; diff --git a/app/config/endpoints.ts b/app/config/endpoints.ts index 841e972..3a6d68e 100644 --- a/app/config/endpoints.ts +++ b/app/config/endpoints.ts @@ -10,6 +10,7 @@ export const ENDPOINTS = { CONTRIBUTIONS: '/contributions', EXCLUSIVE_CONTENT: '/exclusive-contents', FILES: '/upload/files', + REACTIONS: '/reactions', }; export const KLAYR_ENDPOINTS = {