diff --git a/package.json b/package.json index ef95577..ca2237b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.3.4", + "@tonconnect/ui-react": "^2.0.2", "@vkruglikov/react-telegram-web-app": "^2.1.9", "axios": "^1.6.7", "clsx": "^2.1.0", diff --git a/public/splash.gif b/public/splash.gif new file mode 100644 index 0000000..b1e68c1 Binary files /dev/null and b/public/splash.gif differ diff --git a/src/app/providers/index.tsx b/src/app/providers/index.tsx index 199d33a..7e644ed 100644 --- a/src/app/providers/index.tsx +++ b/src/app/providers/index.tsx @@ -1,6 +1,7 @@ import { ReactNode } from "react"; import { QueryClientProvider } from "react-query"; import { WebAppProvider } from "@vkruglikov/react-telegram-web-app"; +import { TonConnectUIProvider } from "@tonconnect/ui-react"; import { queryClient } from "~/shared/libs"; @@ -11,9 +12,15 @@ type ProvidersProps = { export const Providers = ({ children }: ProvidersProps) => { return ( - - {children} - + + + {children} + + ); }; diff --git a/src/app/router/constants/index.ts b/src/app/router/constants/index.ts index f53c493..770f2ff 100644 --- a/src/app/router/constants/index.ts +++ b/src/app/router/constants/index.ts @@ -1,3 +1,4 @@ export const Routes = { Root: "/uploadContent", + ViewContent: "/viewContent", }; diff --git a/src/app/router/index.tsx b/src/app/router/index.tsx index 3d7d669..2ccd9ea 100644 --- a/src/app/router/index.tsx +++ b/src/app/router/index.tsx @@ -2,9 +2,11 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { Routes } from "~/app/router/constants"; import { RootPage } from "~/pages/root"; +import { ViewContentPage } from "~/pages/view-content"; const router = createBrowserRouter([ { path: Routes.Root, element: }, + { path: Routes.ViewContent, element: }, ]); export const AppRouter = () => { diff --git a/src/pages/root/index.tsx b/src/pages/root/index.tsx index dcc4a79..bffbdf5 100644 --- a/src/pages/root/index.tsx +++ b/src/pages/root/index.tsx @@ -4,11 +4,12 @@ import { PresubmitStep } from "./steps/presubmit-step"; import { useSteps } from "~/shared/hooks/use-steps"; import { PriceStep } from "~/pages/root/steps/price-step"; +import { WelcomeStep } from "~/pages/root/steps/welcome-step"; export const RootPage = () => { const { ActiveSection } = useSteps(({ nextStep, prevStep }) => { return [ - // , + , , // , , diff --git a/src/pages/root/steps/data-step/index.tsx b/src/pages/root/steps/data-step/index.tsx index ab14327..e5bd4fe 100644 --- a/src/pages/root/steps/data-step/index.tsx +++ b/src/pages/root/steps/data-step/index.tsx @@ -3,6 +3,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import ReactPlayer from "react-player/lazy"; +import { useNavigate } from "react-router-dom"; import { FormLabel } from "~/shared/ui/form-label"; import { Input } from "~/shared/ui/input"; @@ -13,6 +14,7 @@ import { HiddenFileInput } from "~/shared/ui/hidden-file-input"; import { useRootStore } from "~/shared/stores/root"; import { Checkbox } from "~/shared/ui/checkbox"; import { useAuth } from "~/shared/services/auth"; +import { Routes } from "~/app/router/constants"; type DataStepProps = { nextStep(): void; @@ -22,6 +24,8 @@ export const DataStep = ({ nextStep }: DataStepProps) => { const rootStore = useRootStore(); const auth = useAuth(); + const navigate = useNavigate(); + const formSchema = useMemo(() => { return z.object({ name: z.string().min(1, "Обязательное поле"), @@ -154,14 +158,15 @@ export const DataStep = ({ nextStep }: DataStepProps) => { navigate(Routes.ViewContent)} includeArrows={true} label={"Готово"} - disabled={ - !form.formState.isValid || - !rootStore.file || - (rootStore.allowCover && !rootStore.cover) - } + // disabled={ + // !form.formState.isValid || + // !rootStore.file || + // (rootStore.allowCover && !rootStore.cover) + // } /> ); diff --git a/src/pages/root/steps/welcome-step/index.tsx b/src/pages/root/steps/welcome-step/index.tsx index 75037b8..1a6bd58 100644 --- a/src/pages/root/steps/welcome-step/index.tsx +++ b/src/pages/root/steps/welcome-step/index.tsx @@ -1,3 +1,6 @@ +import { useEffect, useState } from "react"; +import { useTonConnectUI } from "@tonconnect/ui-react"; + import { Button } from "~/shared/ui/button"; import { useAuth } from "~/shared/services/auth"; @@ -6,16 +9,54 @@ type WelcomeStepProps = { }; export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => { + const [tonConnectUI] = useTonConnectUI(); + const [isLoaded, setLoaded] = useState(false); + const auth = useAuth(); const handleNextClick = async () => { - const res = await auth.mutateAsync(); - sessionStorage.setItem("auth_v1_token", res.data.auth_v1_token); - nextStep(); + if (tonConnectUI.connected) { + nextStep(); + } else { + await tonConnectUI.openModal(); + + const res = await auth.mutateAsync(); + sessionStorage.setItem("auth_v1_token", res.data.auth_v1_token); + } }; + useEffect(() => { + setTimeout(() => { + setLoaded(true); + }, 4000); + }, []); + + useEffect(() => { + if (tonConnectUI.connected) { + nextStep(); + } + }, [nextStep, tonConnectUI.connected]); + + if (!isLoaded) { + return ( + + + + ); + } + return ( - + + + + + / Добро пожаловать в MY @@ -34,8 +75,8 @@ export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => { { + const WebApp = useWebApp(); + + const { data: content } = useViewContent(WebApp.initDataUnsafe?.start_param); + + const { mutateAsync: purchaseContent } = usePurchaseContent(); + + const [tonConnectUI] = useTonConnectUI(); + + const transaction = useMemo(() => { + const address = content?.data?.encrypted?.cid; + + return { + validUntil: Math.floor(Date.now() / 1000) + 120, + messages: [ + { amount: content?.data?.encrypted?.license?.listen?.price, address }, + ], + }; + }, [content?.data]); + + const handleBuyContent = async () => { + try { + if (!tonConnectUI.connected) { + await tonConnectUI.openModal(); + return; + } + + const res = await tonConnectUI.sendTransaction(transaction); + + if (res.boc) { + await purchaseContent({ + content_address: content?.data?.encrypted?.cid, + license_type: "listen", + }); + + WebApp.close(); + + await tonConnectUI.disconnect(); + } else { + console.error("Transaction failed:", res); + } + } catch (error) { + await tonConnectUI.disconnect(); + console.error("Error handling Ton Connect subscription:", error); + } + }; + + return ( + + {content?.data?.display_options?.metadata?.image && ( + + + + )} + + + + + + {content?.data?.display_options?.metadata?.name} + + {/*Russian*/} + {/*2022*/} + + {content?.data?.display_options?.metadata?.description} + + + + + + ); +}; diff --git a/src/shared/services/content/index.ts b/src/shared/services/content/index.ts index 25d442c..ff7cb1a 100644 --- a/src/shared/services/content/index.ts +++ b/src/shared/services/content/index.ts @@ -1,4 +1,4 @@ -import { useMutation } from "react-query"; +import { useMutation, useQuery } from "react-query"; import { request } from "~/shared/libs"; import { Royalty } from "~/shared/stores/root"; @@ -26,13 +26,37 @@ export const useCreateNewContent = () => { ); }; +// export const usePurchaseContent = () => { +// return useMutation( +// ["purchase-content"], +// (payload: { content_address: string; price: string }) => { +// return request.post<{ +// message: string; +// }>("/blockchain.sendPurchaseContentMessage", payload); +// }, +// ); +// }; + +export const useViewContent = (contentId: string) => { + return useQuery(["view", "content", contentId], () => { + return request.get(`/content.view/${contentId}`); + }); +}; + export const usePurchaseContent = () => { return useMutation( - ["purchase-content"], - (payload: { content_address: string; price: string }) => { - return request.post<{ - message: string; - }>("/blockchain.sendPurchaseContentMessage", payload); + ["purchase", "content"], + ({ + content_address, + license_type, + }: { + content_address: string; + license_type: "listen" | "resale"; + }) => { + return request.post("/blockchain.sendPurchaseContentMessage", { + content_address, + license_type, + }); }, ); }; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index 4510578..9ca098b 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -17,3 +17,5 @@ export const processFile = async (file: File) => { export const getIndexArray = (len: number) => new Array(len).fill("").map((_, i) => i); + +export const fromNanoTON = (amount: string) => Number(amount) / 10 ** 9; diff --git a/yarn.lock b/yarn.lock index acfafb4..61f43e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -541,6 +541,54 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz#9ffdf9ed133a7464f4ae187eb9e1294413fab235" integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg== +"@tonconnect/isomorphic-eventsource@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz#e58c44cf9953e090f2c35da9a638946ddb614be5" + integrity sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ== + dependencies: + eventsource "^2.0.2" + +"@tonconnect/isomorphic-fetch@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz#31978e04ddc4428eff532c23d20229ed5ddb6417" + integrity sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w== + dependencies: + node-fetch "^2.6.9" + +"@tonconnect/protocol@^2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@tonconnect/protocol/-/protocol-2.2.6.tgz#24b3fbcde6003e65fb5840a190072db5378699db" + integrity sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w== + dependencies: + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + +"@tonconnect/sdk@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@tonconnect/sdk/-/sdk-3.0.2.tgz#013bbfda9ec11e907bff0291cef046250fecc534" + integrity sha512-TEPIoczYZhJcXu9pixYJimGlYKLWLSkgJZgC2vwHLObwuqoiOa06BTOlooaxoNFgZ0LOgIzt+QRs8tF8jyYsSw== + dependencies: + "@tonconnect/isomorphic-eventsource" "^0.0.2" + "@tonconnect/isomorphic-fetch" "^0.0.3" + "@tonconnect/protocol" "^2.2.6" + +"@tonconnect/ui-react@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@tonconnect/ui-react/-/ui-react-2.0.2.tgz#917956381c66ab3a1f891a9291e4ec760cc6a841" + integrity sha512-nNsaB8DzQsVfBRtKli4KTC4Nj1WwJ+gHP4j9FOgjqNylf+VRYH5mpDkpRw/vRVwOYNMsZ4k8ZznreccWqFJRXg== + dependencies: + "@tonconnect/ui" "2.0.2" + +"@tonconnect/ui@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@tonconnect/ui/-/ui-2.0.2.tgz#dd3e5719ab582af14d726f4e5f7da0baf7614eb7" + integrity sha512-x/iI7yX8yMvs7HKZ1wm3Quz5yg8mJJwZQlg+DYHGBvqdx0JMqXbF8YOcvGdh/Lhmrq92ahE8QIwX+85cS80vjQ== + dependencies: + "@tonconnect/sdk" "3.0.2" + classnames "^2.3.2" + deepmerge "^4.2.2" + ua-parser-js "^1.0.35" + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -1038,6 +1086,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +classnames@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clsx@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" @@ -1127,7 +1180,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.0.0: +deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -1496,6 +1549,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2210,6 +2268,13 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-fetch@^2.6.9: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -2918,6 +2983,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-api-utils@^1.0.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" @@ -2948,6 +3018,16 @@ tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tweetnacl-util@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -3009,6 +3089,11 @@ typescript@^5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +ua-parser-js@^1.0.35: + version "1.0.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" + integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -3077,6 +3162,19 @@ vite@^5.1.4: optionalDependencies: fsevents "~2.3.3" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+ {content?.data?.display_options?.metadata?.description} +