video new

This commit is contained in:
dev.daminik00 2025-10-30 01:23:48 +01:00
parent c31325e01a
commit d9a8d171fb
9 changed files with 237 additions and 808 deletions

985
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"hls.js": "^1.6.13",
"idb": "^8.0.3",
"media-chrome": "^4.15.0",
"next": "15.3.3",
"next": "^15.5.6",
"next-intl": "^4.1.0",
"react": "^19.0.0",
"react-circular-progressbar": "^2.2.0",

View File

@ -1,7 +1,7 @@
import { notFound } from "next/navigation";
import { VideoGuideView } from "@/components/domains/video-guides";
import { DashboardData, DashboardSchema } from "@/entities/dashboard/types";
import { VideoGuideSchema } from "@/entities/dashboard/types";
import { http } from "@/shared/api/httpClient";
import { API_ROUTES } from "@/shared/constants/api-routes";
@ -15,15 +15,17 @@ export default async function VideoGuidePage({
}) {
const { id } = await params;
// Get fresh dashboard data without cache
const dashboard = await http.get<DashboardData>(API_ROUTES.dashboard(), {
cache: "no-store",
schema: DashboardSchema,
});
// Get specific video guide data from new API
const response = await http.get<{ status: string; data: typeof VideoGuideSchema._type }>(
API_ROUTES.videoGuide(id),
{
cache: "no-store",
}
);
const videoGuide = dashboard.videoGuides?.find(v => v.id === id);
const videoGuide = response.data;
if (!videoGuide || !videoGuide.isPurchased || !videoGuide.videoLink) {
if (!videoGuide || !videoGuide.isPurchased) {
notFound();
}
@ -32,7 +34,8 @@ export default async function VideoGuidePage({
id={videoGuide.id}
name={videoGuide.name}
description={videoGuide.description}
videoLink={videoGuide.videoLink}
videoLinkHLS={videoGuide.videoLinkHLS}
videoLinkDASH={videoGuide.videoLinkDASH}
contentUrl={videoGuide.contentUrl}
/>
);

View File

@ -71,6 +71,7 @@ export default function VideoGuideCard(props: VideoGuideCardProps) {
alt={name}
width={260}
height={160}
priority
unoptimized
className={styles.imageContent}
/>

View File

@ -18,17 +18,16 @@ function VideoGuideCardWrapper({ videoGuide }: { videoGuide: VideoGuide }) {
const { handlePurchase, isCheckoutLoading, isProcessingPurchase } =
useVideoGuidePurchase({
videoGuideId: videoGuide.id,
productId: videoGuide.id,
productId: videoGuide.productId, // Используем productId из payment-service
productKey: videoGuide.key,
});
// Для купленных видео - ссылка на страницу просмотра
const href =
videoGuide.isPurchased && videoGuide.videoLink
? `/video-guides/${videoGuide.id}`
: "#";
const href = videoGuide.isPurchased
? `/video-guides/${videoGuide.id}`
: "#";
const isClickable = videoGuide.isPurchased && videoGuide.videoLink;
const isClickable = videoGuide.isPurchased;
const cardElement = (
<VideoGuideCard

View File

@ -14,14 +14,16 @@ interface VideoGuideViewProps {
id: string;
name: string;
description: string;
videoLink: string;
videoLinkHLS: string;
videoLinkDASH: string;
contentUrl?: string;
}
export default function VideoGuideView({
name,
description,
videoLink,
videoLinkHLS,
videoLinkDASH,
contentUrl,
}: VideoGuideViewProps) {
const router = useRouter();
@ -51,13 +53,6 @@ export default function VideoGuideView({
loadMarkdown();
}, [contentUrl]);
// TODO: Remove hardcoded URLs when backend is ready
// Temporary hardcoded DASH/HLS URLs - using for ALL videos until backend updated
const dashUrl = "https://video.witlab.us/videos/TALK_FEELINGS/cmaf/source.mpd";
const hlsUrl = "https://video.witlab.us/videos/TALK_FEELINGS/cmaf/source.m3u8";
const _originalVideoLink = videoLink; // Keep for reference when backend is ready
return (
<div className={styles.container}>
{/* Header with back button and title */}
@ -79,7 +74,7 @@ export default function VideoGuideView({
<div className={styles.contentWrapper}>
{/* Video Player */}
<div className={styles.videoContainer}>
<VideoPlayer mpd={dashUrl} m3u8={hlsUrl} />
<VideoPlayer mpd={videoLinkDASH} m3u8={videoLinkHLS} />
</div>
{/* Description or Markdown Content */}

View File

@ -64,6 +64,7 @@ export type PartnerPortrait = z.infer<typeof PartnerPortraitSchema>;
/* ---------- Video Guide ---------- */
export const VideoGuideSchema = z.object({
id: z.string(),
productId: z.string(), // ID продукта для покупки
key: z.string(),
type: z.string(),
name: z.string(),
@ -74,7 +75,8 @@ export const VideoGuideSchema = z.object({
oldPrice: z.number(),
discount: z.number(),
isPurchased: z.boolean(),
videoLink: z.string().optional(),
videoLinkHLS: z.string(), // HLS format (.m3u8)
videoLinkDASH: z.string(), // DASH format (.mpd)
contentUrl: z.string().optional(), // URL to markdown content file
});
export type VideoGuide = z.infer<typeof VideoGuideSchema>;

View File

@ -35,8 +35,8 @@ export function useVideoGuidePurchase(options: UseVideoGuidePurchaseOptions) {
// Включаем лоадер на всей карточке
setIsProcessingPurchase(true);
// Ждем 4 секунды
await new Promise(resolve => setTimeout(resolve, 4000));
// Ждем 3 секунды перед обновлением
await new Promise(resolve => setTimeout(resolve, 3000));
// Обновляем данные dashboard в transition
// isPending будет true пока данные загружаются

View File

@ -13,6 +13,8 @@ const createRoute = (
export const API_ROUTES = {
dashboard: () => createRoute(["dashboard"]),
videoGuides: () => createRoute(["video-guides"], ROOT_ROUTE_V2),
videoGuide: (id: string) => createRoute(["video-guides", id], ROOT_ROUTE_V2),
checkVideoGuidePurchase: (productKey: string) =>
createRoute(["products", "video-guides", productKey, "check-purchase"]),
subscriptions: () => createRoute(["payment", "subscriptions"], ROOT_ROUTE_V3),