diff --git a/messages/en.json b/messages/en.json index 733f1b33..255b4a99 100644 --- a/messages/en.json +++ b/messages/en.json @@ -201,6 +201,8 @@ "GIVE_REVIEW_SECTION_HEADER": "Give review to this pioneer", "REVIEWS_GIVEN_SECTION_HEADER": "Reviews given by this pioneer", "REVIEWS_RECEIVED_SECTION_HEADER": "Reviews received by this pioneer", + "TRUST_PROTECT": "Trust Protect", + "TRUST_PROTECT_SUCCESS": "Trust Protect action completed successfully", "VALIDATION": { "NO_REVIEWS_FOUND": "No reviews found for Pioneer {search_value}", "NO_PIONEER_FOUND": "Pioneer {search_value} not found" diff --git a/src/app/[locale]/seller/reviews/[id]/page.tsx b/src/app/[locale]/seller/reviews/[id]/page.tsx index 4a51c37a..962c52d2 100644 --- a/src/app/[locale]/seller/reviews/[id]/page.tsx +++ b/src/app/[locale]/seller/reviews/[id]/page.tsx @@ -19,6 +19,9 @@ import { resolveDate } from '@/utils/date'; import { getImageSrc } from '@/utils/image'; import { AppContext } from '../../../../../../context/AppContextProvider'; import logger from '../../../../../../logger.config.mjs'; +import { deductMappi } from '@/services/membershipApi'; +import { applyTrustProtect } from '@/services/reviewsApi'; +import { updateReview } from '@/services/reviewsApi'; function SellerReviews({ params, @@ -33,13 +36,18 @@ function SellerReviews({ const locale = useLocale(); const [giverReviews, setGiverReviews] = useState(null); - const [receiverReviews, setReceiverReviews] = useState(null); + const [receiverReviews, setReceiverReviews] = useState( + null, + ); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isSaveEnabled, setIsSaveEnabled] = useState(false); - const [userFallbackImage, setUserFallbackImage] = useState(null); - const { currentUser, reload, setReload, autoLoginUser } = useContext(AppContext); - + const [userFallbackImage, setUserFallbackImage] = useState( + null, + ); + const { currentUser, reload, setReload, autoLoginUser } = + useContext(AppContext); + const inputRef = useRef(null); const [searchBarValue, setSearchBarValue] = useState(''); const [toUser, setToUser] = useState(''); @@ -78,8 +86,8 @@ function SellerReviews({ receiverId: feedback.review_receiver_id, reviewId: feedback._id, reaction, - unicode, - image: feedback.image + unicode, + image: feedback.image, }; }) .filter((review): review is ReviewInt => review !== null); @@ -97,7 +105,9 @@ function SellerReviews({ if (data) { if (data.givenReviews.length > 0) { - logger.info(`Fetched ${data.givenReviews.length} reviews given by userID: ${userId_}`); + logger.info( + `Fetched ${data.givenReviews.length} reviews given by userID: ${userId_}`, + ); setGiverReviews(processReviews(data.givenReviews)); } else { logger.warn(`No given reviews found for userID: ${userId_}`); @@ -105,12 +115,14 @@ function SellerReviews({ } if (data.receivedReviews.length > 0) { - logger.info(`Fetched ${data.receivedReviews.length} reviews received by userID: ${userId_}`); + logger.info( + `Fetched ${data.receivedReviews.length} reviews received by userID: ${userId_}`, + ); setReceiverReviews(processReviews(data.receivedReviews)); } else { logger.warn(`No received reviews found for userID: ${userId_}`); setReceiverReviews([]); - } + } } else { logger.warn(`No reviews found for userID: ${userId_}`); setGiverReviews([]); @@ -125,17 +137,54 @@ function SellerReviews({ } }; + const handleTrustProtect = async (reviewId: string) => { + if (!currentUser?.pi_uid) { + toast.error(t('SCREEN.REVIEWS.USER_NOT_LOGGED_IN')); + return; + } + + try { + // Step 1: Deduct 100 Mappi + const response = await deductMappi(100); + + if (response?.balance !== undefined) { + // Step 2: Apply Trust Protect + await applyTrustProtect(reviewId); + + // Step 3: Refresh reviews + await fetchUserReviews(toUser); + + toast.success( + 'Trust Protect was successful! Review face changed to Sad.', + ); + } + } catch (error: any) { + console.error('Trust & Protect failed:', error); + if (error?.message?.includes('Insufficient')) { + toast.error( + 'Each use of Trust Protect consumes 100 Mappi. Please top up.', + ); + } else { + toast.error('Trust Protect failed. Please try again later.'); + } + } + }; + // Handle search logic const handleSearch = async () => { setReload(true); setError(null); try { - logger.info(`Searching reviews for userID: ${userId} with query: ${searchBarValue}`); + logger.info( + `Searching reviews for userID: ${userId} with query: ${searchBarValue}`, + ); const data = await fetchReviews(userId, searchBarValue); if (data) { if (data.givenReviews.length > 0) { - logger.info(`Found ${data.givenReviews.length} reviews given by Pioneer: ${searchBarValue}`); + logger.info( + `Found ${data.givenReviews.length} reviews given by Pioneer: ${searchBarValue}`, + ); setGiverReviews(processReviews(data.givenReviews)); setToUser(data.givenReviews[0].review_giver_id); userName.current = data.givenReviews[0].giver; @@ -144,7 +193,9 @@ function SellerReviews({ setGiverReviews([]); } if (data.receivedReviews.length > 0) { - logger.info(`Found ${data.receivedReviews.length} reviews received by Pioneer: ${searchBarValue}`); + logger.info( + `Found ${data.receivedReviews.length} reviews received by Pioneer: ${searchBarValue}`, + ); setReceiverReviews(processReviews(data.receivedReviews)); setToUser(data.receivedReviews[0].review_receiver_id); userName.current = data.receivedReviews[0].receiver; @@ -152,29 +203,38 @@ function SellerReviews({ logger.warn(`No given reviews found for Pioneer: ${searchBarValue}`); setReceiverReviews([]); } - } else { - toast.error(t('SCREEN.REVIEWS.VALIDATION.NO_REVIEWS_FOUND', { search_value: searchBarValue })); + toast.error( + t('SCREEN.REVIEWS.VALIDATION.NO_REVIEWS_FOUND', { + search_value: searchBarValue, + }), + ); logger.warn(`No reviews found for Pioneer: ${searchBarValue}`); setGiverReviews([]); setReceiverReviews([]); } } catch (error) { logger.error(`Pioneer ${searchBarValue} not found`, error); - return toast.error(t('SCREEN.REVIEWS.VALIDATION.NO_PIONEER_FOUND', { search_value: searchBarValue })); + return toast.error( + t('SCREEN.REVIEWS.VALIDATION.NO_PIONEER_FOUND', { + search_value: searchBarValue, + }), + ); } finally { setReload(false); } }; - const handleSearchBarChange = (event: React.ChangeEvent) => { + const handleSearchBarChange = ( + event: React.ChangeEvent, + ) => { logger.debug(`Search bar value changed: ${event.target.value}`); setSearchBarValue(event.target.value); }; if (loading) { logger.info('Loading seller reviews..'); - return ; + return ; } return ( @@ -186,7 +246,7 @@ function SellerReviews({ {/* Search area */} -
+
{t('SHARED.PIONEER_LABEL')} + className="bg-primary rounded h-full w-15 p-[15.5px] flex items-center justify-center hover:bg-gray-600">
- +
-
- - {reload - ? - : giverReviews && giverReviews.map((review, index) => ( + + + {reload ? ( + + ) : ( + giverReviews && + giverReviews.map((review, index) => (
{/* Left content */} @@ -240,7 +308,6 @@ function SellerReviews({

{review.heading}

-
{/* Right content */} @@ -251,7 +318,10 @@ function SellerReviews({
{(() => { - const imgSrc = getImageSrc(review.image, userFallbackImage); + const imgSrc = getImageSrc( + review.image, + userFallbackImage, + ); return imgSrc ? ( ) : null; })()} -

+

{review.unicode}

-
{/* Bottom row with Edit left, Reply right */}
{review.giverId === currentUser?.pi_uid && ( - + )} - +
)) - } + )} - - - {reload - ? - : receiverReviews && receiverReviews.map((review, index) => ( -
-
- {/* Left content */} -
-

- {review.giver} {' → '} - - {review.receiver} - -

-

{review.heading}

-
- {/* Right content */} -
-
-

{review.date}

-

{review.time}

-
-
- {(() => { - const imgSrc = getImageSrc(review.image, userFallbackImage); - return imgSrc ? ( - review image - ) : null; - })()} -

- {review.unicode} + + {reload ? ( + + ) : ( + receiverReviews && + receiverReviews.map((review, index) => ( +

+
+ {/* Left content */} +
+

+ {review.giver} {' → '} + + {review.receiver} +

+

{review.heading}

+ {/* Right content */} +
+
+

{review.date}

+

{review.time}

+
+
+ {(() => { + const imgSrc = getImageSrc( + review.image, + userFallbackImage, + ); + return imgSrc ? ( + review image + ) : null; + })()} +

+ {review.unicode} +

+
+
-
- {/* Bottom row with Edit left, Reply right */} -
- {review.giverId === currentUser?.pi_uid && ( - - - - )} +
+ {/* Left side: Edit + Trust Protect */} +
+ {review.giverId === currentUser?.pi_uid && ( + + + + )} - - - + {/* Only show Trust Protect if the emoji reaction is 'Despair' */} + {review.reaction === 'Despair' && ( + { + e.preventDefault(); + handleTrustProtect(review.reviewId); + }}> + + + )} +
+ + {/* Far right: Reply button */} +
+ + + +
+
-
- )) - } + )) + )}
); } -export default SellerReviews; \ No newline at end of file +export default SellerReviews; diff --git a/src/services/membershipApi.ts b/src/services/membershipApi.ts index 49a6328a..d702a8aa 100644 --- a/src/services/membershipApi.ts +++ b/src/services/membershipApi.ts @@ -1,18 +1,25 @@ -import axiosClient from "@/config/client"; -import { IMembership, MembershipOption } from "@/constants/types"; -import logger from "../../logger.config.mjs" +import axiosClient from '@/config/client'; +import { IMembership, MembershipOption } from '@/constants/types'; +import logger from '../../logger.config.mjs'; -export const fetchMembershipList = async (): Promise => { +export const fetchMembershipList = async (): Promise< + MembershipOption[] | null +> => { try { logger.info(`Fetching membership list`); - const response = await axiosClient.get("/memberships/membership-list"); + const response = await axiosClient.get('/memberships/membership-list'); if (response.status === 200) { - logger.info(`Fetch membership list successful with Status ${response.status}`, { - data: response.data - }); + logger.info( + `Fetch membership list successful with Status ${response.status}`, + { + data: response.data, + }, + ); return response.data; } else { - logger.error(`Fetch membership list failed with Status ${response.status}`); + logger.error( + `Fetch membership list failed with Status ${response.status}`, + ); return null; } } catch (error) { @@ -25,18 +32,49 @@ export const fetchMembershipList = async (): Promise export const fetchMembership = async (): Promise => { try { logger.info(`Fetching user membership`); - const response = await axiosClient.get("/memberships"); + const response = await axiosClient.get('/memberships'); if (response.status === 200) { - logger.info(`Fetch user membership successful with Status ${response.status}`, { - data: response.data - }); + logger.info( + `Fetch user membership successful with Status ${response.status}`, + { + data: response.data, + }, + ); return response.data; } else { - logger.error(`Fetch user membership failed with Status ${response.status}`); + logger.error( + `Fetch user membership failed with Status ${response.status}`, + ); return null; } } catch (error) { logger.error('Fetch user membership encountered an error:', error); throw new Error('Failed to fetch user membership. Please try again later.'); } -}; \ No newline at end of file +}; + +// Deduct Mappi for Trust Protect +export const deductMappi = async ( + amount: number, +): Promise<{ balance: number } | null> => { + try { + logger.info('Initiating Trust Protect Mappi deduction...', { amount }); + + const response = await axiosClient.post('/memberships/deduct-mappi', { + amount, + }); + + if (response.status === 200) { + logger.info('Mappi deduction successful', { + balance: response.data.balance, + }); + return response.data; + } else { + logger.error(`Mappi deduction failed with Status ${response.status}`); + return null; + } + } catch (error) { + logger.error('Mappi deduction encountered an error:', error); + throw new Error('Failed to deduct Mappi. Please try again later.'); + } +}; diff --git a/src/services/reviewsApi.ts b/src/services/reviewsApi.ts index ad11bcce..3b31caa6 100644 --- a/src/services/reviewsApi.ts +++ b/src/services/reviewsApi.ts @@ -93,3 +93,24 @@ export const updateReview = async (review_id: string, formData: FormData) => { throw new Error('Failed to update review. Please try again later.'); } }; +// Apply Trust Protect to a review +export const applyTrustProtect = async (review_id: string) => { + try { + logger.info(`Applying Trust Protect to review with ID: ${review_id}`); + + const response = await axiosClient.put(`/review-feedback/trust-protect/${review_id}`); + + if (response.status === 200) { + logger.info(`Trust Protect applied successfully with Status ${response.status}`, { + data: response.data + }); + return response.data; + } else { + logger.error(`Trust Protect failed with Status ${response.status}`); + return null; + } + } catch (error) { + logger.error(`Trust Protect for reviewID ${review_id} encountered an error:`, error); + throw new Error('Failed to apply Trust Protect. Please try again later.'); + } +};