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
.DS_Store
dist
.env

65
package-lock.json generated
View File

@ -13,7 +13,6 @@
"@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",
@ -2055,26 +2054,6 @@
"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",
@ -2160,30 +2139,6 @@
"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",
@ -3666,26 +3621,6 @@
"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",

View File

@ -15,7 +15,6 @@
"@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",

View File

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

View File

@ -26,26 +26,18 @@
/*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 */
-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 */
border-radius: 9999px; /* Rounded-full track */
outline: none; /* Remove outline */
transition: background 0.3s;
}
@ -53,64 +45,45 @@
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 */
-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 */
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-[#2B2B2B] outline-none w-full h-8 text-sm !important;
@apply border border-white px-[10px] py-[18px] !important;
@apply whitespace-pre !important;
@apply bg-transparent text-gray placeholder:text-gray outline-none w-full h-8;
@apply border border-white py-[20px] !important;
/* English comment:
'bg-transparent' to blend with your dark background
'text-white' to have white text
@ -121,7 +94,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 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:
'bg-[#363636]' to have a dark gray background
'text-white' keeps the text white
@ -132,7 +105,7 @@
}
.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:
'ml-1' a small margin to separate the 'x' or close symbol
'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 { Replace } from "~/shared/ui/icons/replace";
import { XMark } from "~/shared/ui/icons/x-mark.tsx";
@ -37,8 +36,8 @@ export const CoverButton = ({ src, onClick }: CoverButtonProps) => {
}
>
<div />
<div className={"flex gap-2 text-sm"}>Изменить</div>
<Replace />
<div className={"flex gap-2 text-sm"}>Удалить</div>
<XMark />
</button>
</div>
);

View File

@ -14,8 +14,6 @@ import { useRootStore } from "~/shared/stores/root";
import { Checkbox } from "~/shared/ui/checkbox";
import { AudioPlayer } from "~/shared/ui/audio-player";
import { HashtagInput } from "~/shared/ui/hashtag-input";
import { XMark } from "~/shared/ui/icons/x-mark";
import { Replace } from "~/shared/ui/icons/replace";
type DataStepProps = {
nextStep(): void;
@ -59,12 +57,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
})();
};
const handleFileReset = () => {
rootStore.setFile(null);
rootStore.setFileSrc('');
rootStore.setFileType('');
}
return (
<section className={"mt-4 px-4 pb-8"}>
<div className={"mb-[30px] flex flex-col text-sm"}>
@ -96,7 +88,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
</FormLabel>
<FormLabel label={"Файл"}>
{!rootStore.fileSrc && <>
<HiddenFileInput
id={"file"}
shouldProcess={false}
@ -108,8 +99,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
}}
/>
<FileButton htmlFor={"file"} />
</>}
{!rootStore.fileSrc && <FileButton htmlFor={"file"} />}
{rootStore.fileSrc && (
<div
@ -128,16 +118,6 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
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>
)}
</FormLabel>
@ -156,7 +136,13 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
{rootStore.allowCover && (
<FormLabel label={"Обложка"}>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
{rootStore.cover ? (
<CoverButton
@ -166,16 +152,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
}}
/>
) : (
<>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
<FileButton htmlFor={"cover"} />
</>
)}
</FormLabel>
)}

View File

@ -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,24 +151,6 @@ 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={

View File

@ -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.15;
const MIN_RESALE_PRICE = 0.15;
const MIN_PRICE = 0.07;
const MIN_RESALE_PRICE = 0.07;
// 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" || typeof value === "number") {
const stringValue = value.toString().replace(",", ".");
const parsedValue = parseFloat(stringValue);
if (typeof value === "string") {
// Replace commas with dots and parse the value
const parsedValue = parseFloat(value.replace(",", "."));
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({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
),
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(),
});
}
@ -48,7 +48,7 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
return z.object({
price: z.preprocess(
parsePrice,
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
z.number().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 || MIN_PRICE,
price: rootStore.price,
//@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"}>
<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={"[ Введите цену ]"}
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, '');
}
}
})}
{...form.register("price")}
/>
</FormLabel>
</div>

View File

@ -12,30 +12,17 @@ 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();
@ -55,24 +42,12 @@ 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;
const newRoyalty = royalty.map((royalty, i) =>
@ -92,28 +67,20 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
const isValid = useMemo(() => {
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]);
const [tonConnectUI] = useTonConnectUI();
// Устанавливаем адрес из tonConnectUI.account при загрузке страницы
useEffect(() => {
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
}]);
console.log('tonconnectUI', tonConnectUI)
if (tonConnectUI.account) {
setRoyalty([{ address: tonConnectUI.account.address, value: 100 }]);
}
}, [tonConnectUI.account, setRoyalty, royalty]);
}, [tonConnectUI.account, setRoyalty]);
return (
<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"}>
{royalty.map((member, index) => (
<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%]"}>
<FormLabel
labelClassName={"flex"}
@ -178,7 +145,6 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
value={member.address}
onChange={(e) => handleWalletChange(index, e.target.value)}
placeholder={"[ Введите адрес криптокошелька TON ]"}
error={addressErrors[index]}
/>
</FormLabel>
</div>

View File

@ -18,20 +18,15 @@ export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => {
console.log("💩💩💩 after useAuth");
const handleNextClick = async () => {
const handleNextClick = async () => {
if (tonConnectUI.connected) {
await auth.mutateAsync();
nextStep();
} else {
try {
await tonConnectUI.openModal();
await auth.mutateAsync();
nextStep();
} catch (error) {
console.error('Failed to connect or authenticate:', error);
}
}
};
};
// useEffect(() => {
// const first = setTimeout(async () => {

View File

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

View File

@ -1,8 +1,4 @@
import { useTonConnectUI } from "@tonconnect/ui-react";
import { ReactNode, useEffect, useMemo, useState } from "react";
const CHECK_INTERVAL = 20000;
import { ReactNode, useMemo, useState } from "react";
export const useSteps = (
sections: ({
@ -13,24 +9,15 @@ export const useSteps = (
prevStep(): void;
}) => ReactNode[],
) => {
const [tonConnectUI] = useTonConnectUI();
const [step, setStep] = useState(0);
// If connection is lost, reset the step
useEffect(() => {
const interval = setInterval(() => {
if (!tonConnectUI.connected && step !== 0) {
setStep(0);
}
}, CHECK_INTERVAL);
const nextStep = () => {
return setStep((s) => s + 1);
};
return () => clearInterval(interval);
}, []);
const nextStep = () => setStep((s) => s + 1);
const prevStep = () => setStep((s) => s - 1);
const prevStep = () => {
return setStep((s) => s - 1);
};
const ActiveSection = useMemo(() => {
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 interval = useRef<ReturnType<typeof setInterval> | undefined>();
const waitForWalletProof = async () => {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("Timeout waiting for proof")), 30000);
const checkProof = setInterval(() => {
const currentWallet = tonConnectUI.wallet;
if (
currentWallet?.connectItems?.tonProof &&
!("error" in currentWallet.connectItems.tonProof)
) {
clearInterval(checkProof);
clearTimeout(timeout);
resolve(currentWallet.connectItems.tonProof.proof);
}
}, 500);
return useMutation(["auth"], async () => {
clearInterval(interval.current);
if (!wallet) {
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,
})
.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: {
twa_data: string;
ton_proof?: {
account: any;
ton_proof: any;
};
}) => {
const res = await request.post<{
void refreshPayload();
setInterval(refreshPayload, payloadTTLMS);
return;
}
if (
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 | {
version: string;
address: string;
ton_balance: string;
};
auth_v1_token: string;
}>("/auth.twa", params);
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({
}>("/auth.twa", {
twa_data: WebApp.initData,
ton_proof: {
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
console.log("DEBUG: Connected without proof, proceeding without it");
return makeAuthRequest({
twa_data: WebApp.initData,
})
.catch((error: any) => {
console.error("Error in authentication request: ", error);
throw new Error("Failed to authenticate.");
});
} else {
void tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey)
}
});
};

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
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";
// English comment: no extra Tag interface, just cast to any
export const HashtagInput = () => {
const { hashtags, setHashtags } = useRootStore();
@ -8,7 +9,9 @@ export const HashtagInput = () => {
// English comment: local state as string[] for simplicity
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 newTags = tags.filter((_, index) => index !== i);
setTags(newTags);
@ -17,47 +20,20 @@ export const HashtagInput = () => {
// English comment: pass "any" to the function
const handleAddition = (newTag: any) => {
// 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];
// English comment: newTag might be { id, text, ... } from react-tag-input
const updatedTags = [...tags, newTag?.text || newTag?.id || ""];
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}
separators={separators as any}
delimiters={delimiters as any}
handleDelete={handleDelete as any}
handleAddition={handleAddition as any}
allowDragDrop={false}
placeholder="[ введите тэги через запятую ]"
inputProps={{
onKeyUp: handleKeyUp as any
}}
placeholder="[ enter a hashtag ]"
/>
);
};

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({
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"
to-fast-properties "^2.0.0"
"@esbuild/win32-x64@0.19.12":
"@esbuild/darwin-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz"
integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz"
integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
"@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-win32-x64-msvc@4.12.0":
"@rollup/rollup-darwin-arm64@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"
integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==
resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz"
integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==
"@ton/core@^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"
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"
@ -921,14 +916,6 @@ 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"
@ -1582,6 +1569,11 @@ 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"
@ -1772,11 +1764,6 @@ 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"