feat: viewContent page by mik

This commit is contained in:
user 2024-05-24 13:48:39 +03:00
parent 35c9a6704d
commit 6d37b74706
12 changed files with 300 additions and 22 deletions

View File

@ -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",

BIN
public/splash.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -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 (
<WebAppProvider options={{ smoothButtonsTransition: true }}>
<QueryClientProvider client={queryClient}>
<main className="antialiased">{children}</main>
</QueryClientProvider>
<TonConnectUIProvider
manifestUrl={
"https://my-public-node-1.projscale.dev/api/tonconnect-manifest.json"
}
>
<QueryClientProvider client={queryClient}>
<main className="antialiased">{children}</main>
</QueryClientProvider>
</TonConnectUIProvider>
</WebAppProvider>
);
};

View File

@ -1,3 +1,4 @@
export const Routes = {
Root: "/uploadContent",
ViewContent: "/viewContent",
};

View File

@ -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: <RootPage /> },
{ path: Routes.ViewContent, element: <ViewContentPage /> },
]);
export const AppRouter = () => {

View File

@ -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 [
// <WelcomeStep nextStep={nextStep} />,
<WelcomeStep nextStep={nextStep} />,
<DataStep nextStep={nextStep} />,
// <AuthorsStep prevStep={prevStep} nextStep={nextStep} />,
<RoyaltyStep prevStep={prevStep} nextStep={nextStep} />,

View File

@ -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) => {
<Button
className={"mt-[30px]"}
onClick={handleSubmit}
// onClick={handleSubmit}
onClick={() => 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)
// }
/>
</section>
);

View File

@ -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 (
<section
className={"relative flex h-[100vh] items-center justify-center"}
>
<img alt={"splash"} className={"mb-20 h-[300px]"} src={"/splash.gif"} />
</section>
);
}
return (
<section className={"mt-4 px-4"}>
<section className={"mt-4 flex flex-col px-4"}>
<div className={"flex items-center justify-center"}>
<img
alt={"splash"}
className={" w-full shrink-0"}
src={"/splash.gif"}
/>
</div>
<div className={"flex gap-2 text-sm"}>
<span>/ Добро пожаловать в MY</span>
@ -34,8 +75,8 @@ export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => {
</div>
<Button
label={"Подключить криптокошелёк TON"}
className={"mt-[30px]"}
label={"Подключить криптокошелёк TON"}
includeArrows={true}
isLoading={auth.isLoading}
onClick={handleNextClick}

View File

@ -0,0 +1,96 @@
import ReactPlayer from "react-player/lazy";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { useMemo } from "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";
export const ViewContentPage = () => {
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 (
<main className={"flex w-full flex-col gap-[50px] px-4"}>
{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>
)}
<ReactPlayer
playsinline={true}
controls={true}
width={"100%"}
config={{}}
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>
<Button
onClick={handleBuyContent}
className={"mb-4 mt-[30px] h-[48px]"}
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.listen?.price)} ТОН`}
includeArrows={true}
/>
</main>
);
};

View File

@ -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,
});
},
);
};

View File

@ -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;

100
yarn.lock
View File

@ -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"