prettier config, dwnld button, dwnld content, chain login > purchase, selectWallet post in useAuth
This commit is contained in:
parent
5f4b13d7ba
commit
2351ee51eb
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
|
|
@ -3,18 +3,18 @@ import { Outlet } from 'react-router-dom';
|
|||
import { useAuthTwa } from '~/shared/services/authTwa';
|
||||
|
||||
export const ProtectedLayout = () => {
|
||||
const auth = useAuthTwa();
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
const auth = useAuthTwa();
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
void auth.mutateAsync().finally(() => {
|
||||
setIsInitialLoad(false);
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void auth.mutateAsync().finally(() => {
|
||||
setIsInitialLoad(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isInitialLoad || auth.isLoading) {
|
||||
return null;
|
||||
}
|
||||
if (isInitialLoad || auth.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
return <Outlet />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -165,6 +165,16 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
|
|||
</FormLabel>
|
||||
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<FormLabel
|
||||
label={"Разрешить скачивание"}
|
||||
labelClassName={"flex"}
|
||||
formLabelAddon={
|
||||
<Checkbox
|
||||
onClick={() => rootStore.setAllowDwnld(!rootStore.allowDwnld)}
|
||||
checked={rootStore.allowDwnld}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel
|
||||
label={"Разрешить обложку"}
|
||||
labelClassName={"flex"}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
|||
// Откомментировать при условии того что вы принимаете много авторов
|
||||
// следует отметить что вы должны еще откомментровать AuthorsStep в RootPage
|
||||
// authors: rootStore.authors,
|
||||
|
||||
downloadable: rootStore.allowDwnld,
|
||||
content: fileUploadResult.content_id_v1,
|
||||
image: coverUploadResult.content_id_v1,
|
||||
price: String(rootStore.price * 10 ** 9),
|
||||
|
|
|
|||
|
|
@ -1,217 +1,275 @@
|
|||
import ReactPlayer from "react-player/lazy";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
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, useState} from "react";
|
||||
import { AudioPlayer } from "~/shared/ui/audio-player";
|
||||
import {useAuth} from "~/shared/services/auth";
|
||||
import { CongratsModal } from "./components/congrats-modal";
|
||||
import { ErrorModal } from "./components/error-modal";
|
||||
import { Button } from '~/shared/ui/button';
|
||||
import { usePurchaseContent, useViewContent } from '~/shared/services/content';
|
||||
import { fromNanoTON } from '~/shared/utils';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { AudioPlayer } from '~/shared/ui/audio-player';
|
||||
import { useAuth } from '~/shared/services/auth';
|
||||
import { CongratsModal } from './components/congrats-modal';
|
||||
import { ErrorModal } from './components/error-modal';
|
||||
|
||||
type InvoiceStatus = 'paid' | 'failed' | 'cancelled' | 'pending';
|
||||
|
||||
// Add type for invoice event
|
||||
interface InvoiceEvent {
|
||||
url: string;
|
||||
status: InvoiceStatus;
|
||||
url: string;
|
||||
status: InvoiceStatus;
|
||||
}
|
||||
|
||||
|
||||
export const ViewContentPage = () => {
|
||||
const WebApp = useWebApp();
|
||||
const WebApp = useWebApp();
|
||||
|
||||
const { data: content, refetch: refetchContent } = useViewContent(WebApp.initDataUnsafe?.start_param);
|
||||
const { data: content, refetch: refetchContent } = useViewContent(
|
||||
WebApp.initDataUnsafe?.start_param
|
||||
);
|
||||
|
||||
const { mutateAsync: purchaseContent } = usePurchaseContent();
|
||||
const { mutateAsync: purchaseContent } = usePurchaseContent();
|
||||
|
||||
const [tonConnectUI] = useTonConnectUI();
|
||||
const [tonConnectUI] = useTonConnectUI();
|
||||
|
||||
const auth = useAuth();
|
||||
const auth = useAuth();
|
||||
const [isCongratsModal, setIsCongratsModal] = useState(false);
|
||||
const [isErrorModal, setIsErrorModal] = useState(false);
|
||||
|
||||
const [isCongratsModal, setIsCongratsModal] = useState(false);
|
||||
const [isErrorModal, setIsErrorModal] = useState(false);
|
||||
const handleBuyContentTON = useCallback(async () => {
|
||||
try {
|
||||
if (!tonConnectUI.connected) {
|
||||
await tonConnectUI.openModal();
|
||||
await auth.mutateAsync();
|
||||
return
|
||||
} else {
|
||||
await auth.mutateAsync()
|
||||
}
|
||||
const handleBuyContentTON = useCallback(async () => {
|
||||
try {
|
||||
// Helper function to wait for wallet connection
|
||||
const waitForConnection = async (timeoutMs = 30000, intervalMs = 500) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
const contentResponse = await purchaseContent({
|
||||
content_address: WebApp.initDataUnsafe?.start_param,
|
||||
license_type: "resale",
|
||||
});
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (tonConnectUI.connected) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
const transactionResponse = await tonConnectUI.sendTransaction({
|
||||
validUntil: Math.floor(Date.now() / 1000) + 120,
|
||||
messages: [
|
||||
{
|
||||
amount: contentResponse.data.amount,
|
||||
address: contentResponse.data.address,
|
||||
payload: contentResponse.data.payload,
|
||||
},
|
||||
],
|
||||
});
|
||||
return false; // Timed out
|
||||
};
|
||||
|
||||
if (transactionResponse.boc) {
|
||||
void refetchContent()
|
||||
setIsCongratsModal(true);
|
||||
console.log(transactionResponse.boc, "PURCHASED")
|
||||
} else {
|
||||
setIsErrorModal(true);
|
||||
console.error("Transaction failed:", transactionResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsErrorModal(true);
|
||||
console.error("Error handling Ton Connect subscription:", error);
|
||||
}
|
||||
}, [content, tonConnectUI.connected]);
|
||||
// If not connected, start connection process
|
||||
if (!tonConnectUI.connected) {
|
||||
console.log('DEBUG: Wallet not connected, opening modal');
|
||||
|
||||
const handleBuyContentStars = useCallback(async () => {
|
||||
try {
|
||||
if (!content?.data?.invoice.url) {
|
||||
console.error('No invoice URL available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add event listener for invoice closing with typed event
|
||||
const handleInvoiceClosed = (event: InvoiceEvent) => {
|
||||
if (event.url === content.data.invoice.url) {
|
||||
if (event.status === 'paid') {
|
||||
void refetchContent();
|
||||
setIsCongratsModal(true);
|
||||
} else if (event.status === 'failed' || event.status === 'cancelled') {
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
// Open connection modal
|
||||
await tonConnectUI.openModal();
|
||||
|
||||
// Wait for connection
|
||||
const connected = await waitForConnection();
|
||||
if (!connected) {
|
||||
console.log('DEBUG: Connection timed out or was cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('DEBUG: Connection successful, authenticating');
|
||||
await auth.mutateAsync();
|
||||
} else {
|
||||
// Already connected, just authenticate
|
||||
await auth.mutateAsync();
|
||||
}
|
||||
|
||||
// Proceed with purchase
|
||||
console.log('DEBUG: Proceeding with purchase');
|
||||
const contentResponse = await purchaseContent({
|
||||
content_address: WebApp.initDataUnsafe?.start_param,
|
||||
license_type: 'resale',
|
||||
});
|
||||
|
||||
const transactionResponse = await tonConnectUI.sendTransaction({
|
||||
validUntil: Math.floor(Date.now() / 1000) + 86400, // 24 hours
|
||||
messages: [
|
||||
{
|
||||
amount: contentResponse.data.amount,
|
||||
address: contentResponse.data.address,
|
||||
payload: contentResponse.data.payload,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (transactionResponse.boc) {
|
||||
void refetchContent();
|
||||
setIsCongratsModal(true);
|
||||
console.log(transactionResponse.boc, 'PURCHASED');
|
||||
} else {
|
||||
setIsErrorModal(true);
|
||||
console.error('Transaction failed:', transactionResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsErrorModal(true);
|
||||
console.error('Error handling Ton Connect subscription:', error);
|
||||
}
|
||||
};
|
||||
|
||||
WebApp.onEvent('invoiceClosed', handleInvoiceClosed);
|
||||
|
||||
await WebApp.openInvoice(
|
||||
content.data.invoice.url,
|
||||
(status: InvoiceStatus) => {
|
||||
console.log('Invoice status:', status);
|
||||
if (status === 'paid') {
|
||||
}, [auth, purchaseContent, refetchContent, tonConnectUI, WebApp.initDataUnsafe?.start_param]);
|
||||
|
||||
const handleBuyContentStars = useCallback(async () => {
|
||||
try {
|
||||
if (!content?.data?.invoice.url) {
|
||||
console.error('No invoice URL available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add event listener for invoice closing with typed event
|
||||
const handleInvoiceClosed = (event: InvoiceEvent) => {
|
||||
if (event.url === content.data.invoice.url) {
|
||||
if (event.status === 'paid') {
|
||||
void refetchContent();
|
||||
setIsCongratsModal(true);
|
||||
} else if (event.status === 'failed' || event.status === 'cancelled') {
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebApp.onEvent('invoiceClosed', handleInvoiceClosed);
|
||||
|
||||
await WebApp.openInvoice(content.data.invoice.url, (status: InvoiceStatus) => {
|
||||
console.log('Invoice status:', status);
|
||||
if (status === 'paid') {
|
||||
void refetchContent();
|
||||
setIsCongratsModal(true);
|
||||
} else if (status === 'failed' || status === 'cancelled') {
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
WebApp.offEvent('invoiceClosed', handleInvoiceClosed);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Payment failed:', error);
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
}
|
||||
}, [content, refetchContent]);
|
||||
|
||||
const haveLicense = useMemo(() => {
|
||||
document.title = content?.data?.display_options?.metadata?.name;
|
||||
return (
|
||||
content?.data?.have_licenses?.includes('listen') ||
|
||||
content?.data?.have_licenses?.includes('resale')
|
||||
);
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
void refetchContent();
|
||||
setIsCongratsModal(true);
|
||||
} else if (status === 'failed' || status === 'cancelled') {
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const handleConfirmCongrats = () => {
|
||||
setIsCongratsModal(!isCongratsModal);
|
||||
};
|
||||
|
||||
const handleErrorModal = () => {
|
||||
setIsErrorModal(!isErrorModal);
|
||||
};
|
||||
|
||||
const handleDwnldContent = async () => {
|
||||
try {
|
||||
const fileUrl = content?.data?.display_options?.content_url;
|
||||
const fileName = content?.data?.display_options?.metadata?.name || 'content';
|
||||
const fileFormat = content?.data?.content_type?.contentFormat || '';
|
||||
await WebApp.downloadFile({
|
||||
url: fileUrl,
|
||||
file_name: fileName + fileFormat,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error downloading content:', error);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
WebApp.offEvent('invoiceClosed', handleInvoiceClosed);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Payment failed:', error);
|
||||
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
|
||||
}
|
||||
}, [content, refetchContent]);
|
||||
};
|
||||
|
||||
const haveLicense = useMemo(() => {
|
||||
document.title = content?.data?.display_options?.metadata?.name;
|
||||
return content?.data?.have_licenses?.includes("listen") || content?.data?.have_licenses?.includes("resale");
|
||||
}, [content])
|
||||
return (
|
||||
<main className={'min-h-screen flex w-full flex-col gap-[50px] px-4 '}>
|
||||
{isCongratsModal && <CongratsModal onConfirm={handleConfirmCongrats} />}
|
||||
{isErrorModal && <ErrorModal onConfirm={handleErrorModal} />}
|
||||
{content?.data?.content_type.startsWith('audio') &&
|
||||
content?.data?.display_options?.metadata?.image && (
|
||||
<div className={'mt-[30px] h-[314px] w-full'}>
|
||||
<img
|
||||
alt={'content_image'}
|
||||
className={'h-full w-full object-cover object-center'}
|
||||
src={content?.data?.display_options?.metadata?.image}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
void refetchContent()
|
||||
}, 5000)
|
||||
{content?.data?.content_type.startsWith('audio') ? (
|
||||
<AudioPlayer src={content?.data?.display_options?.content_url} />
|
||||
) : (
|
||||
<ReactPlayer
|
||||
playsinline={true}
|
||||
controls={true}
|
||||
width="100%"
|
||||
config={{
|
||||
file: {
|
||||
attributes: {
|
||||
playsInline: true,
|
||||
autoPlay: true,
|
||||
poster:
|
||||
content?.data?.display_options?.metadata?.image || undefined,
|
||||
},
|
||||
},
|
||||
}}
|
||||
url={content?.data?.display_options?.content_url}
|
||||
/>
|
||||
)}
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, []);
|
||||
<section className={'flex flex-col'}>
|
||||
<h1 className={'text-[20px] font-bold'}>
|
||||
{content?.data?.display_options?.metadata?.name}
|
||||
</h1>
|
||||
{/*<h2>Russian</h2>*/}
|
||||
{/*<h2>2022</h2>*/}
|
||||
<p className={'mt-2 text-[12px]'}>
|
||||
{content?.data?.display_options?.metadata?.description}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
const handleConfirmCongrats = () => {
|
||||
setIsCongratsModal(!isCongratsModal);
|
||||
};
|
||||
|
||||
const handleErrorModal = () => {
|
||||
setIsErrorModal(!isErrorModal);
|
||||
}
|
||||
return (
|
||||
<main className={"min-h-screen flex w-full flex-col gap-[50px] px-4 "}>
|
||||
{isCongratsModal && <CongratsModal
|
||||
onConfirm={handleConfirmCongrats}/>}
|
||||
{isErrorModal && <ErrorModal
|
||||
onConfirm={handleErrorModal}/>}
|
||||
{content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
|
||||
<div className={"mt-[30px] h-[314px] w-full"}>
|
||||
<img
|
||||
alt={"content_image"}
|
||||
className={"h-full w-full object-cover object-center"}
|
||||
src={content?.data?.display_options?.metadata?.image}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{content?.data?.content_type.startsWith("audio") ? (
|
||||
<AudioPlayer src={content?.data?.display_options?.content_url} />
|
||||
) : (
|
||||
<ReactPlayer
|
||||
playsinline={true}
|
||||
controls={true}
|
||||
width="100%"
|
||||
config={{ file: { attributes: {
|
||||
playsInline: true, autoplay: true,
|
||||
poster: content?.data?.display_options?.metadata?.image || undefined,
|
||||
} }, }}
|
||||
url={content?.data?.display_options?.content_url}
|
||||
/>
|
||||
)}
|
||||
|
||||
<section className={"flex flex-col"}>
|
||||
<h1 className={"text-[20px] font-bold"}>
|
||||
{content?.data?.display_options?.metadata?.name}
|
||||
</h1>
|
||||
{/*<h2>Russian</h2>*/}
|
||||
{/*<h2>2022</h2>*/}
|
||||
<p className={"mt-2 text-[12px]"}>
|
||||
{content?.data?.display_options?.metadata?.description}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="mt-auto pb-2">
|
||||
{!haveLicense && <div className="flex flex-row gap-4">
|
||||
<Button
|
||||
onClick={handleBuyContentTON}
|
||||
className={"mb-4 h-[48px] px-2"}
|
||||
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
|
||||
includeArrows={content?.data?.invoice ? false : true}
|
||||
/>
|
||||
{content?.data?.invoice && (
|
||||
<Button
|
||||
onClick={handleBuyContentStars}
|
||||
className={"mb-4 h-[48px] px-2"}
|
||||
label={`Купить за ${content?.data?.invoice?.amount} ⭐️`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
<Button
|
||||
onClick={() => {
|
||||
WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
|
||||
}}
|
||||
className={"h-[48px] bg-darkred"}
|
||||
label={`Загрузить свой контент`}
|
||||
/>
|
||||
{tonConnectUI.connected && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
tonConnectUI.disconnect();
|
||||
}}
|
||||
className={"h-[48px] bg-darkred mt-4"}
|
||||
label={`Отключить кошелек`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
<div className="mt-auto pb-2">
|
||||
{content?.data?.downloadable && (
|
||||
<Button
|
||||
onClick={() => handleDwnldContent()}
|
||||
className={'h-[48px] bg-darkred mb-4'}
|
||||
label={`Скачать контент`}
|
||||
/>
|
||||
)}
|
||||
{!haveLicense && (
|
||||
<div className="flex flex-row gap-4">
|
||||
<Button
|
||||
onClick={handleBuyContentTON}
|
||||
className={'mb-4 h-[48px] px-2'}
|
||||
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
|
||||
includeArrows={content?.data?.invoice ? false : true}
|
||||
/>
|
||||
{content?.data?.invoice && (
|
||||
<Button
|
||||
onClick={handleBuyContentStars}
|
||||
className={'mb-4 h-[48px] px-2'}
|
||||
label={`Купить за ${content?.data?.invoice?.amount} ⭐️`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
|
||||
}}
|
||||
className={'h-[48px] bg-darkred'}
|
||||
label={`Загрузить свой контент`}
|
||||
/>
|
||||
{tonConnectUI.connected && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
tonConnectUI.disconnect();
|
||||
}}
|
||||
className={'h-[48px] bg-darkred mt-4'}
|
||||
label={`Отключить кошелек`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import axios from "axios";
|
||||
import axios from 'axios';
|
||||
|
||||
export const APP_API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
export const request = axios.create({
|
||||
baseURL: APP_API_BASE_URL,
|
||||
baseURL: APP_API_BASE_URL,
|
||||
});
|
||||
|
||||
request.interceptors.request.use((config) => {
|
||||
const auth_v1_token = sessionStorage.getItem("auth_v1_token");
|
||||
const auth_v1_token = localStorage.getItem('auth_v1_token');
|
||||
|
||||
if (auth_v1_token) {
|
||||
config.headers.Authorization = auth_v1_token;
|
||||
}
|
||||
if (auth_v1_token) {
|
||||
config.headers.Authorization = auth_v1_token;
|
||||
}
|
||||
|
||||
return config;
|
||||
return config;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,118 +1,131 @@
|
|||
import { useRef } from "react";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { useMutation } from "react-query";
|
||||
import { request } from "~/shared/libs";
|
||||
import { useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useRef } from 'react';
|
||||
import { useTonConnectUI } from '@tonconnect/ui-react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { request } from '~/shared/libs';
|
||||
import { useWebApp } from '@vkruglikov/react-telegram-web-app';
|
||||
|
||||
const sessionStorageKey = "auth_v1_token";
|
||||
const sessionStorageKey = 'auth_v1_token';
|
||||
const payloadTTLMS = 1000 * 60 * 20;
|
||||
|
||||
export const useAuth = () => {
|
||||
const WebApp = useWebApp();
|
||||
// const wallet = useTonWallet();
|
||||
const [tonConnectUI] = useTonConnectUI();
|
||||
const interval = useRef<ReturnType<typeof setInterval> | undefined>();
|
||||
const WebApp = useWebApp();
|
||||
// const wallet = useTonWallet();
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
const makeAuthRequest = async (params: {
|
||||
twa_data: string;
|
||||
ton_proof?: {
|
||||
account: any;
|
||||
ton_proof: any;
|
||||
};
|
||||
}) => {
|
||||
const res = await 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,
|
||||
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);
|
||||
});
|
||||
|
||||
if (value?.data?.auth_v1_token) {
|
||||
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<{
|
||||
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 {
|
||||
tonConnectUI.setConnectRequestParameters(null);
|
||||
throw new Error('Failed to get auth token');
|
||||
}
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
await refreshPayload();
|
||||
interval.current = setInterval(refreshPayload, payloadTTLMS);
|
||||
const makeSelectWalletRequest = async (params: { wallet_address: string }) => {
|
||||
const res = await request.post('/auth.selectWallet', params);
|
||||
return res;
|
||||
};
|
||||
|
||||
const tonProof = await waitForWalletProof();
|
||||
console.log("DEBUG: Got initial proof", tonProof);
|
||||
return useMutation(['auth'], async () => {
|
||||
clearInterval(interval.current);
|
||||
let authResult;
|
||||
console.log('DEBUG: Starting auth flow');
|
||||
|
||||
return makeAuthRequest({
|
||||
twa_data: WebApp.initData,
|
||||
ton_proof: {
|
||||
account: tonConnectUI.wallet!.account,
|
||||
ton_proof: tonProof,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Case 1: Not connected - need to connect and get proof
|
||||
if (!tonConnectUI.connected) {
|
||||
console.log('DEBUG: No wallet connection, starting flow');
|
||||
localStorage.removeItem(sessionStorageKey);
|
||||
|
||||
// Commented this part for two reasons:
|
||||
// 1) When we include ton_proof from the wallet it fails the call for a reason of bad ton_proof
|
||||
// 2) This call could happen only if the first case happened and it means that the ton_proof is already have been stored once before
|
||||
// Case 2: Connected with proof - use it
|
||||
// if (wallet?.connectItems?.tonProof && !("error" in wallet.connectItems.tonProof)) {
|
||||
// console.log("DEBUG: Using existing proof", wallet.connectItems.tonProof.proof);
|
||||
// return makeAuthRequest({
|
||||
// twa_data: WebApp.initData,
|
||||
// ton_proof: {
|
||||
// account: wallet.account,
|
||||
// ton_proof: wallet.connectItems.tonProof.proof,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
const refreshPayload = async () => {
|
||||
tonConnectUI.setConnectRequestParameters({ state: 'loading' });
|
||||
const value = await request.post<{ auth_v1_token: string }>('/auth.twa', {
|
||||
twa_data: WebApp.initData,
|
||||
});
|
||||
|
||||
// Case 3: Connected without proof - already authenticated
|
||||
console.log("DEBUG: Connected without proof, proceeding without it");
|
||||
return makeAuthRequest({
|
||||
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);
|
||||
|
||||
authResult = await makeAuthRequest({
|
||||
twa_data: WebApp.initData,
|
||||
ton_proof: {
|
||||
account: tonConnectUI.wallet!.account,
|
||||
ton_proof: tonProof,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Case 3: Connected without proof - already authenticated
|
||||
console.log('DEBUG: Connected without proof, proceeding without it');
|
||||
authResult = await makeAuthRequest({
|
||||
twa_data: WebApp.initData,
|
||||
});
|
||||
}
|
||||
|
||||
if (tonConnectUI.wallet?.account?.address) {
|
||||
console.log('DEBUG: Selecting wallet', tonConnectUI.wallet.account.address);
|
||||
await makeSelectWalletRequest({ wallet_address: tonConnectUI.wallet.account.address });
|
||||
}
|
||||
|
||||
return authResult;
|
||||
|
||||
// Commented this part for two reasons:
|
||||
// 1) When we include ton_proof from the wallet it fails the call for a reason of bad ton_proof
|
||||
// 2) This call could happen only if the first case happened and it means that the ton_proof is already have been stored once before
|
||||
// Case 2: Connected with proof - use it
|
||||
// if (wallet?.connectItems?.tonProof && !("error" in wallet.connectItems.tonProof)) {
|
||||
// console.log("DEBUG: Using existing proof", wallet.connectItems.tonProof.proof);
|
||||
// return makeAuthRequest({
|
||||
// twa_data: WebApp.initData,
|
||||
// ton_proof: {
|
||||
// account: wallet.account,
|
||||
// ton_proof: wallet.connectItems.tonProof.proof,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ type UseCreateNewContentPayload = {
|
|||
allowResale: boolean;
|
||||
authors: string[];
|
||||
royaltyParams: Royalty[];
|
||||
downloadable: boolean;
|
||||
};
|
||||
|
||||
export const useCreateNewContent = () => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ type RootStore = {
|
|||
allowCover: boolean;
|
||||
setAllowCover: (allowCover: boolean) => void;
|
||||
|
||||
allowDwnld: boolean;
|
||||
setAllowDwnld: (allowDwnld: boolean) => void;
|
||||
|
||||
cover: File | null;
|
||||
setCover: (cover: File | null) => void;
|
||||
|
||||
|
|
@ -68,6 +71,9 @@ export const useRootStore = create<RootStore>((set) => ({
|
|||
allowCover: false,
|
||||
setAllowCover: (allowCover) => set({ allowCover }),
|
||||
|
||||
allowDwnld: false,
|
||||
setAllowDwnld: (allowDwnld) => set({ allowDwnld }),
|
||||
|
||||
cover: null,
|
||||
setCover: (cover) => set({ cover }),
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue