diff --git a/src/app/[locale]/user/watch-ads/page.tsx b/src/app/[locale]/user/watch-ads/page.tsx index 01e54837..bc3879a8 100644 --- a/src/app/[locale]/user/watch-ads/page.tsx +++ b/src/app/[locale]/user/watch-ads/page.tsx @@ -1,81 +1,153 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; +import { Button } from '@/components/shared/Forms/Buttons/Buttons'; +import MembershipIcon from '@/components/shared/membership/MembershipIcon'; +import { MembershipClassType } from '@/constants/types'; +import { createWatchAdsSession, completeWatchAdsSegment } from'../../../../services/watchAdsApi' + +const tiers: [string, string, MembershipClassType | null][] = [ + ['Single mappi', 'Watch ads for 10 minutes', null], + ['White membership', 'Watch ads for 50 minutes', MembershipClassType.WHITE], + ['Green membership', 'Watch ads for 1 hour and 40 minutes', MembershipClassType.GREEN], + ['Gold membership', 'Watch ads for 4 hours and 10 minutes', MembershipClassType.GOLD], + ['Double Gold membership', 'Watch ads for 8 hours and 20 minutes', MembershipClassType.DOUBLE_GOLD], + ['Triple Gold membership', 'Watch ads for 16 hours and 40 minutes', MembershipClassType.TRIPLE_GOLD], +]; declare const Pi: any; export default function WatchAdsPage() { const ready = useRef(false); - const [log, setLog] = useState([]); - const push = (m: string) => setLog(prev => [m, ...prev].slice(0, 60)); + const [sessionId, setSessionId] = useState(null); + const [earnedSecs, setEarnedSecs] = useState(0); + const [status, setStatus] = useState(""); useEffect(() => { (async () => { - if (typeof window === 'undefined' || !window.Pi) { - push('Pi SDK not found. Open this page in Pi Browser.'); - return; - } + if (typeof window === 'undefined' || !window.Pi) return; + try { await Pi.init({ version: '2.0' }); - push('Pi.init OK'); - const feats = await Pi.nativeFeaturesList(); - push(`nativeFeaturesList: ${JSON.stringify(feats)}`); - if (!feats.includes('ad_network')) { - push('ad_network not supported. Update Pi Browser.'); - return; - } + if (!feats.includes('ad_network')) return; try { - await Pi.authenticate(); // no payment callbacks - push('Pi.authenticate OK'); + await Pi.authenticate(); } catch { - push('User skipped auth; rewarded may not show.'); + console.warn('User skipped authentication.'); + } + + // Create backend watch-ads session + const data = await createWatchAdsSession(); + if (data?._id) { + setSessionId(data._id); + setEarnedSecs(data.earnedSecs ?? 0); + setStatus(data.status ?? "unknown"); } ready.current = true; - } catch (e: any) { - push(`Init/Auth error: ${e?.message || String(e)}`); + } catch (err: any) { + console.error('Initialization error:', err); } })(); }, []); const showRewarded = async () => { - if (!ready.current) return push('SDK not ready yet.'); + if (!ready.current) return; try { const isReady = await Pi.Ads.isAdReady('rewarded'); - push(`isAdReady(rewarded): ${JSON.stringify(isReady)}`); - if (!isReady.ready) { const req = await Pi.Ads.requestAd('rewarded'); - push(`requestAd(rewarded): ${JSON.stringify(req)}`); - if (req.result !== 'AD_LOADED') { - push('No rewarded ad available right now.'); - return; - } + if (req.result !== 'AD_LOADED') return; } - const show = await Pi.Ads.showAd('rewarded'); // { result, adId? } - push(`showAd(rewarded): ${JSON.stringify(show)}`); - if (show.result === 'AD_REWARDED') push('Rewarded completed.'); - } catch (e: any) { - push(`showRewarded error: ${e?.message || String(e)}`); + const show = await Pi.Ads.showAd('rewarded'); + if (show.result === 'AD_REWARDED' && sessionId) { + // Complete segment + const updated = await completeWatchAdsSegment(sessionId, show.adId); + if (updated) { + setEarnedSecs(Number(updated.earnedSecs ?? 0)); + setStatus(updated.status ?? "unknown"); + } + } + } catch (err: any) { + console.error('Error showing ad:', err); } }; return ( -
-

Watch Ads (Test)

- - -
- {log.length === 0 ?
No logs yet.
: ( -
    - {log.map((l, i) =>
  • {l}
  • )} -
- )} +
+
+ +

+ Watch Ads to Buy Membership +

+ +
+

+ How many ad minutes do I need: +

+ +
    + {tiers.map(([title, desc, tier], i) => ( +
  • +
    + {title} + {tier && ( + + )} +
    +

    {desc}

    +
  • + ))} +
+
+ +

+ Ads are presented in 10 minute blocks +

+ +
+
-
+ +

+ Ad minutes watched so far +
+ + {Math.floor(Number(earnedSecs) / 3600)} hours,{' '} + {Math.floor((Number(earnedSecs) / 60) % 60)} min and{' '} + {Math.floor(Number(earnedSecs) % 60)} seconds + +

+
); } diff --git a/src/components/shared/membership/MembershipIcon.tsx b/src/components/shared/membership/MembershipIcon.tsx index fe848c43..bd3977da 100644 --- a/src/components/shared/membership/MembershipIcon.tsx +++ b/src/components/shared/membership/MembershipIcon.tsx @@ -27,16 +27,45 @@ function MembershipIcon({ category, className, styleComponent }: { return null } } - + const icon = HandleMembership(category); if (!icon) return null; // Don't render anything for casual members - return ( -
- {category} + return ( +
+ {category}
- ) + ); } export default MembershipIcon \ No newline at end of file diff --git a/src/services/watchAdsApi.ts b/src/services/watchAdsApi.ts new file mode 100644 index 00000000..bd72cf9d --- /dev/null +++ b/src/services/watchAdsApi.ts @@ -0,0 +1,38 @@ +import axiosClient from "@/config/client"; +import logger from '../../logger.config.mjs'; + +export const createWatchAdsSession = async () => { + try { + logger.info("Creating new Watch Ads session"); + const response = await axiosClient.post("/v1/watch-ads/session"); + + if (response.status === 200) { + logger.info("Session created successfully", { data: response.data }); + return response.data; + } else { + logger.error(`Session creation failed with status ${response.status}`); + return null; + } + } catch (error) { + logger.error("Error creating Watch Ads session:", error); + throw new Error("Failed to initialize Watch Ads session"); + } +}; + +export const completeWatchAdsSegment = async (sessionId: string, adId: string) => { + try { + logger.info(`Completing ad segment for session ${sessionId}`); + const response = await axiosClient.post(`/v1/watch-ads/session/${sessionId}/segment-complete`, { adId }); + + if (response.status === 200) { + logger.info("Segment completion successful", { data: response.data }); + return response.data; + } else { + logger.error(`Segment completion failed with status ${response.status}`); + return null; + } + } catch (error) { + logger.error("Error completing ad segment:", error); + throw new Error("Failed to complete ad segment"); + } +}; \ No newline at end of file