Skip to content

Commit

Permalink
Enchance quests and X share support (#43)
Browse files Browse the repository at this point in the history
Co-authored-by: Ulad Palinski <[email protected]>
Co-authored-by: upalinski <[email protected]>
  • Loading branch information
3 people authored Dec 10, 2024
1 parent 314882e commit 727dbbb
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 43 deletions.
43 changes: 26 additions & 17 deletions apps/creator/src/components/Quests/EditQuestModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const EditQuestModalContent = ({ quest, onSave, onDelete, isLoading }: Mo
const [description, setDescription] = useState(quest?.description);
const [type, setType] = useState(quest?.type || 'video');
const [videoId, setVideoId] = useState(quest?.videoId);
const [url, setUrl] = useState<string>(quest?.url || '');
const [rewardPoints, setRewardPoints] = useState(quest?.rewardPoints);

const [videos, setVideos] = useState<Video[]>([]);
Expand All @@ -36,7 +37,8 @@ export const EditQuestModalContent = ({ quest, onSave, onDelete, isLoading }: Mo
title: title!,
description: description!,
type: type!,
videoId: videoId!,
videoId: type === 'video' ? videoId : '',
url: type === 'share' ? url : '',
rewardPoints: rewardPoints!,
});
};
Expand Down Expand Up @@ -70,23 +72,30 @@ export const EditQuestModalContent = ({ quest, onSave, onDelete, isLoading }: Mo
displayEmpty
>
<option value="video">Watch the video</option>
<option value="post_x" disabled={true}>
Share post on X
</option>
</Select>
<Select
header="Video"
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
placeholder="I am usual input, just leave me alone"
onChange={(e) => setVideoId(e.target.value)}
>
{videos.map((video) => (
<option key={video.id} value={video.id}>
{video.title}
</option>
))}
<option value="share">Share post url</option>
</Select>
{type === 'video' ? (
<Select
header="Video"
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
placeholder="I am usual input, just leave me alone"
onChange={(e) => setVideoId(e.target.value)}
>
{videos.map((video) => (
<option key={video.id} value={video.id}>
{video.title}
</option>
))}
</Select>
) : (
<Input
header="X url"
placeholder="Paste your X post link here"
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
)}
<Input
header="Reward points"
placeholder="I am usual input, just leave me alone"
Expand Down
2 changes: 1 addition & 1 deletion apps/creator/src/components/Quests/QuestListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type QuestListItemProps = Pick<CardProps, 'onClick'> & {

const iconByType = new Map<string, ReactNode>([
['video', <PlayIcon />],
['post_x', <XIcon />],
['share', <XIcon />],
]);

export const QuestListItem = ({ title, description, questType, rewardPoints, onClick }: QuestListItemProps) => {
Expand Down
4 changes: 3 additions & 1 deletion apps/creator/src/screens/Quests/Quests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export const Quests = () => {
mode="filled"
size="s"
style={{ alignItems: 'center' }}
onClick={() => setSelectedQuest({ title: '', description: '', type: '', videoId: '', rewardPoints: 0 })}
onClick={() =>
setSelectedQuest({ title: '', description: '', type: '', videoId: '', url: '', rewardPoints: 0 })
}
>
Add quest
</Button>
Expand Down
31 changes: 19 additions & 12 deletions apps/viewer/src/screens/ActiveQuests/ActiveQuests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import { useBot, useEvents } from '../../hooks';
import { useEffect, useMemo, useState } from 'react';
import { Campaign, Quest } from '@tg-app/api';
import { getActiveCampaign } from '@integration-telegram-app/creator/src/helpers';
import { ActivityEvent } from '@cere-activity-sdk/events';
import { ActivityEvent, CereWalletSigner } from '@cere-activity-sdk/events';
import { EngagementEventData } from '~/types';
import { useCereWallet } from '../../cere-wallet';

export const ActiveQuests = () => {
const bot = useBot();
const [activeCampaign, setActiveCampaign] = useState<Campaign>();
const [quests, setQuests] = useState<Quest[]>([]);
const [preparingData, setPreparingData] = useState<boolean>(true);
const [completedTaskIds, setCompletedTaskIds] = useState<number[]>([]);
const [completedTaskIds, setCompletedTaskIds] = useState<number[]>([551]);
const [accountId, setAccountId] = useState<string>();
const eventSource = useEvents();
const cereWallet = useCereWallet();

useEffect(() => {
const signer = new CereWalletSigner(cereWallet);
signer.isReady().then(() => {
setAccountId(signer.address);
});
}, [accountId, cereWallet]);

useEffect(() => {
bot.getCampaigns().then((campaigns) => {
Expand Down Expand Up @@ -79,17 +89,11 @@ export const ActiveQuests = () => {

const sortedQuests = useMemo(() => {
return [...quests].sort((a, b) => {
const aCompleted = completedTaskIds.includes(a.id as number);
const bCompleted = completedTaskIds.includes(b.id as number);
const aCompleted = completedTaskIds.includes(Number(a?.videoId));
const bCompleted = completedTaskIds.includes(Number(b?.videoId));

if (!aCompleted && bCompleted) return -1;
if (aCompleted && !bCompleted) return 1;

const aHasVideo = Boolean(a.videoId);
const bHasVideo = Boolean(b.videoId);

if (aHasVideo && !bHasVideo) return -1;
if (!aHasVideo && bHasVideo) return 1;
if (!aCompleted && bCompleted) return -1;

return (b.rewardPoints || 0) - (a.rewardPoints || 0);
});
Expand Down Expand Up @@ -121,7 +125,10 @@ export const ActiveQuests = () => {
name={quest?.title || ''}
description={quest?.description || ''}
rewardPoints={quest?.rewardPoints || 0}
questType={quest.type as 'video' | 'post_x'}
questType={quest.type as 'video' | 'share'}
postUrl={quest.url || ''}
accountId={accountId}
campaignId={activeCampaign?.id}
/>
))}
</QuestsList>
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/BotApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type Quest = {
description?: string;
type?: string;
videoId?: string;
url?: string;
rewardPoints?: number;
};

Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/components/QuestsList/QuestsListItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
transition: transform 0.3s ease;
}

.dark-icon > path {
fill: var(--tgui--button_color);
}

.arrow-icon.open {
transform: rotate(90deg);
}
67 changes: 55 additions & 12 deletions packages/ui/src/components/QuestsList/QuestsListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
import { Badge, Card, CardProps, Input, Text } from '@telegram-apps/telegram-ui';
import { Badge, Card, CardProps, Text } from '@telegram-apps/telegram-ui';
import './QuestsListItem.css';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { ArrowIcon, Button } from '@tg-app/ui';
import { useMiniApp } from '@telegram-apps/sdk-react';

export type QuestsListItemProps = Pick<CardProps, 'onClick'> & {
name: string;
description: string;
rewardPoints: number;
questType: 'video' | 'post_x';
questType: 'video' | 'share';
postUrl?: string;
loading?: boolean;
completed?: boolean;
accountId?: string;
campaignId?: number;
};

export const QuestsListItem = ({ name, description, rewardPoints, questType, completed }: QuestsListItemProps) => {
export const QuestsListItem = ({
name,
description,
rewardPoints,
questType,
completed,
postUrl,
accountId,
campaignId,
}: QuestsListItemProps) => {
const [isOpen, setIsOpen] = useState(false);
const miniApp = useMiniApp();

const handleClick = () => {
if (questType === 'post_x') {
if (questType === 'share') {
setIsOpen((prev) => !prev);
}
};

const handleRetweet = useCallback(() => {
if (questType === 'share' && postUrl) {
const text = encodeURIComponent(`${accountId}:${campaignId} #CereMedia`);
// @TODO think about how to do it generic
const quoteTweetUrl = `https://twitter.com/intent/tweet?text=${text}&url=${encodeURIComponent(postUrl)}`;

if (miniApp && typeof (miniApp as any).postEvent === 'function') {
(miniApp as any).postEvent('web_app_open_link', {
url: quoteTweetUrl,
});
} else {
window.open(quoteTweetUrl, '_blank');
}
}
}, [accountId, campaignId, miniApp, postUrl, questType]);

return (
<Card style={{ margin: 16, display: 'block' }}>
<Card.Cell
Expand All @@ -33,23 +63,36 @@ export const QuestsListItem = ({ name, description, rewardPoints, questType, com
</Badge>
}
>
{questType === 'post_x' && (
<ArrowIcon className={`arrow-icon ${isOpen ? 'open' : ''}`} style={{ marginRight: '12px' }} />
{questType === 'share' && (
<ArrowIcon
className={`arrow-icon ${isOpen ? 'open' : ''} ${miniApp.isDark ? '' : 'dark-icon'}`}
style={{ marginRight: '12px' }}
/>
)}
{name}
</Card.Cell>
{questType === 'post_x' && isOpen && (
{questType === 'share' && isOpen && (
<div style={{ padding: '16px' }}>
<div style={{ display: 'flex', flexDirection: 'column', backgroundColor: '#F5FAFC12' }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
backgroundColor: '#F5FAFC12',
padding: '12px',
borderRadius: '12px',
}}
>
<Text color="white">How to participate:</Text>
<Text color="white">1. Repost our announcement on X (Twitter)</Text>
<Text color="white">2. Submit your tweet URL to earn points</Text>
</div>
<Button style={{ width: '100%', marginTop: '12px', marginBottom: '24px', borderRadius: '8px' }} mode="cta">
<Button
style={{ width: '100%', marginTop: '12px', marginBottom: '24px', borderRadius: '8px' }}
mode="cta"
onClick={handleRetweet}
>
Repost on X (Twitter)
</Button>
<Input header="Url" placeholder="Enter your tweet URL" />
<Button style={{ marginTop: '12px', width: '100%', borderRadius: '8px' }}>Submit Tweet URL</Button>
</div>
)}
</Card>
Expand Down

0 comments on commit 727dbbb

Please sign in to comment.