Initial commit: Tapalka monorepo (bot, front, strapi)
This commit is contained in:
65
CMyTapper/robucks-front/components/tasks/Task.tsx
Normal file
65
CMyTapper/robucks-front/components/tasks/Task.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
|
||||
import { ITask } from '@/api/tasks.api';
|
||||
import { FC, useCallback } from 'react';
|
||||
import styles from '@/components/tasks/Tasks.module.css';
|
||||
import Image from 'next/image';
|
||||
import { IFinishedTasks } from '@/interfaces/player.type';
|
||||
import TaskContent from '@/components/tasks/TaskContent';
|
||||
|
||||
interface ITaskProps {
|
||||
task: ITask;
|
||||
finishedTask: IFinishedTasks | undefined;
|
||||
handleClaimPrize: (taskId: number, prize: number) => Promise<void>;
|
||||
handleOpenLink: (link: string, taskId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const Task: FC<ITaskProps> = ({ task, finishedTask, handleClaimPrize, handleOpenLink }) => {
|
||||
const handleTaskFunction = useCallback(() => {
|
||||
if (!finishedTask) {
|
||||
return handleOpenLink(task.attributes.link, task.id);
|
||||
}
|
||||
}, [handleOpenLink, finishedTask]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
finishedTask && finishedTask.attributes.status === 'claimed'
|
||||
? `${styles.taskClaimed} ${styles.task}`
|
||||
: `${styles.task}`
|
||||
}
|
||||
onClick={handleTaskFunction}
|
||||
>
|
||||
{task?.attributes?.icon?.data && (
|
||||
<div className={styles.taskIcon}>
|
||||
<Image
|
||||
src={`${process.env.NEXT_PUBLIC_STRAPI || ''}${task.attributes.icon.data.attributes.url}`}
|
||||
alt={'task icon'}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.taskContent}>
|
||||
<div
|
||||
className={
|
||||
finishedTask && finishedTask.attributes.status === 'claimed'
|
||||
? `${styles.taskClaimedTitle} ${styles.taskTitle}`
|
||||
: `${styles.taskTitle}`
|
||||
}
|
||||
>
|
||||
<span>{task.attributes.title}</span>
|
||||
</div>
|
||||
<TaskContent
|
||||
taskId={task.id}
|
||||
link={task.attributes.link}
|
||||
status={finishedTask?.attributes.status}
|
||||
prize={task.attributes.prize}
|
||||
handleClaimPrize={handleClaimPrize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Task;
|
||||
61
CMyTapper/robucks-front/components/tasks/TaskContent.tsx
Normal file
61
CMyTapper/robucks-front/components/tasks/TaskContent.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
import { FC, useState } from 'react';
|
||||
import styles from '@/components/tasks/Tasks.module.css';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface ITaskContent {
|
||||
taskId: number;
|
||||
link: string;
|
||||
status: string | undefined;
|
||||
prize: number;
|
||||
handleClaimPrize: (taskId: number, prize: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const TaskContent: FC<ITaskContent> = ({ taskId, status, prize, handleClaimPrize }) => {
|
||||
const [isClaiming, setIsClaiming] = useState(false);
|
||||
|
||||
const onClaim = () => {
|
||||
setIsClaiming(true);
|
||||
handleClaimPrize(taskId, prize).catch(err => {
|
||||
console.error(err);
|
||||
setIsClaiming(false);
|
||||
});
|
||||
};
|
||||
if (status) {
|
||||
if (status === 'pending_prize') {
|
||||
return (
|
||||
<>
|
||||
<button className={styles.claimPrize} onClick={onClaim} disabled={isClaiming}>
|
||||
{`Получить +${prize}`}
|
||||
<Image
|
||||
src={'/icons/small-robucks-icon.svg'}
|
||||
alt={'small coin icon'}
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
} else if (status === 'claimed') {
|
||||
return (
|
||||
<div className={styles.claimedPrize}>
|
||||
<span>Выполнено</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.taskPrize}>
|
||||
<span>+ {prize}</span>
|
||||
<Image
|
||||
src={'/icons/small-robucks-icon.svg'}
|
||||
alt={'small coin icon'}
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default TaskContent;
|
||||
109
CMyTapper/robucks-front/components/tasks/Tasks.module.css
Normal file
109
CMyTapper/robucks-front/components/tasks/Tasks.module.css
Normal file
@@ -0,0 +1,109 @@
|
||||
.tasks {
|
||||
background: #101010;
|
||||
padding-top: 45px;
|
||||
font-family: var(--font-manrope);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tasksHeader {
|
||||
padding-left: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tasksHeader span {
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
color: white;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.tasksBlock {
|
||||
height: auto;
|
||||
margin-top: 1rem;
|
||||
padding: 30px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background: #0E0E10;
|
||||
border: 1px solid #18181A;
|
||||
border-radius: 1.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tasksBlock.overflow {
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #FFFFFF0D;
|
||||
min-height: 75px;
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
gap: 7.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.taskContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.taskTitle span {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.taskPrize {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.taskPrize span {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #BBBBBB;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.claimPrize {
|
||||
font-family: var(--font-manrope);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #101010;
|
||||
border: 1px solid #42BB47;
|
||||
width: 267px;
|
||||
height: 42px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 40px;
|
||||
gap: 5px;
|
||||
color: #42BB47;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.claimedPrize {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.claimedPrize span {
|
||||
color: #42BB47;
|
||||
}
|
||||
|
||||
.taskClaimed {
|
||||
background: #42BB4740;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.taskClaimedTitle span {
|
||||
color: #42BB47;
|
||||
}
|
||||
64
CMyTapper/robucks-front/components/tasks/Tasks.tsx
Normal file
64
CMyTapper/robucks-front/components/tasks/Tasks.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import styles from '@/components/tasks/Tasks.module.css';
|
||||
import Task from '@/components/tasks/Task';
|
||||
import { useTasksStore } from '@/store/tasks.state';
|
||||
import { usePlayerStore } from '@/store/player.state';
|
||||
|
||||
interface ITasksProps {
|
||||
handleClaimPrize: (taskId: number, prize: number) => Promise<void>;
|
||||
handleOpenLink: (link: string, taskId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const Tasks: FC<ITasksProps> = ({ handleOpenLink, handleClaimPrize }) => {
|
||||
const tasksBlockRef = useRef<HTMLDivElement>(null);
|
||||
const [isOverflow, setIsOverflow] = useState(false);
|
||||
const { tasks } = useTasksStore();
|
||||
const { player } = usePlayerStore();
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverflow = () => {
|
||||
if (tasksBlockRef.current) {
|
||||
setIsOverflow(
|
||||
tasksBlockRef.current.scrollHeight > tasksBlockRef.current.clientHeight,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
|
||||
window.addEventListener('resize', checkOverflow);
|
||||
|
||||
return () => window.removeEventListener('resize', checkOverflow);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.tasks}>
|
||||
<div className={styles.tasksHeader}>
|
||||
<span>Задания</span>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.tasksBlock} ${isOverflow ? styles.overflow : ''}`}
|
||||
ref={tasksBlockRef}
|
||||
>
|
||||
{tasks.map(task => {
|
||||
const finishedTask = player?.attributes.finished_tasks.data?.find(
|
||||
el => el?.attributes.task?.data?.id === task?.id,
|
||||
);
|
||||
return (
|
||||
<Task
|
||||
key={task.id}
|
||||
task={task}
|
||||
finishedTask={finishedTask}
|
||||
handleClaimPrize={handleClaimPrize}
|
||||
handleOpenLink={handleOpenLink}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tasks;
|
||||
54
CMyTapper/robucks-front/components/tasks/TasksContainer.tsx
Normal file
54
CMyTapper/robucks-front/components/tasks/TasksContainer.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import Tasks from '@/components/tasks/Tasks';
|
||||
import { useCallback } from 'react';
|
||||
import { StatusTask } from '@/interfaces/task.type';
|
||||
import { usePlayerStore } from '@/store/player.state';
|
||||
import { useTokenStore } from '@/store/token.state';
|
||||
import { PlayerApi } from '@/api/player.api';
|
||||
|
||||
const TasksContainer = () => {
|
||||
const { player, setPlayer } = usePlayerStore();
|
||||
const { token } = useTokenStore();
|
||||
|
||||
const handleClaimPrize = useCallback(
|
||||
async (taskId: number) => {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const playerApi = PlayerApi.getInstance(token);
|
||||
const updatedPlayer = await playerApi.updateTasks(player!.id, {
|
||||
taskId: taskId,
|
||||
status: 'claimed' as StatusTask,
|
||||
});
|
||||
const newPlayer = await playerApi.findPlayerByAuthId(
|
||||
encodeURIComponent(updatedPlayer.attributes.auth_id),
|
||||
);
|
||||
setPlayer(newPlayer);
|
||||
},
|
||||
[player, token],
|
||||
);
|
||||
|
||||
const handleOpenLink = useCallback(
|
||||
async (link: string, taskId: number) => {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
window.Telegram.WebApp.openLink(link);
|
||||
const playerApi = PlayerApi.getInstance(token);
|
||||
const updatedPlayer = await playerApi.updateTasks(player!.id, {
|
||||
taskId: taskId,
|
||||
status: 'pending_prize' as StatusTask,
|
||||
});
|
||||
const newPlayer = await playerApi.findPlayerByAuthId(
|
||||
encodeURIComponent(updatedPlayer.attributes.auth_id),
|
||||
);
|
||||
setPlayer(newPlayer);
|
||||
},
|
||||
[player, token],
|
||||
);
|
||||
|
||||
return <Tasks handleClaimPrize={handleClaimPrize} handleOpenLink={handleOpenLink} />;
|
||||
};
|
||||
|
||||
export default TasksContainer;
|
||||
Reference in New Issue
Block a user