Compare commits

..

No commits in common. "dd62962de8c5b700767f67e09945cf5dd24dd509" and "372f84d1b1d209fa8fb24c9d13c3302ec43543d2" have entirely different histories.

23 changed files with 292 additions and 580 deletions

1
.env
View File

@ -1 +0,0 @@
VITE_API_BASE_URL=https://my-public-node-1.projscale.dev/api/v1

1
.gitignore vendored
View File

@ -2,4 +2,3 @@
node_modules node_modules
.DS_Store .DS_Store
dist dist
.env

65
package-lock.json generated
View File

@ -13,7 +13,6 @@
"@tonconnect/ui-react": "^2.0.2", "@tonconnect/ui-react": "^2.0.2",
"@vkruglikov/react-telegram-web-app": "^2.1.9", "@vkruglikov/react-telegram-web-app": "^2.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"buffer": "^6.0.3",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -2055,26 +2054,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "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": { "node_modules/big-integer": {
"version": "1.6.52", "version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@ -2160,30 +2139,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "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": { "node_modules/call-bind": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@ -3666,26 +3621,6 @@
"react-is": "^16.7.0" "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": { "node_modules/ignore": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",

View File

@ -15,7 +15,6 @@
"@tonconnect/ui-react": "^2.0.2", "@tonconnect/ui-react": "^2.0.2",
"@vkruglikov/react-telegram-web-app": "^2.1.9", "@vkruglikov/react-telegram-web-app": "^2.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"buffer": "^6.0.3",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -1,5 +1,4 @@
import "~/app/styles/globals.css"; import "~/app/styles/globals.css";
import '~/shared/libs/buffer';
import { useEffect } from "react"; import { useEffect } from "react";
import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app"; import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app";

View File

@ -26,26 +26,18 @@
/*Input Range*/ /*Input Range*/
/* Custom styles for the range input */ /* Custom styles for the range input */
input[type="range"] { input[type="range"] {
-webkit-appearance: none; -webkit-appearance: none; /* Remove default appearance */
/* Remove default appearance */ width: 100%; /* Full width */
width: 100%; height: 2px; /* Track height */
/* Full width */ background: linear-gradient(
height: 2px; to right,
/* Track height */ white 0%, /* Start color of passed track */
background: linear-gradient(to right, white var(--value-percentage, 0%), /* End color of passed track */
white 0%, gray var(--value-percentage, 0%), /* Start color of remaining track */
/* Start color of passed track */ gray 100% /* End color of remaining 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; border-radius: 9999px; /* Rounded-full track */
/* Rounded-full track */ outline: none; /* Remove outline */
outline: none;
/* Remove outline */
transition: background 0.3s; transition: background 0.3s;
} }
@ -53,64 +45,45 @@
input[type="range"]::-webkit-slider-thumb { input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
width: 9px; width: 9px; /* Thumb width */
/* Thumb width */ height: 9px; /* Thumb height */
height: 9px; background: #fff; /* Thumb color (blue-500) */
/* Thumb height */ border-radius: 9999px; /* Rounded-full thumb */
background: #fff; cursor: pointer; /* Pointer cursor on hover */
/* Thumb color (blue-500) */
border-radius: 9999px;
/* Rounded-full thumb */
cursor: pointer;
/* Pointer cursor on hover */
transition: background 0.3s; transition: background 0.3s;
} }
/* For Firefox */ /* For Firefox */
input[type="range"]::-moz-range-thumb { input[type="range"]::-moz-range-thumb {
appearance: none; appearance: none;
width: 9px; width: 9px; /* Thumb width */
/* Thumb width */ height: 9px; /* Thumb height */
height: 9px; background: #fff; /* Thumb color (blue-500) */
/* Thumb height */ border-radius: 9999px; /* Rounded-full thumb */
background: #fff; cursor: pointer; /* Pointer cursor on hover */
/* Thumb color (blue-500) */
border-radius: 9999px;
/* Rounded-full thumb */
cursor: pointer;
/* Pointer cursor on hover */
transition: background 0.3s; transition: background 0.3s;
} }
input[type="range"]::-moz-range-track { input[type="range"]::-moz-range-track {
-webkit-appearance: none; -webkit-appearance: none; /* Remove default appearance */
/* Remove default appearance */ width: 100%; /* Full width */
width: 100%; height: 2px; /* Track height */
/* Full width */ background: linear-gradient(
height: 2px; to right,
/* Track height */ white 0%, /* Start color of passed track */
background: linear-gradient(to right, white var(--value-percentage, 0%), /* End color of passed track */
white 0%, gray var(--value-percentage, 0%), /* Start color of remaining track */
/* Start color of passed track */ gray 100% /* End color of remaining 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; border-radius: 9999px; /* Rounded-full track */
/* Rounded-full track */ outline: none; /* Remove outline */
outline: none;
/* Remove outline */
transition: background 0.3s; transition: background 0.3s;
} }
/* English comment: override react-tag-input classes */ /* English comment: override react-tag-input classes */
.ReactTags__tagInputField { .ReactTags__tagInputField {
@apply bg-[#2B2B2B] outline-none w-full h-8 text-sm !important; @apply bg-transparent text-gray placeholder:text-gray outline-none w-full h-8;
@apply border border-white px-[10px] py-[18px] !important; @apply border border-white py-[20px] !important;
@apply whitespace-pre !important;
/* English comment: /* English comment:
'bg-transparent' to blend with your dark background 'bg-transparent' to blend with your dark background
'text-white' to have white text 'text-white' to have white text
@ -121,7 +94,7 @@
/* English comment: style for the tag itself when it's rendered */ /* English comment: style for the tag itself when it's rendered */
.ReactTags__selected .ReactTags__tag { .ReactTags__selected .ReactTags__tag {
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 mb-2 rounded mr-1 !important; @apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1;
/* English comment: /* English comment:
'bg-[#363636]' to have a dark gray background 'bg-[#363636]' to have a dark gray background
'text-white' keeps the text white 'text-white' keeps the text white
@ -132,7 +105,7 @@
} }
.ReactTags__selected .ReactTags__remove { .ReactTags__selected .ReactTags__remove {
@apply ml-1 text-gray hover:text-white cursor-pointer !important; @apply ml-1 text-gray hover:text-white cursor-pointer;
/* English comment: /* English comment:
'ml-1' a small margin to separate the 'x' or close symbol 'ml-1' a small margin to separate the 'x' or close symbol
'text-gray-400' by default, and change to white on hover 'text-gray-400' by default, and change to white on hover

View File

@ -1,5 +1,4 @@
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app"; import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
import { Replace } from "~/shared/ui/icons/replace";
import { XMark } from "~/shared/ui/icons/x-mark.tsx"; import { XMark } from "~/shared/ui/icons/x-mark.tsx";
@ -37,8 +36,8 @@ export const CoverButton = ({ src, onClick }: CoverButtonProps) => {
} }
> >
<div /> <div />
<div className={"flex gap-2 text-sm"}>Изменить</div> <div className={"flex gap-2 text-sm"}>Удалить</div>
<Replace /> <XMark />
</button> </button>
</div> </div>
); );

View File

@ -14,8 +14,6 @@ import { useRootStore } from "~/shared/stores/root";
import { Checkbox } from "~/shared/ui/checkbox"; import { Checkbox } from "~/shared/ui/checkbox";
import { AudioPlayer } from "~/shared/ui/audio-player"; import { AudioPlayer } from "~/shared/ui/audio-player";
import { HashtagInput } from "~/shared/ui/hashtag-input"; import { HashtagInput } from "~/shared/ui/hashtag-input";
import { XMark } from "~/shared/ui/icons/x-mark";
import { Replace } from "~/shared/ui/icons/replace";
type DataStepProps = { type DataStepProps = {
nextStep(): void; nextStep(): void;
@ -59,12 +57,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
})(); })();
}; };
const handleFileReset = () => {
rootStore.setFile(null);
rootStore.setFileSrc('');
rootStore.setFileType('');
}
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
<div className={"mb-[30px] flex flex-col text-sm"}> <div className={"mb-[30px] flex flex-col text-sm"}>
@ -96,7 +88,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
</FormLabel> </FormLabel>
<FormLabel label={"Файл"}> <FormLabel label={"Файл"}>
{!rootStore.fileSrc && <>
<HiddenFileInput <HiddenFileInput
id={"file"} id={"file"}
shouldProcess={false} shouldProcess={false}
@ -108,8 +99,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
}} }}
/> />
<FileButton htmlFor={"file"} /> {!rootStore.fileSrc && <FileButton htmlFor={"file"} />}
</>}
{rootStore.fileSrc && ( {rootStore.fileSrc && (
<div <div
@ -128,16 +118,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
url={rootStore.fileSrc} url={rootStore.fileSrc}
/> />
)} )}
<button
onClick={handleFileReset}
className={
"flex w-full items-center justify-between gap-1 border border-white px-[10px] py-[8px]"
}
>
<div />
<div className={"flex gap-2 text-sm"}>Изменить выбор</div>
<Replace />
</button>
</div> </div>
)} )}
</FormLabel> </FormLabel>
@ -156,7 +136,13 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
{rootStore.allowCover && ( {rootStore.allowCover && (
<FormLabel label={"Обложка"}> <FormLabel label={"Обложка"}>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
{rootStore.cover ? ( {rootStore.cover ? (
<CoverButton <CoverButton
@ -166,16 +152,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
}} }}
/> />
) : ( ) : (
<>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
<FileButton htmlFor={"cover"} /> <FileButton htmlFor={"cover"} />
</>
)} )}
</FormLabel> </FormLabel>
)} )}

View File

@ -64,7 +64,7 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
content: fileUploadResult.content_id_v1, content: fileUploadResult.content_id_v1,
image: coverUploadResult.content_id_v1, image: coverUploadResult.content_id_v1,
price: String(rootStore.price * 10 ** 9), price: String(rootStore.price * 10 ** 9),
hashtags: rootStore.hashtags,
royaltyParams: rootStore.royalty.map((member) => ({ royaltyParams: rootStore.royalty.map((member) => ({
...member, ...member,
value: member.value * 100, value: member.value * 100,
@ -151,24 +151,6 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
</FormLabel> </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={"Цена"}> <FormLabel label={"Цена"}>
<div <div
className={ className={

View File

@ -9,10 +9,10 @@ import { Button } from "~/shared/ui/button";
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
import { BackButton } from "~/shared/ui/back-button"; import { BackButton } from "~/shared/ui/back-button";
const MIN_PRICE = 0.15; const MIN_PRICE = 0.07;
const MIN_RESALE_PRICE = 0.15; const MIN_RESALE_PRICE = 0.07;
// const RECOMMENDED_PRICE = 0.15; const RECOMMENDED_PRICE = 0.15;
// const RECOMMENDED_RESALE_PRICE = 0.15; // const RECOMMENDED_RESALE_PRICE = 0.15;
type PriceStepProps = { type PriceStepProps = {
@ -25,9 +25,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
const formSchema = useMemo(() => { const formSchema = useMemo(() => {
const parsePrice = (value: unknown) => { const parsePrice = (value: unknown) => {
if (typeof value === "string" || typeof value === "number") { if (typeof value === "string") {
const stringValue = value.toString().replace(",", "."); // Replace commas with dots and parse the value
const parsedValue = parseFloat(stringValue); const parsedValue = parseFloat(value.replace(",", "."));
return isNaN(parsedValue) ? undefined : parsedValue; return isNaN(parsedValue) ? undefined : parsedValue;
} }
return undefined; return undefined;
@ -37,10 +37,10 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
return z.object({ return z.object({
price: z.preprocess( price: z.preprocess(
parsePrice, parsePrice,
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`) z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
), ),
resaleLicensePrice: z resaleLicensePrice: z
.preprocess(parsePrice, z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`)) .preprocess(parsePrice, z.number().min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
.optional(), .optional(),
}); });
} }
@ -48,7 +48,7 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
return z.object({ return z.object({
price: z.preprocess( price: z.preprocess(
parsePrice, parsePrice,
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`) z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
), ),
}); });
}, [rootStore.allowResale]); }, [rootStore.allowResale]);
@ -59,9 +59,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
mode: "onChange", mode: "onChange",
defaultValues: { defaultValues: {
price: rootStore.price || MIN_PRICE, price: rootStore.price,
//@ts-expect-error Fix typings //@ts-expect-error Fix typings
resaleLicensePrice: rootStore?.licenseResalePrice || MIN_RESALE_PRICE, resaleLicensePrice: rootStore?.licenseResalePrice,
}, },
}); });
@ -98,21 +98,12 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
<FormLabel label={"Цена продажи TON"}> <FormLabel label={"Цена продажи TON"}>
<div className={"my-2 flex flex-col gap-1.5"}> <div className={"my-2 flex flex-col gap-1.5"}>
<p className={"text-xs"}>Минимальная стоимость {MIN_PRICE} TON.</p> <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> </div>
<Input <Input
error={form.formState.errors?.price} error={form.formState.errors?.price}
placeholder={"[ Введите цену ]"} placeholder={"[ Введите цену ]"}
inputMode="decimal" {...form.register("price")}
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> </FormLabel>
</div> </div>

View File

@ -12,30 +12,17 @@ import { ConfirmModal } from "~/pages/root/steps/royalty-step/components/confirm
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
import { BackButton } from "~/shared/ui/back-button"; import { BackButton } from "~/shared/ui/back-button";
import { useTonConnectUI } from "@tonconnect/ui-react"; import { useTonConnectUI } from "@tonconnect/ui-react";
import { Address } from "@ton/core";
import { FieldError } from "react-hook-form";
type RoyaltyStepProps = { type RoyaltyStepProps = {
prevStep(): void; prevStep(): void;
nextStep(): 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) => { export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
const [impactOccurred] = useHapticFeedback(); const [impactOccurred] = useHapticFeedback();
const [isDeleteAllOpen, setDeleteAllOpen] = useState(false); const [isDeleteAllOpen, setDeleteAllOpen] = useState(false);
const [isSpreadOpen, setSpreadOpen] = useState(false); const [isSpreadOpen, setSpreadOpen] = useState(false);
const [addressErrors, setAddressErrors] = useState<Record<number, FieldError | undefined>>({});
const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } = const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } =
useRootStore(); useRootStore();
@ -55,24 +42,12 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
}; };
const handleWalletChange = (index: number, address: string) => { 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) => const newRoyalty = royalty.map((member, i) =>
i === index ? { ...member, address } : member i === index ? { ...member, address } : member,
); );
setRoyalty(newRoyalty); setRoyalty(newRoyalty);
}; };
const handlePercentChange = (index: number, value: string) => { const handlePercentChange = (index: number, value: string) => {
const percentNumber = parseInt(value, 10) || 0; const percentNumber = parseInt(value, 10) || 0;
const newRoyalty = royalty.map((royalty, i) => const newRoyalty = royalty.map((royalty, i) =>
@ -92,28 +67,20 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
const isValid = useMemo(() => { const isValid = useMemo(() => {
return ( return (
royalty.every((member) => isValidTonAddress(member.address) && member.value >= 0) && royalty.every((member) => member.address && member.value >= 0) &&
royalty.reduce((acc, curr) => acc + curr.value, 0) === 100 royalty.reduce((acc, curr) => acc + curr.value, 0) === 100
); );
}, [royalty]); }, [royalty]);
const [tonConnectUI] = useTonConnectUI(); const [tonConnectUI] = useTonConnectUI();
// Устанавливаем адрес из tonConnectUI.account при загрузке страницы // Устанавливаем адрес из tonConnectUI.account при загрузке страницы
useEffect(() => { useEffect(() => {
if (!tonConnectUI.account) return; console.log('tonconnectUI', tonConnectUI)
if (tonConnectUI.account) {
if (royalty.length === 0) { setRoyalty([{ address: tonConnectUI.account.address, value: 100 }]);
// First initialization with 100%
setRoyalty([{
address: Address.parse(tonConnectUI.account.address).toString({
bounceable: true,
urlSafe: true,
testOnly: false,
}),
value: 100
}]);
} }
}, [tonConnectUI.account, setRoyalty, royalty]); }, [tonConnectUI.account, setRoyalty]);
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
@ -161,7 +128,7 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
<section className={"flex flex-col gap-1.5"}> <section className={"flex flex-col gap-1.5"}>
{royalty.map((member, index) => ( {royalty.map((member, index) => (
<div key={index} className={"flex flex-col gap-[20px]"}> <div key={index} className={"flex flex-col gap-[20px]"}>
<div className={"flex w-full items-start gap-1"}> <div className={"flex w-full items-center gap-1"}>
<div className={"w-[83%]"}> <div className={"w-[83%]"}>
<FormLabel <FormLabel
labelClassName={"flex"} labelClassName={"flex"}
@ -178,7 +145,6 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
value={member.address} value={member.address}
onChange={(e) => handleWalletChange(index, e.target.value)} onChange={(e) => handleWalletChange(index, e.target.value)}
placeholder={"[ Введите адрес криптокошелька TON ]"} placeholder={"[ Введите адрес криптокошелька TON ]"}
error={addressErrors[index]}
/> />
</FormLabel> </FormLabel>
</div> </div>

View File

@ -23,13 +23,8 @@ const handleNextClick = async () => {
await auth.mutateAsync(); await auth.mutateAsync();
nextStep(); nextStep();
} else { } else {
try {
await tonConnectUI.openModal(); await tonConnectUI.openModal();
await auth.mutateAsync(); await auth.mutateAsync();
nextStep();
} catch (error) {
console.error('Failed to connect or authenticate:', error);
}
} }
}; };

View File

@ -74,7 +74,7 @@ export const ViewContentPage = () => {
return ( return (
<main className={"min-h-screen flex w-full flex-col gap-[50px] px-4 "}> <main className={"flex w-full flex-col gap-[50px] px-4"}>
{content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && ( {content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
<div className={"mt-[30px] h-[314px] w-full"}> <div className={"mt-[30px] h-[314px] w-full"}>
<img <img
@ -111,10 +111,9 @@ export const ViewContentPage = () => {
</p> </p>
</section> </section>
<div className="mt-auto pb-2">
{!haveLicense && <Button {!haveLicense && <Button
onClick={handleBuyContent} onClick={handleBuyContent}
className={"mb-4 h-[48px]"} className={"mb-4 mt-[30px] h-[48px]"}
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`} label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
includeArrows={true} includeArrows={true}
/> />
@ -124,10 +123,9 @@ export const ViewContentPage = () => {
onClick={() => { onClick={() => {
WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`); WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
}} }}
className={"h-[48px] bg-darkred"} className={"mb-4 mt-[-20px] h-[48px] bg-darkred"}
label={`Загрузить свой контент`} label={`Загрузить свой контент`}
/> />
</div>
</main> </main>
); );
}; };

View File

@ -1,8 +1,4 @@
import { useTonConnectUI } from "@tonconnect/ui-react"; import { ReactNode, useMemo, useState } from "react";
import { ReactNode, useEffect, useMemo, useState } from "react";
const CHECK_INTERVAL = 20000;
export const useSteps = ( export const useSteps = (
sections: ({ sections: ({
@ -13,24 +9,15 @@ export const useSteps = (
prevStep(): void; prevStep(): void;
}) => ReactNode[], }) => ReactNode[],
) => { ) => {
const [tonConnectUI] = useTonConnectUI();
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
// If connection is lost, reset the step const nextStep = () => {
useEffect(() => { return setStep((s) => s + 1);
const interval = setInterval(() => { };
if (!tonConnectUI.connected && step !== 0) {
setStep(0);
}
}, CHECK_INTERVAL);
return () => clearInterval(interval); const prevStep = () => {
}, []); return setStep((s) => s - 1);
};
const nextStep = () => setStep((s) => s + 1);
const prevStep = () => setStep((s) => s - 1);
const ActiveSection = useMemo(() => { const ActiveSection = useMemo(() => {
return sections({ nextStep, prevStep })[step]; return sections({ nextStep, prevStep })[step];

View File

@ -1,2 +0,0 @@
import { Buffer } from 'buffer';
globalThis.Buffer = Buffer;

View File

@ -13,103 +13,80 @@ export const useAuth = () => {
const [tonConnectUI] = useTonConnectUI(); const [tonConnectUI] = useTonConnectUI();
const interval = useRef<ReturnType<typeof setInterval> | undefined>(); const interval = useRef<ReturnType<typeof setInterval> | undefined>();
const waitForWalletProof = async () => { return useMutation(["auth"], async () => {
return new Promise((resolve, reject) => { clearInterval(interval.current);
const timeout = setTimeout(() => reject(new Error("Timeout waiting for proof")), 30000);
const checkProof = setInterval(() => { if (!wallet) {
const currentWallet = tonConnectUI.wallet; localStorage.removeItem(sessionStorageKey);
if (
currentWallet?.connectItems?.tonProof && const refreshPayload = async () => {
!("error" in currentWallet.connectItems.tonProof) tonConnectUI.setConnectRequestParameters({ state: "loading" });
) {
clearInterval(checkProof); const value = await request
clearTimeout(timeout); .post<{
resolve(currentWallet.connectItems.tonProof.proof); auth_v1_token: string;
} }>("/auth.twa", {
}, 500); twa_data: WebApp.initData,
})
.catch((error: any) => {
console.error("Error in authentication request: ", error);
throw new Error("Failed to authenticate.");
}); });
if (!value) {
tonConnectUI.setConnectRequestParameters(null);
} else {
tonConnectUI.setConnectRequestParameters({
state: "ready",
value: {
tonProof: value?.data?.auth_v1_token,
},
});
}
}; };
const makeAuthRequest = async (params: { void refreshPayload();
twa_data: string; setInterval(refreshPayload, payloadTTLMS);
ton_proof?: {
account: any; return;
ton_proof: any; }
};
}) => { if (
const res = await request.post<{ wallet.connectItems?.tonProof &&
!("error" in wallet.connectItems.tonProof)
) {
const tonProof = wallet.connectItems.tonProof.proof;
console.log("DEBUG TON-PROOF", tonProof);
request
.post<{
connected_wallet: null | { connected_wallet: null | {
version: string; version: string;
address: string; address: string;
ton_balance: string; ton_balance: string;
}; };
auth_v1_token: string; auth_v1_token: string;
}>("/auth.twa", params); }>("/auth.twa", {
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res.data.auth_v1_token);
} else {
throw new Error("Failed to get auth token");
}
return res;
};
return useMutation(["auth"], async () => {
clearInterval(interval.current);
console.log("DEBUG: Starting auth flow");
// Case 1: Not connected - need to connect and get proof
if (!tonConnectUI.connected) {
console.log("DEBUG: No wallet connection, starting flow");
localStorage.removeItem(sessionStorageKey);
const refreshPayload = async () => {
tonConnectUI.setConnectRequestParameters({ state: "loading" });
const value = await request.post<{ auth_v1_token: string }>("/auth.twa", {
twa_data: WebApp.initData,
});
if (value?.data?.auth_v1_token) {
tonConnectUI.setConnectRequestParameters({
state: "ready",
value: { tonProof: value.data.auth_v1_token },
});
} else {
tonConnectUI.setConnectRequestParameters(null);
}
};
await refreshPayload();
interval.current = setInterval(refreshPayload, payloadTTLMS);
const tonProof = await waitForWalletProof();
console.log("DEBUG: Got initial proof", tonProof);
return makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet!.account,
ton_proof: tonProof,
},
});
}
// Case 2: Connected with proof - use it
if (wallet?.connectItems?.tonProof && !("error" in wallet.connectItems.tonProof)) {
console.log("DEBUG: Using existing proof");
return makeAuthRequest({
twa_data: WebApp.initData, twa_data: WebApp.initData,
ton_proof: { ton_proof: {
account: wallet.account, account: wallet.account,
ton_proof: wallet.connectItems.tonProof.proof, ton_proof: tonProof,
}, },
}); })
.then((res) => {
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res?.data?.auth_v1_token);
} else {
alert("Please try another wallet");
} }
})
// Case 3: Connected without proof - already authenticated .catch((error: any) => {
console.log("DEBUG: Connected without proof, proceeding without it"); console.error("Error in authentication request: ", error);
return makeAuthRequest({ throw new Error("Failed to authenticate.");
twa_data: WebApp.initData,
}); });
} else {
void tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey)
}
}); });
}; };

View File

@ -8,7 +8,6 @@ type UseCreateNewContentPayload = {
content: string; content: string;
image: string; image: string;
description: string; description: string;
hashtags: string[];
price: string; price: string;
resaleLicensePrice: string; // nanoTON bignum (default = 0) resaleLicensePrice: string; // nanoTON bignum (default = 0)
allowResale: boolean; allowResale: boolean;

View File

@ -21,12 +21,12 @@ export const useUploadFile = () => {
}>("/storage", formData, { }>("/storage", formData, {
headers: { headers: {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
Authorization: localStorage.getItem('auth_v1_token') || "" Authorization: localStorage.getItem('auth_v1_token') ?? ""
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round( const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent?.total as number) || (progressEvent.loaded * 100) / (progressEvent?.total as number) ??
0, 0,
); );
setUploadProgress(percentCompleted); setUploadProgress(percentCompleted);

View File

@ -13,7 +13,7 @@ type RootStore = {
setAuthor: (author: string) => void; setAuthor: (author: string) => void;
file: File | null; file: File | null;
setFile: (file: File | null) => void; setFile: (file: File) => void;
fileType: string; fileType: string;
setFileType: (type: string) => void; setFileType: (type: string) => void;
@ -77,10 +77,10 @@ export const useRootStore = create<RootStore>((set) => ({
authors: [], authors: [],
setAuthors: (authors) => set({ authors }), setAuthors: (authors) => set({ authors }),
royalty: [], royalty: [{ address: "", value: 100 }],
setRoyalty: (royalty) => set({ royalty }), setRoyalty: (royalty) => set({ royalty }),
price: 0.15, price: 0,
setPrice: (price: number) => set({ price }), setPrice: (price: number) => set({ price }),
allowResale: false, allowResale: false,

View File

@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { WithContext as ReactTags, SEPARATORS } from "react-tag-input"; import { WithContext as ReactTags } from "react-tag-input";
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
// English comment: no extra Tag interface, just cast to any // English comment: no extra Tag interface, just cast to any
export const HashtagInput = () => { export const HashtagInput = () => {
const { hashtags, setHashtags } = useRootStore(); const { hashtags, setHashtags } = useRootStore();
@ -8,7 +9,9 @@ export const HashtagInput = () => {
// English comment: local state as string[] for simplicity // English comment: local state as string[] for simplicity
const [tags, setTags] = useState<string[]>(hashtags); const [tags, setTags] = useState<string[]>(hashtags);
const separators = [SEPARATORS.ENTER, SEPARATORS.COMMA] const KeyCodes = { comma: 188, enter: 13 };
const delimiters = [KeyCodes.comma, KeyCodes.enter];
const handleDelete = (i: number) => { const handleDelete = (i: number) => {
const newTags = tags.filter((_, index) => index !== i); const newTags = tags.filter((_, index) => index !== i);
setTags(newTags); setTags(newTags);
@ -17,47 +20,20 @@ export const HashtagInput = () => {
// English comment: pass "any" to the function // English comment: pass "any" to the function
const handleAddition = (newTag: any) => { const handleAddition = (newTag: any) => {
// Clean up text from commas and trim whitespace // English comment: newTag might be { id, text, ... } from react-tag-input
const text = newTag?.text || newTag?.id || ""; const updatedTags = [...tags, newTag?.text || newTag?.id || ""];
const cleanText = text.replace(/,/g, '').trim();
// Skip empty tags
if (!cleanText) return;
const updatedTags = [...tags, cleanText];
setTags(updatedTags); setTags(updatedTags);
setHashtags(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 ( return (
<ReactTags <ReactTags
tags={tags.map((t) => ({ id: t, text: t })) as any} tags={tags.map((t) => ({ id: t, text: t })) as any}
separators={separators as any} delimiters={delimiters as any}
handleDelete={handleDelete as any} handleDelete={handleDelete as any}
handleAddition={handleAddition as any} handleAddition={handleAddition as any}
allowDragDrop={false} allowDragDrop={false}
placeholder="[ введите тэги через запятую ]" placeholder="[ enter a hashtag ]"
inputProps={{
onKeyUp: handleKeyUp as any
}}
/> />
); );
}; };

View File

@ -1,9 +0,0 @@
export const Replace = () => {
return (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="1">
<path d="M15.75 6.01C15.9293 6.01 16.0939 6.07292 16.2229 6.17788C17.3108 7.09184 18.0024 8.46516 18.0024 10C18.0024 12.6888 15.8801 14.8818 13.2193 14.9954L13.0024 15H8.56057L9.78032 16.2197C10.0466 16.4859 10.0708 16.9026 9.85294 17.1962L9.78032 17.2803C9.51406 17.5466 9.09739 17.5708 8.80378 17.3529L8.71966 17.2803L6.21966 14.7803C5.9534 14.5141 5.92919 14.0974 6.14704 13.8038L6.21966 13.7197L8.71966 11.2197C9.01256 10.9268 9.48743 10.9268 9.78032 11.2197C10.0466 11.4859 10.0708 11.9026 9.85294 12.1962L9.78032 12.2803L8.56057 13.5H13.0024C14.871 13.5 16.3975 12.0357 16.4972 10.192L16.5024 10C16.5024 8.91885 16.0122 7.95219 15.2419 7.31017C15.0935 7.17538 15 6.97861 15 6.76C15 6.34579 15.3358 6.01 15.75 6.01ZM10.2197 2.71967C10.4859 2.4534 10.9026 2.4292 11.1962 2.64705L11.2803 2.71967L13.7803 5.21967L13.8529 5.30379C14.0466 5.56478 14.049 5.92299 13.8601 6.18638L13.7803 6.28033L11.2803 8.78033L11.1962 8.85295C10.9352 9.0466 10.577 9.04899 10.3136 8.86012L10.2197 8.78033L10.147 8.69621C9.9534 8.43522 9.951 8.07701 10.1399 7.81362L10.2197 7.71967L11.4386 6.5H6.99757C5.12901 6.5 3.60245 7.96428 3.50275 9.80796L3.49757 10C3.49757 11.0831 3.98958 12.0514 4.7623 12.6934C4.9085 12.8289 4.99999 13.0238 4.99999 13.24C4.99999 13.6542 4.66421 13.99 4.24999 13.99C4.05871 13.99 3.88415 13.9184 3.75166 13.8005C2.67865 12.8872 1.99757 11.5232 1.99757 10C1.99757 7.31124 4.11988 5.11818 6.78068 5.00462L6.99757 5H11.4386L10.2197 3.78033L10.147 3.69621C9.92919 3.4026 9.9534 2.98594 10.2197 2.71967Z" fill="white" />
</g>
</svg>
);
};

View File

@ -4,19 +4,4 @@ import TSPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [react(), TSPaths()], plugins: [react(), TSPaths()],
define: {
global: 'globalThis',
},
resolve: {
alias: {
buffer: 'buffer/',
}
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
}
}); });

View File

@ -225,10 +225,10 @@
"@babel/helper-validator-identifier" "^7.22.20" "@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@esbuild/win32-x64@0.19.12": "@esbuild/darwin-arm64@0.19.12":
version "0.19.12" version "0.19.12"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz" resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz"
integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0" version "4.4.0"
@ -381,10 +381,10 @@
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz"
integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q== integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==
"@rollup/rollup-win32-x64-msvc@4.12.0": "@rollup/rollup-darwin-arm64@4.12.0":
version "4.12.0" version "4.12.0"
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz" resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz"
integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg== integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==
"@ton/core@^0.59.1": "@ton/core@^0.59.1":
version "0.59.1" version "0.59.1"
@ -860,11 +860,6 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 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: big-integer@^1.6.16:
version "1.6.52" version "1.6.52"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
@ -921,14 +916,6 @@ browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0":
node-releases "^2.0.14" node-releases "^2.0.14"
update-browserslist-db "^1.0.13" 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: call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz"
@ -1582,6 +1569,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 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: function-bind@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@ -1772,11 +1764,6 @@ hoist-non-react-statics@^3.3.2:
dependencies: dependencies:
react-is "^16.7.0" 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: ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1" version "5.3.1"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"