tg buttons /viewContent, tags, validation address form and more /uploadContent
This commit is contained in:
parent
372f84d1b1
commit
7705908484
|
|
@ -0,0 +1 @@
|
|||
VITE_API_BASE_URL=https://my-public-node-1.projscale.dev/api/v1
|
||||
|
|
@ -2,3 +2,4 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
.env
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
"@tonconnect/ui-react": "^2.0.2",
|
||||
"@vkruglikov/react-telegram-web-app": "^2.1.9",
|
||||
"axios": "^1.6.7",
|
||||
"buffer": "^6.0.3",
|
||||
"clsx": "^2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -2054,6 +2055,26 @@
|
|||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
|
||||
|
|
@ -2139,6 +2160,30 @@
|
|||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
|
|
@ -3621,6 +3666,26 @@
|
|||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@tonconnect/ui-react": "^2.0.2",
|
||||
"@vkruglikov/react-telegram-web-app": "^2.1.9",
|
||||
"axios": "^1.6.7",
|
||||
"buffer": "^6.0.3",
|
||||
"clsx": "^2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "~/app/styles/globals.css";
|
||||
import '~/shared/libs/buffer';
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
|
|
|
|||
|
|
@ -26,18 +26,26 @@
|
|||
/*Input Range*/
|
||||
/* Custom styles for the range input */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none; /* Remove default appearance */
|
||||
width: 100%; /* Full width */
|
||||
height: 2px; /* Track height */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
white 0%, /* Start color of passed track */
|
||||
white var(--value-percentage, 0%), /* End color of passed track */
|
||||
gray var(--value-percentage, 0%), /* Start color of remaining track */
|
||||
gray 100% /* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px; /* Rounded-full track */
|
||||
outline: none; /* Remove outline */
|
||||
-webkit-appearance: none;
|
||||
/* Remove default appearance */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 2px;
|
||||
/* Track height */
|
||||
background: linear-gradient(to right,
|
||||
white 0%,
|
||||
/* Start color of passed track */
|
||||
white var(--value-percentage, 0%),
|
||||
/* End color of passed track */
|
||||
gray var(--value-percentage, 0%),
|
||||
/* Start color of remaining track */
|
||||
gray 100%
|
||||
/* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full track */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
|
|
@ -45,45 +53,64 @@
|
|||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 9px; /* Thumb width */
|
||||
height: 9px; /* Thumb height */
|
||||
background: #fff; /* Thumb color (blue-500) */
|
||||
border-radius: 9999px; /* Rounded-full thumb */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
width: 9px;
|
||||
/* Thumb width */
|
||||
height: 9px;
|
||||
/* Thumb height */
|
||||
background: #fff;
|
||||
/* Thumb color (blue-500) */
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full thumb */
|
||||
cursor: pointer;
|
||||
/* Pointer cursor on hover */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
appearance: none;
|
||||
width: 9px; /* Thumb width */
|
||||
height: 9px; /* Thumb height */
|
||||
background: #fff; /* Thumb color (blue-500) */
|
||||
border-radius: 9999px; /* Rounded-full thumb */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
width: 9px;
|
||||
/* Thumb width */
|
||||
height: 9px;
|
||||
/* Thumb height */
|
||||
background: #fff;
|
||||
/* Thumb color (blue-500) */
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full thumb */
|
||||
cursor: pointer;
|
||||
/* Pointer cursor on hover */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-track {
|
||||
-webkit-appearance: none; /* Remove default appearance */
|
||||
width: 100%; /* Full width */
|
||||
height: 2px; /* Track height */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
white 0%, /* Start color of passed track */
|
||||
white var(--value-percentage, 0%), /* End color of passed track */
|
||||
gray var(--value-percentage, 0%), /* Start color of remaining track */
|
||||
gray 100% /* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px; /* Rounded-full track */
|
||||
outline: none; /* Remove outline */
|
||||
-webkit-appearance: none;
|
||||
/* Remove default appearance */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 2px;
|
||||
/* Track height */
|
||||
background: linear-gradient(to right,
|
||||
white 0%,
|
||||
/* Start color of passed track */
|
||||
white var(--value-percentage, 0%),
|
||||
/* End color of passed track */
|
||||
gray var(--value-percentage, 0%),
|
||||
/* Start color of remaining track */
|
||||
gray 100%
|
||||
/* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full track */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
/* English comment: override react-tag-input classes */
|
||||
.ReactTags__tagInputField {
|
||||
@apply bg-transparent text-gray placeholder:text-gray outline-none w-full h-8;
|
||||
@apply border border-white py-[20px] !important;
|
||||
@apply bg-[#2B2B2B] outline-none w-full h-8 text-sm !important;
|
||||
@apply border border-white px-[10px] py-[18px] !important;
|
||||
@apply whitespace-pre !important;
|
||||
/* English comment:
|
||||
'bg-transparent' to blend with your dark background
|
||||
'text-white' to have white text
|
||||
|
|
@ -94,7 +121,7 @@
|
|||
|
||||
/* English comment: style for the tag itself when it's rendered */
|
||||
.ReactTags__selected .ReactTags__tag {
|
||||
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1;
|
||||
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 mb-2 rounded mr-1 !important;
|
||||
/* English comment:
|
||||
'bg-[#363636]' to have a dark gray background
|
||||
'text-white' keeps the text white
|
||||
|
|
@ -105,11 +132,11 @@
|
|||
}
|
||||
|
||||
.ReactTags__selected .ReactTags__remove {
|
||||
@apply ml-1 text-gray hover:text-white cursor-pointer;
|
||||
@apply ml-1 text-gray hover:text-white cursor-pointer !important;
|
||||
/* English comment:
|
||||
'ml-1' a small margin to separate the 'x' or close symbol
|
||||
'text-gray-400' by default, and change to white on hover
|
||||
'cursor-pointer' so it looks clickable
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
|||
content: fileUploadResult.content_id_v1,
|
||||
image: coverUploadResult.content_id_v1,
|
||||
price: String(rootStore.price * 10 ** 9),
|
||||
|
||||
hashtags: rootStore.hashtags,
|
||||
royaltyParams: rootStore.royalty.map((member) => ({
|
||||
...member,
|
||||
value: member.value * 100,
|
||||
|
|
@ -151,6 +151,24 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
|||
</FormLabel>
|
||||
)}
|
||||
|
||||
{rootStore.author && (
|
||||
<FormLabel label={"Теги"}>
|
||||
<div
|
||||
className="flex flex-wrap gap-1">
|
||||
{rootStore.hashtags.map((tag, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={
|
||||
"bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1"
|
||||
}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormLabel label={"Цена"}>
|
||||
<div
|
||||
className={
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import { Button } from "~/shared/ui/button";
|
|||
import { useRootStore } from "~/shared/stores/root";
|
||||
import { BackButton } from "~/shared/ui/back-button";
|
||||
|
||||
const MIN_PRICE = 0.07;
|
||||
const MIN_RESALE_PRICE = 0.07;
|
||||
const MIN_PRICE = 0.15;
|
||||
const MIN_RESALE_PRICE = 0.15;
|
||||
|
||||
const RECOMMENDED_PRICE = 0.15;
|
||||
// const RECOMMENDED_PRICE = 0.15;
|
||||
// const RECOMMENDED_RESALE_PRICE = 0.15;
|
||||
|
||||
type PriceStepProps = {
|
||||
|
|
@ -25,9 +25,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
|||
|
||||
const formSchema = useMemo(() => {
|
||||
const parsePrice = (value: unknown) => {
|
||||
if (typeof value === "string") {
|
||||
// Replace commas with dots and parse the value
|
||||
const parsedValue = parseFloat(value.replace(",", "."));
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
const stringValue = value.toString().replace(",", ".");
|
||||
const parsedValue = parseFloat(stringValue);
|
||||
return isNaN(parsedValue) ? undefined : parsedValue;
|
||||
}
|
||||
return undefined;
|
||||
|
|
@ -37,10 +37,10 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
|||
return z.object({
|
||||
price: z.preprocess(
|
||||
parsePrice,
|
||||
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
),
|
||||
resaleLicensePrice: z
|
||||
.preprocess(parsePrice, z.number().min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
|
||||
.preprocess(parsePrice, z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
|
||||
.optional(),
|
||||
});
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
|||
return z.object({
|
||||
price: z.preprocess(
|
||||
parsePrice,
|
||||
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
),
|
||||
});
|
||||
}, [rootStore.allowResale]);
|
||||
|
|
@ -59,9 +59,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
|||
resolver: zodResolver(formSchema),
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
price: rootStore.price,
|
||||
price: rootStore.price || MIN_PRICE,
|
||||
//@ts-expect-error Fix typings
|
||||
resaleLicensePrice: rootStore?.licenseResalePrice,
|
||||
resaleLicensePrice: rootStore?.licenseResalePrice || MIN_RESALE_PRICE,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -98,13 +98,22 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
|||
<FormLabel label={"Цена продажи TON"}>
|
||||
<div className={"my-2 flex flex-col gap-1.5"}>
|
||||
<p className={"text-xs"}>Минимальная стоимость {MIN_PRICE} TON.</p>
|
||||
<p className={"text-xs"}>Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.</p>
|
||||
{/* <p className={"text-xs"}>Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.</p> */}
|
||||
</div>
|
||||
<Input
|
||||
error={form.formState.errors?.price}
|
||||
placeholder={"[ Введите цену ]"}
|
||||
{...form.register("price")}
|
||||
/>
|
||||
inputMode="decimal"
|
||||
pattern="[0-9]*[.,]?[0-9]*"
|
||||
{...form.register("price", {
|
||||
onChange: (e) => {
|
||||
const value = e.target.value;
|
||||
if (!/^\d*[.,]?\d*$/.test(value)) {
|
||||
e.target.value = value.replace(/[^\d.,]/g, '');
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormLabel>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -12,17 +12,30 @@ import { ConfirmModal } from "~/pages/root/steps/royalty-step/components/confirm
|
|||
import { useRootStore } from "~/shared/stores/root";
|
||||
import { BackButton } from "~/shared/ui/back-button";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { Address } from "@ton/core";
|
||||
import { FieldError } from "react-hook-form";
|
||||
|
||||
type RoyaltyStepProps = {
|
||||
prevStep(): void;
|
||||
nextStep(): void;
|
||||
};
|
||||
|
||||
const isValidTonAddress = (address: string): boolean => {
|
||||
try {
|
||||
if (!address) return false;
|
||||
Address.parse(address);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
|
||||
const [isDeleteAllOpen, setDeleteAllOpen] = useState(false);
|
||||
const [isSpreadOpen, setSpreadOpen] = useState(false);
|
||||
const [addressErrors, setAddressErrors] = useState<Record<number, FieldError | undefined>>({});
|
||||
|
||||
const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } =
|
||||
useRootStore();
|
||||
|
|
@ -42,11 +55,23 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
|||
};
|
||||
|
||||
const handleWalletChange = (index: number, address: string) => {
|
||||
const isValid = isValidTonAddress(address);
|
||||
setAddressErrors({
|
||||
...addressErrors,
|
||||
[index]: !isValid
|
||||
? {
|
||||
type: 'validation',
|
||||
message: 'Неверный адрес TON'
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
const newRoyalty = royalty.map((member, i) =>
|
||||
i === index ? { ...member, address } : member,
|
||||
i === index ? { ...member, address } : member
|
||||
);
|
||||
setRoyalty(newRoyalty);
|
||||
};
|
||||
|
||||
|
||||
const handlePercentChange = (index: number, value: string) => {
|
||||
const percentNumber = parseInt(value, 10) || 0;
|
||||
|
|
@ -67,20 +92,28 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
|||
|
||||
const isValid = useMemo(() => {
|
||||
return (
|
||||
royalty.every((member) => member.address && member.value >= 0) &&
|
||||
royalty.every((member) => isValidTonAddress(member.address) && member.value >= 0) &&
|
||||
royalty.reduce((acc, curr) => acc + curr.value, 0) === 100
|
||||
);
|
||||
}, [royalty]);
|
||||
|
||||
const [tonConnectUI] = useTonConnectUI();
|
||||
|
||||
// Устанавливаем адрес из tonConnectUI.account при загрузке страницы
|
||||
useEffect(() => {
|
||||
console.log('tonconnectUI', tonConnectUI)
|
||||
if (tonConnectUI.account) {
|
||||
setRoyalty([{ address: tonConnectUI.account.address, value: 100 }]);
|
||||
if (!tonConnectUI.account) return;
|
||||
|
||||
if (royalty.length === 0) {
|
||||
// First initialization with 100%
|
||||
setRoyalty([{
|
||||
address: Address.parse(tonConnectUI.account.address).toString({
|
||||
bounceable: true,
|
||||
urlSafe: true,
|
||||
testOnly: false,
|
||||
}),
|
||||
value: 100
|
||||
}]);
|
||||
}
|
||||
}, [tonConnectUI.account, setRoyalty]);
|
||||
}, [tonConnectUI.account, setRoyalty, royalty]);
|
||||
|
||||
return (
|
||||
<section className={"mt-4 px-4 pb-8"}>
|
||||
|
|
@ -128,7 +161,7 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
|||
<section className={"flex flex-col gap-1.5"}>
|
||||
{royalty.map((member, index) => (
|
||||
<div key={index} className={"flex flex-col gap-[20px]"}>
|
||||
<div className={"flex w-full items-center gap-1"}>
|
||||
<div className={"flex w-full items-start gap-1"}>
|
||||
<div className={"w-[83%]"}>
|
||||
<FormLabel
|
||||
labelClassName={"flex"}
|
||||
|
|
@ -145,7 +178,8 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
|||
value={member.address}
|
||||
onChange={(e) => handleWalletChange(index, e.target.value)}
|
||||
placeholder={"[ Введите адрес криптокошелька TON ]"}
|
||||
/>
|
||||
error={addressErrors[index]}
|
||||
/>
|
||||
</FormLabel>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import ReactPlayer from "react-player/lazy";
|
|||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
|
||||
import { Button } from "~/shared/ui/button";
|
||||
import { usePurchaseContent, useViewContent } from "~/shared/services/content";
|
||||
import { fromNanoTON } from "~/shared/utils";
|
||||
import {useCallback, useEffect, useMemo} from "react";
|
||||
|
|
@ -72,7 +71,43 @@ export const ViewContentPage = () => {
|
|||
}, []);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const mainButton = WebApp.MainButton;
|
||||
const secondaryButton = WebApp.SecondaryButton;
|
||||
|
||||
try {
|
||||
// Set main button color
|
||||
mainButton.color = '#e40615';
|
||||
mainButton.textColor = '#FFFFFF';
|
||||
|
||||
// Set secondary button color
|
||||
secondaryButton.color = '#363636';
|
||||
secondaryButton.textColor = '#FFFFFF';
|
||||
if (!haveLicense) {
|
||||
mainButton.text = `Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`;
|
||||
mainButton.show();
|
||||
mainButton.onClick(handleBuyContent);
|
||||
} else {
|
||||
mainButton.hide();
|
||||
}
|
||||
|
||||
secondaryButton.text = 'Загрузить свой контент';
|
||||
secondaryButton.show();
|
||||
secondaryButton.onClick(() => {
|
||||
WebApp.openTelegramLink('https://t.me/MY_UploaderRobot');
|
||||
});
|
||||
|
||||
return () => {
|
||||
mainButton.hide();
|
||||
mainButton.offClick(handleBuyContent);
|
||||
secondaryButton.hide();
|
||||
secondaryButton.offClick();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error setting up Telegram WebApp buttons:', error);
|
||||
}
|
||||
}, [content, haveLicense, WebApp, handleBuyContent]);
|
||||
|
||||
return (
|
||||
<main className={"flex w-full flex-col gap-[50px] px-4"}>
|
||||
{content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
|
||||
|
|
@ -111,7 +146,7 @@ export const ViewContentPage = () => {
|
|||
</p>
|
||||
</section>
|
||||
|
||||
{!haveLicense && <Button
|
||||
{/* {!haveLicense && <Button
|
||||
onClick={handleBuyContent}
|
||||
className={"mb-4 mt-[30px] h-[48px]"}
|
||||
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
|
||||
|
|
@ -125,7 +160,7 @@ export const ViewContentPage = () => {
|
|||
}}
|
||||
className={"mb-4 mt-[-20px] h-[48px] bg-darkred"}
|
||||
label={`Загрузить свой контент`}
|
||||
/>
|
||||
/> */}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
import { Buffer } from 'buffer';
|
||||
globalThis.Buffer = Buffer;
|
||||
|
|
@ -8,6 +8,7 @@ type UseCreateNewContentPayload = {
|
|||
content: string;
|
||||
image: string;
|
||||
description: string;
|
||||
hashtags: string[];
|
||||
price: string;
|
||||
resaleLicensePrice: string; // nanoTON bignum (default = 0)
|
||||
allowResale: boolean;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export const useRootStore = create<RootStore>((set) => ({
|
|||
royalty: [{ address: "", value: 100 }],
|
||||
setRoyalty: (royalty) => set({ royalty }),
|
||||
|
||||
price: 0,
|
||||
price: 0.15,
|
||||
setPrice: (price: number) => set({ price }),
|
||||
|
||||
allowResale: false,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { WithContext as ReactTags } from "react-tag-input";
|
||||
import { WithContext as ReactTags, SEPARATORS } from "react-tag-input";
|
||||
import { useRootStore } from "~/shared/stores/root";
|
||||
|
||||
// English comment: no extra Tag interface, just cast to any
|
||||
export const HashtagInput = () => {
|
||||
const { hashtags, setHashtags } = useRootStore();
|
||||
|
|
@ -9,9 +8,7 @@ export const HashtagInput = () => {
|
|||
// English comment: local state as string[] for simplicity
|
||||
const [tags, setTags] = useState<string[]>(hashtags);
|
||||
|
||||
const KeyCodes = { comma: 188, enter: 13 };
|
||||
const delimiters = [KeyCodes.comma, KeyCodes.enter];
|
||||
|
||||
const separators = [SEPARATORS.ENTER, SEPARATORS.COMMA]
|
||||
const handleDelete = (i: number) => {
|
||||
const newTags = tags.filter((_, index) => index !== i);
|
||||
setTags(newTags);
|
||||
|
|
@ -20,20 +17,47 @@ export const HashtagInput = () => {
|
|||
|
||||
// English comment: pass "any" to the function
|
||||
const handleAddition = (newTag: any) => {
|
||||
// English comment: newTag might be { id, text, ... } from react-tag-input
|
||||
const updatedTags = [...tags, newTag?.text || newTag?.id || ""];
|
||||
// Clean up text from commas and trim whitespace
|
||||
const text = newTag?.text || newTag?.id || "";
|
||||
const cleanText = text.replace(/,/g, '').trim();
|
||||
|
||||
// Skip empty tags
|
||||
if (!cleanText) return;
|
||||
|
||||
const updatedTags = [...tags, cleanText];
|
||||
setTags(updatedTags);
|
||||
setHashtags(updatedTags);
|
||||
};
|
||||
|
||||
// Simulate Enter keypress for Android
|
||||
const enterEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
which: 13,
|
||||
keyCode: 13,
|
||||
});
|
||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Unidentified') {
|
||||
const lastChar = e.currentTarget.value.slice(-1);
|
||||
if (lastChar === ',') {
|
||||
e.currentTarget.dispatchEvent(enterEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactTags
|
||||
tags={tags.map((t) => ({ id: t, text: t })) as any}
|
||||
delimiters={delimiters as any}
|
||||
separators={separators as any}
|
||||
handleDelete={handleDelete as any}
|
||||
handleAddition={handleAddition as any}
|
||||
allowDragDrop={false}
|
||||
placeholder="[ enter a hashtag ]"
|
||||
placeholder="[ введите тэги через запятую ]"
|
||||
inputProps={{
|
||||
onKeyUp: handleKeyUp as any
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,4 +4,19 @@ import TSPaths from "vite-tsconfig-paths";
|
|||
|
||||
export default defineConfig({
|
||||
plugins: [react(), TSPaths()],
|
||||
});
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
buffer: 'buffer/',
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: 'globalThis'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
35
yarn.lock
35
yarn.lock
|
|
@ -225,10 +225,10 @@
|
|||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@esbuild/darwin-arm64@0.19.12":
|
||||
"@esbuild/win32-x64@0.19.12":
|
||||
version "0.19.12"
|
||||
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz"
|
||||
integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
|
||||
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz"
|
||||
integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
|
|
@ -381,10 +381,10 @@
|
|||
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz"
|
||||
integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.12.0":
|
||||
"@rollup/rollup-win32-x64-msvc@4.12.0":
|
||||
version "4.12.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz"
|
||||
integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz"
|
||||
integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==
|
||||
|
||||
"@ton/core@^0.59.1":
|
||||
version "0.59.1"
|
||||
|
|
@ -860,6 +860,11 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
big-integer@^1.6.16:
|
||||
version "1.6.52"
|
||||
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
|
||||
|
|
@ -916,6 +921,14 @@ browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0":
|
|||
node-releases "^2.0.14"
|
||||
update-browserslist-db "^1.0.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz"
|
||||
|
|
@ -1569,11 +1582,6 @@ fs.realpath@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
|
|
@ -1764,6 +1772,11 @@ hoist-non-react-statics@^3.3.2:
|
|||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
ignore@^5.2.0, ignore@^5.2.4:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
|
||||
|
|
|
|||
Loading…
Reference in New Issue