Initial commit: Tapalka monorepo (bot, front, strapi)

This commit is contained in:
2026-05-21 11:54:44 +07:00
commit 9ec9485940
208 changed files with 58314 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
.shop {
background: #101010;
height: calc(100vh - 82px);
font-family: var(--font-manrope);
overflow-y: auto;
padding-bottom: 82px;
}
.item_img {
--size: 32px;
width: var(--size);
height: var(--size);
max-width: var(--size);
max-height: var(--size);
}
.item_img_sm {
--size: 18px;
width: var(--size);
height: var(--size);
max-width: var(--size);
max-height: var(--size);
}
.item_img > img, .item_img_sm > img {
width: 100%;
height: 100%;
}
.content {
padding-top: 45px;
padding-left: 1rem;
padding-right: 1rem;
}
.shop.overflow {
padding-bottom: 90px;
}
.header {
padding-bottom: 1rem;
}
.itemsAll {
color: #42BB47;
font-weight: 500;
font-size: 16px;
line-height: 22px;
}
.header span {
color: white;
font-weight: 600;
font-size: 19px;
line-height: 20px;
}
.exchangeRobucks {
display: flex;
align-items: center;
gap: 4px;
}
.items {
min-height: 155px;
border-radius: 1.5rem;
padding: 20px 16px;
background: #0E0E10;
display: flex;
flex-direction: column;
gap: 8px;
}
.itemsHeader {
display: flex;
justify-content: space-between;
}
.item {
cursor: pointer;
background: #FFFFFF0D;
height: 76px;
border-radius: 1rem;
display: flex;
align-items: center;
padding: 1rem;
gap: 8px;
}
.shopItem {
cursor: pointer;
background: #FFFFFF0D;
height: 76px;
border-radius: 1rem;
display: flex;
align-items: center;
padding: 1rem;
gap: 8px;
}
.shopItemPrice {
display: flex;
align-items: center;
gap: 8px;
}
.itemInfo {
display: flex;
flex-direction: column;
}
.itemInfoTitle span {
font-weight: 600;
font-size: 18px;
line-height: 24px;
}
.itemInfoContent {
display: flex;
align-items: center;
gap: 5px;
}
.upgrades {
margin-top: 1rem;
border-radius: 1.5rem;
background: #0E0E10;
border: 1px solid #18181A;
padding: 20px 16px;
}
.upgradesTitle span {
color: white;
font-weight: 600;
font-size: 19px;
line-height: 20.9px;
}
.upgradesContent {
padding-top: 1rem;
}
.upgradesContent span {
font-weight: 500;
color: white;
font-size: 16px;
line-height: 21px;
}
.shopItems {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 0.8rem;
}

View File

@@ -0,0 +1,84 @@
'use client';
import styles from '@/components/shop/Shop.module.css';
import { FC, useEffect, useRef, useState } from 'react';
import ModalExchangeContainer from '@/components/ui/modal-exchange/ModalExchangeContainer';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import ShopItem from '@/components/shop/ShopItem';
import { IHandleOpen } from '@/components/shop/ShopContainer';
import { useShopItemsStore } from '@/store/shopItems.state';
import { usePlayerStore } from '@/store/player.state';
import { toFixed } from '@/utils/FormatNumber';
interface IShop {
isOpen: boolean;
handleClose: () => void;
handleOpen: (data: IHandleOpen) => void;
}
const Shop: FC<IShop> = ({ isOpen, handleOpen, handleClose }) => {
const shopBlockRef = useRef<HTMLDivElement>(null);
const [isOverflow, setIsOverflow] = useState(false);
const { shopItems } = useShopItemsStore();
const { player } = usePlayerStore();
useEffect(() => {
const checkOverflow = () => {
if (shopBlockRef.current) {
setIsOverflow(
shopBlockRef.current.scrollHeight > shopBlockRef.current.clientHeight,
);
}
};
checkOverflow();
window.addEventListener('resize', checkOverflow);
return () => window.removeEventListener('resize', checkOverflow);
}, []);
return (
<div className={`${styles.shop} ${isOverflow ? styles.overflow : ''}`} ref={shopBlockRef}>
<div className={styles.content}>
<div className={styles.header}>
<span>Магазин</span>
</div>
<div className={styles.items}>
<div className={styles.itemsHeader}>
<span>Товары</span>
<span className={styles.itemsAll}>Все</span>
</div>
<div className={styles.shopItems}>
{shopItems &&
shopItems.map(shopItem => (
<ShopItem
key={shopItem.id}
shopItem={shopItem}
handleOpen={handleOpen}
/>
))}
</div>
</div>
<div className={styles.upgrades}>
<div className={styles.upgradesTitle}>
<span>Улучшения</span>
</div>
<div className={styles.upgradesContent}>
<span>Уже скоро...</span>
</div>
</div>
<ModalExchangeContainer
isOpen={isOpen}
balance={toFixed(player?.attributes.balance)}
onClose={handleClose}
/>
<ToastContainer />
</div>
</div>
);
};
export default Shop;

View File

@@ -0,0 +1,42 @@
'use client';
import Shop from '@/components/shop/Shop';
import { useCallback, useState } from 'react';
import { useModalStore } from '@/store/modal.state';
export interface IHandleOpen {
image?: string;
isDecimal?: boolean;
priceItem?: number;
shopItem?: number;
}
const ShopContainer = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const { setShopItemId, setPriceItem, setImage, setIsDecimal } = useModalStore();
const handleClose = useCallback(() => {
setIsOpen(false);
}, []);
const handleOpen = useCallback((data: IHandleOpen) => {
if (data.image && data.priceItem && data.shopItem && data.isDecimal !== undefined) {
setIsDecimal(data.isDecimal);
setImage(data.image);
setPriceItem(data.priceItem);
setShopItemId(data.shopItem);
setIsOpen(true);
} else {
setIsOpen(true);
setIsDecimal(true);
setImage('/icons/small-coin-icon.svg');
setPriceItem(0);
setShopItemId(null);
}
}, []);
return <Shop isOpen={isOpen} handleClose={handleClose} handleOpen={handleOpen} />;
};
export default ShopContainer;

View File

@@ -0,0 +1,63 @@
'use client';
import { FC } from 'react';
import { IShopItem } from '@/api/shop-item.api';
import styles from '@/components/shop/Shop.module.css';
import Image from 'next/image';
import { IHandleOpen } from '@/components/shop/ShopContainer';
interface IShopItemProps {
shopItem: IShopItem;
handleOpen: (data: IHandleOpen) => void;
}
const ShopItem: FC<IShopItemProps> = ({ shopItem, handleOpen }) => {
return (
<div
className={styles.item}
onClick={() =>
handleOpen({
image: `${process.env.NEXT_PUBLIC_STRAPI || ''}${shopItem.attributes.icon.data.attributes.url}`,
isDecimal: shopItem.attributes.isDecimal,
priceItem: shopItem.attributes.price,
shopItem: shopItem.id,
})
}
>
{shopItem?.attributes?.icon?.data && (
<div className={styles.item_img}>
<img
loading={'lazy'}
src={`${process.env.NEXT_PUBLIC_STRAPI || ''}${shopItem.attributes.icon.data.attributes.url}`}
alt={'shop item icon'}
/>
</div>
)}
<div className={styles.itemInfo}>
<div className={styles.itemInfoTitle}>
<span>{shopItem.attributes.name}</span>
</div>
<div className={styles.itemInfoContent}>
<span>{shopItem.attributes.price}</span>
<Image
src={'/icons/small-robucks-icon.svg'}
alt={'small robucks'}
width={18}
height={18}
/>
<span>=</span>
<span>1</span>
<div className={styles.item_img_sm}>
<img
loading={'lazy'}
src={`${process.env.NEXT_PUBLIC_STRAPI || ''}${shopItem.attributes.icon.data.attributes.url}`}
alt={'small coin icon'}
/>
</div>
</div>
</div>
</div>
);
};
export default ShopItem;