Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add handler for reaction button #229

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions app/actions/reactToProject.ts
Original file line number Diff line number Diff line change
@@ -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' };
}
};

114 changes: 89 additions & 25 deletions app/components/Feed/Meta.tsx
Original file line number Diff line number Diff line change
@@ -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<MetaProps> = ({reactionCount, backers, type, status}) => {
let statusTag = 'Support';
if (status === ProjectStatus.Failed) {
statusTag = 'Failed';
}
if (status === ProjectStatus.Successful) {
statusTag = 'Successful';
}
const Meta: FC<MetaProps> = ({
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 (
<View className="flex flex-row justify-between items-end pt-6">
<View className="flex flex row nowrap gap-4">
<View className="flex flex row nowrap items-center">
<Icon name="thumbsUp" size={18} color="#6D6C6C" />
{reactionCount && reactionCount > 0 ? (
<Span className="text-neutralSteady font-light">{reactionCount}</Span>
) : null}
</View>
<View className="flex flex-row nowrap gap-4">
{/* Reaction Section */}
<View className="flex flex-row items-center">
<TouchableOpacity
onPress={toggleReaction}
disabled={isLoading}
className={`flex flex-row items-center ${isLoading ? 'opacity-50' : ''}`}
>
<Icon
name="thumbsUp"
size={18}
color={hasUserReacted ? '#825E87' : '#6B7280'}
/>
</TouchableOpacity>

{type === FeedType.Project && reactionCount && reactionCount > 0 ? (
<View className="flex flex row nowrap items-center">
{currentReactionCount > 0 && (
<Span className="text-neutralSteady font-light">
{currentReactionCount}
</Span>
)}
</View>
{/* Backers Section */}
{type === FeedType.Project && currentReactionCount > 0 && (
<View className="flex flex-row items-center">
<Icon name="Profile" size={18} color="#6D6C6C" />
<Span className="text-neutralSteady font-light">{`${backers} fans`}</Span>
</View>
) : null}
)}
</View>
{/* Status Section */}
<View
className={`${statusTag === 'Successful' ? 'text-assureStrong' : ''} ${
statusTag === 'Successful' ? 'text-warnStrong' : ''
} text-neutralPure rounded-xl`}>
className={`${
statusTag === 'Successful'
? 'text-assureStrong'
: statusTag === 'Failed'
? 'text-warnStrong'
: ''
} text-neutralPure rounded-xl`}
>
<Span>{statusTag}</Span>
</View>
</View>
Expand Down
15 changes: 8 additions & 7 deletions app/components/Feed/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import Meta from './Meta';
import {Routes} from '@/app/config/routes';

const Project: FC<ProjectProps> = ({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 (
<View className="w-full bg-neutralPure p-4 rounded-xl mb-6">
<Artist data={owner} />
<Link to={{screen: `${Routes.Projects}/${documentId}`}}>
<View>
<H4 className="pt-4 pb-2">{name}</H4>
<Span className="text-neutralMighty dark:!text-neutralStrong font-light">{summary}</Span>
<Meta
reactionCount={reaction_count}
type={type}
status={ProjectStatus.Live}
/>
</View>
</Link>
<Meta
projectId={id}
reactionCount={reaction_count}
hasReaction={has_reaction}
type={type}
status={ProjectStatus.Live}
/>
</View>
);
};
Expand Down
3 changes: 3 additions & 0 deletions app/components/Feed/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,7 +125,9 @@ export interface LinkToProjectProps {
}

export interface MetaProps {
projectId: number;
reactionCount?: number;
hasReaction?: boolean;
backers?: number;
type: FeedType;
status?: ProjectStatus;
Expand Down
2 changes: 1 addition & 1 deletion app/components/ProjectDetails/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {ActionsProps} from './types';
const Actions: FC<ActionsProps> = 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;
Expand Down
1 change: 1 addition & 0 deletions app/config/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const ENDPOINTS = {
CONTRIBUTIONS: '/contributions',
EXCLUSIVE_CONTENT: '/exclusive-contents',
FILES: '/upload/files',
REACTIONS: '/reactions',
};

export const KLAYR_ENDPOINTS = {
Expand Down
Loading