diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..9f1e92d
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,7 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "tabWidth": 4,
+ "printWidth": 100,
+ "trailingComma": "es5"
+ }
\ No newline at end of file
diff --git a/src/app/router/protected-layout/index.tsx b/src/app/router/protected-layout/index.tsx
index 468fcb7..1abd710 100644
--- a/src/app/router/protected-layout/index.tsx
+++ b/src/app/router/protected-layout/index.tsx
@@ -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 ;
-};
\ No newline at end of file
+ return ;
+};
diff --git a/src/pages/root/steps/data-step/index.tsx b/src/pages/root/steps/data-step/index.tsx
index eacd209..dc42ac7 100644
--- a/src/pages/root/steps/data-step/index.tsx
+++ b/src/pages/root/steps/data-step/index.tsx
@@ -165,6 +165,16 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
+
rootStore.setAllowDwnld(!rootStore.allowDwnld)}
+ checked={rootStore.allowDwnld}
+ />
+ }
+ />
{
// Откомментировать при условии того что вы принимаете много авторов
// следует отметить что вы должны еще откомментровать AuthorsStep в RootPage
// authors: rootStore.authors,
-
+ downloadable: rootStore.allowDwnld,
content: fileUploadResult.content_id_v1,
image: coverUploadResult.content_id_v1,
price: String(rootStore.price * 10 ** 9),
diff --git a/src/pages/view-content/index.tsx b/src/pages/view-content/index.tsx
index 575f557..cbc79ee 100644
--- a/src/pages/view-content/index.tsx
+++ b/src/pages/view-content/index.tsx
@@ -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 (
+
+ {isCongratsModal && }
+ {isErrorModal && }
+ {content?.data?.content_type.startsWith('audio') &&
+ content?.data?.display_options?.metadata?.image && (
+
+

+
+ )}
- useEffect(() => {
- const interval = setInterval(() => {
- void refetchContent()
- }, 5000)
+ {content?.data?.content_type.startsWith('audio') ? (
+
+ ) : (
+
+ )}
- return () => clearInterval(interval)
- }, []);
+
+
+ {content?.data?.display_options?.metadata?.name}
+
+ {/*Russian
*/}
+ {/*2022
*/}
+
+ {content?.data?.display_options?.metadata?.description}
+
+
- const handleConfirmCongrats = () => {
- setIsCongratsModal(!isCongratsModal);
- };
-
- const handleErrorModal = () => {
- setIsErrorModal(!isErrorModal);
- }
- return (
-
- {isCongratsModal && }
- {isErrorModal && }
- {content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
-
-

-
- )}
-
- {content?.data?.content_type.startsWith("audio") ? (
-
- ) : (
-
- )}
-
-
-
- {content?.data?.display_options?.metadata?.name}
-
- {/*Russian
*/}
- {/*2022
*/}
-
- {content?.data?.display_options?.metadata?.description}
-
-
-
-
- {!haveLicense &&
-
- {content?.data?.invoice && (
-
- )}
-
- }
-
-
- );
+
+ {content?.data?.downloadable && (
+
handleDwnldContent()}
+ className={'h-[48px] bg-darkred mb-4'}
+ label={`Скачать контент`}
+ />
+ )}
+ {!haveLicense && (
+
+
+ {content?.data?.invoice && (
+
+ )}
+
+ )}
+ {
+ WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
+ }}
+ className={'h-[48px] bg-darkred'}
+ label={`Загрузить свой контент`}
+ />
+ {tonConnectUI.connected && (
+ {
+ tonConnectUI.disconnect();
+ }}
+ className={'h-[48px] bg-darkred mt-4'}
+ label={`Отключить кошелек`}
+ />
+ )}
+
+
+ );
};
diff --git a/src/shared/libs/request/index.ts b/src/shared/libs/request/index.ts
index 6b7338e..9eae009 100644
--- a/src/shared/libs/request/index.ts
+++ b/src/shared/libs/request/index.ts
@@ -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;
});
diff --git a/src/shared/services/auth/index.ts b/src/shared/services/auth/index.ts
index bc63a47..f4197a4 100644
--- a/src/shared/services/auth/index.ts
+++ b/src/shared/services/auth/index.ts
@@ -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 | undefined>();
+ const WebApp = useWebApp();
+ // const wallet = useTonWallet();
+ const [tonConnectUI] = useTonConnectUI();
+ const interval = useRef | 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,
+ // },
+ // });
+ // }
});
- });
-};
\ No newline at end of file
+};
diff --git a/src/shared/services/content/index.ts b/src/shared/services/content/index.ts
index bbad153..44e5417 100644
--- a/src/shared/services/content/index.ts
+++ b/src/shared/services/content/index.ts
@@ -14,6 +14,7 @@ type UseCreateNewContentPayload = {
allowResale: boolean;
authors: string[];
royaltyParams: Royalty[];
+ downloadable: boolean;
};
export const useCreateNewContent = () => {
diff --git a/src/shared/stores/root/index.ts b/src/shared/stores/root/index.ts
index 94a9ab4..6baf63e 100644
--- a/src/shared/stores/root/index.ts
+++ b/src/shared/stores/root/index.ts
@@ -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((set) => ({
allowCover: false,
setAllowCover: (allowCover) => set({ allowCover }),
+ allowDwnld: false,
+ setAllowDwnld: (allowDwnld) => set({ allowDwnld }),
+
cover: null,
setCover: (cover) => set({ cover }),