| update audio player & other fixes

This commit is contained in:
rakhimovkamran 2024-08-19 05:01:17 +05:00
parent b03533dea6
commit c5c36cb078
8 changed files with 602 additions and 371 deletions

View File

@ -22,4 +22,61 @@
a {
@apply transition-all active:opacity-60;
}
/*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 */
);
border-radius: 9999px; /* Rounded-full track */
outline: none; /* Remove outline */
transition: background 0.3s;
}
/* Style the thumb (toggle) */
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 */
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 */
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 */
);
border-radius: 9999px; /* Rounded-full track */
outline: none; /* Remove outline */
transition: background 0.3s;
}
}

View File

@ -12,6 +12,7 @@ import { CoverButton } from "~/pages/root/steps/data-step/components/cover-butto
import { HiddenFileInput } from "~/shared/ui/hidden-file-input";
import { useRootStore } from "~/shared/stores/root";
import { Checkbox } from "~/shared/ui/checkbox";
import { AudioPlayer } from "~/shared/ui/audio-player";
type DataStepProps = {
nextStep(): void;
@ -47,6 +48,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
if (values.author) {
rootStore.setAuthor(values.author);
}
nextStep();
} catch (error) {
console.error("Error:", error);
@ -88,6 +90,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
onChange={(file) => {
rootStore.setFile(file);
rootStore.setFileSrc(URL.createObjectURL(file));
rootStore.setFileType(file.type); // Save the file type for conditional rendering
}}
/>
@ -99,6 +102,9 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm"
}
>
{rootStore.fileType?.startsWith("audio") ? (
<AudioPlayer src={rootStore.fileSrc} />
) : (
<ReactPlayer
playsinline={true}
controls={true}
@ -106,9 +112,7 @@ export const DataStep = ({ nextStep }: DataStepProps) => {
config={{ file: { attributes: { playsInline: true } } }}
url={rootStore.fileSrc}
/>
)}
</div>
)}
</FormLabel>

View File

@ -12,6 +12,8 @@ import { useUploadFile } from "~/shared/services/file";
import { Progress } from "~/shared/ui/progress";
import { useCreateNewContent } from "~/shared/services/content";
import { BackButton } from "~/shared/ui/back-button";
import { useTonConnectUI } from "@tonconnect/ui-react";
type PresubmitStepProps = {
prevStep(): void;
@ -20,6 +22,8 @@ type PresubmitStepProps = {
export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
const WebApp = useWebApp();
const [tonConnectUI] = useTonConnectUI();
const rootStore = useRootStore();
const [impactOccurred] = useHapticFeedback();
@ -44,7 +48,7 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
);
}
await createContent.mutateAsync({
const createContentResponse = await createContent.mutateAsync({
title: rootStore.name,
// Это для одного автора
@ -78,9 +82,32 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
: { allowResale: false, resaleLicensePrice: "0" }),
});
if (createContentResponse.data) {
const transactionResponse = await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 120,
messages: [
{
amount: createContentResponse.data.amount,
address: createContentResponse.data.address,
payload: createContentResponse.data.payload,
},
],
});
if (transactionResponse.boc) {
WebApp.close();
await tonConnectUI.disconnect();
} else {
console.error("Transaction failed:", transactionResponse);
}
}
WebApp.close();
// @ts-expect-error Type issues
} catch (error: never) {
await tonConnectUI.disconnect();
console.error("An error occurred during the submission process:", error);
if (error?.status === 400) {
@ -161,8 +188,6 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
config={{ file: { attributes: { playsInline: true } } }}
url={rootStore.fileSrc}
/>
</div>
)}

View File

@ -118,7 +118,7 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
</div>
<div className={"flex flex-col gap-[20px]"}>
<FormLabel label={"Цена TON"}>
<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"}>

View File

@ -1,11 +1,12 @@
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";
import { useCallback } from "react";
import { AudioPlayer } from "~/shared/ui/audio-player";
export const ViewContentPage = () => {
const WebApp = useWebApp();
@ -16,43 +17,41 @@ export const ViewContentPage = () => {
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 () => {
const handleBuyContent = useCallback(async () => {
try {
if (!tonConnectUI.connected) {
await tonConnectUI.openModal();
return;
}
const res = await tonConnectUI.sendTransaction(transaction);
if (res.boc) {
await purchaseContent({
const contentResponse = await purchaseContent({
content_address: content?.data?.encrypted?.cid,
license_type: "listen",
});
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,
},
],
});
if (transactionResponse.boc) {
WebApp.close();
await tonConnectUI.disconnect();
} else {
console.error("Transaction failed:", res);
console.error("Transaction failed:", transactionResponse);
}
} catch (error) {
await tonConnectUI.disconnect();
console.error("Error handling Ton Connect subscription:", error);
}
};
}, [content]);
return (
<main className={"flex w-full flex-col gap-[50px] px-4"}>
@ -66,6 +65,9 @@ export const ViewContentPage = () => {
</div>
)}
{content?.data?.content_type.startsWith("audio") ? (
<AudioPlayer src={content?.data?.display_options?.content_url} />
) : (
<ReactPlayer
playsinline={true}
controls={true}
@ -73,6 +75,7 @@ export const ViewContentPage = () => {
config={{ file: { attributes: { playsInline: true } } }}
url={content?.data?.display_options?.content_url}
/>
)}
<section className={"flex flex-col"}>
<h1 className={"text-[20px] font-bold"}>

View File

@ -20,7 +20,9 @@ export const useCreateNewContent = () => {
["create-new-content"],
(payload: UseCreateNewContentPayload) => {
return request.post<{
message: string;
address: string;
amount: string;
payload: string;
}>("/blockchain.sendNewContentMessage", payload);
},
);
@ -53,7 +55,11 @@ export const usePurchaseContent = () => {
content_address: string;
license_type: "listen" | "resale";
}) => {
return request.post("/blockchain.sendPurchaseContentMessage", {
return request.post<{
address: string;
amount: string;
payload: string;
}>("/blockchain.sendPurchaseContentMessage", {
content_address,
license_type,
});

View File

@ -15,6 +15,9 @@ type RootStore = {
file: File | null;
setFile: (file: File) => void;
fileType: string;
setFileType: (type: string) => void;
fileSrc: string;
setFileSrc: (fileSrc: string) => void;
@ -53,6 +56,9 @@ export const useRootStore = create<RootStore>((set) => ({
file: null,
setFile: (file) => set({ file }),
fileType: "",
setFileType: (fileType) => set({ fileType }),
fileSrc: "",
setFileSrc: (fileSrc) => set({ fileSrc }),

View File

@ -0,0 +1,130 @@
import { FC, useEffect, useRef, useState } from "react";
interface AudioPlayerProps {
src: string;
}
export const AudioPlayer: FC<AudioPlayerProps> = ({ src }) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [currentTime, setCurrentTime] = useState<number>(0);
const [duration, setDuration] = useState<number>(0);
const audioRef = useRef<HTMLAudioElement | null>(null);
const rangeRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
const handleLoadedMetadata = () => {
setDuration(audio.duration);
};
const handleTimeUpdate = () => {
setCurrentTime(audio.currentTime);
updateRangeBackground(audio.currentTime);
};
audio.addEventListener("loadedmetadata", handleLoadedMetadata);
audio.addEventListener("timeupdate", handleTimeUpdate);
return () => {
audio.removeEventListener("loadedmetadata", handleLoadedMetadata);
audio.removeEventListener("timeupdate", handleTimeUpdate);
};
}, []);
const togglePlayPause = () => {
const audio = audioRef.current;
if (!audio) return;
if (isPlaying) {
audio.pause();
} else {
audio.play();
}
setIsPlaying(!isPlaying);
};
const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const audio = audioRef.current;
if (!audio) return;
const newTime = parseFloat(e.target.value);
audio.currentTime = newTime;
setCurrentTime(newTime);
updateRangeBackground(newTime);
};
const formatTime = (time: number): string => {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
};
const updateRangeBackground = (currentTime: number) => {
if (rangeRef.current) {
const percentage = (currentTime / duration) * 100;
rangeRef.current.style.setProperty(
"--value-percentage",
`${percentage}%`,
);
}
};
return (
<div className="flex items-center space-x-4">
<audio ref={audioRef} src={src} />
<button
onClick={togglePlayPause}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-gray focus:outline-none"
>
{isPlaying ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="size-6"
>
<path
fillRule="evenodd"
d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z"
clipRule="evenodd"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="size-6"
>
<path
fillRule="evenodd"
d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z"
clipRule="evenodd"
/>
</svg>
)}
</button>
<div className={"flex w-full flex-col gap-2"}>
<input
type="range"
ref={rangeRef}
min="0"
max={duration}
value={currentTime}
onChange={handleSliderChange}
className="flex-grow"
/>
<div className="flex items-center justify-between text-xs">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
</div>
</div>
);
};