dragonflight/services/editor/apps/web/src/pages/SharePage.tsx
Zac b68f0c6aba feat(editor): integrate openreel-video as services/editor with MAM hooks
Vendored Augani/openreel-video (MIT) into services/editor and wired it to the MAM. Editor runs as its own container on port 47435. Library assets pull in via ?asset=<uuid>; render exports route back via POST /api/v1/upload/simple. Sidebar Editor link on every page; Edit button on every preview modal. See services/editor/INTEGRATION.md for the patch map.
2026-05-17 21:44:37 -04:00

208 lines
6.7 KiB
TypeScript

import React, { useEffect, useState } from "react";
import {
Play,
Download,
Clock,
AlertCircle,
ExternalLink,
Loader2,
} from "lucide-react";
import {
getShareInfo,
getShareDownloadUrl,
formatExpiresIn,
isShareExpired,
type ShareInfo,
} from "../services/share-service";
interface SharePageProps {
shareId: string;
}
type PageStatus = "loading" | "ready" | "expired" | "not-found" | "error";
export const SharePage: React.FC<SharePageProps> = ({ shareId }) => {
const [status, setStatus] = useState<PageStatus>("loading");
const [shareInfo, setShareInfo] = useState<ShareInfo | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadShareInfo = async () => {
try {
const info = await getShareInfo(shareId);
if (!info) {
setStatus("not-found");
return;
}
if (isShareExpired(info.expiresAt)) {
setStatus("expired");
return;
}
setShareInfo(info);
setStatus("ready");
} catch (err) {
if (err instanceof Error && err.message.includes("expired")) {
setStatus("expired");
} else {
setError(err instanceof Error ? err.message : "Failed to load share");
setStatus("error");
}
}
};
loadShareInfo();
}, [shareId]);
const downloadUrl = getShareDownloadUrl(shareId);
const handleCreateProject = () => {
window.location.hash = "#/editor";
};
if (status === "loading") {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center space-y-4">
<Loader2 size={48} className="text-primary animate-spin mx-auto" />
<p className="text-text-muted">Loading video...</p>
</div>
</div>
);
}
if (status === "not-found") {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center space-y-6 max-w-md px-6">
<div className="w-20 h-20 mx-auto bg-text-muted/10 rounded-full flex items-center justify-center">
<AlertCircle size={40} className="text-text-muted" />
</div>
<div>
<h1 className="text-2xl font-bold text-text-primary">
Video Not Found
</h1>
<p className="text-text-muted mt-2">
This video doesn't exist or the link is invalid.
</p>
</div>
<button
onClick={handleCreateProject}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary hover:bg-primary-hover text-white font-bold rounded-lg transition-colors"
>
<ExternalLink size={18} />
Create Your Own Video
</button>
</div>
</div>
);
}
if (status === "expired") {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center space-y-6 max-w-md px-6">
<div className="w-20 h-20 mx-auto bg-warning/10 rounded-full flex items-center justify-center">
<Clock size={40} className="text-warning" />
</div>
<div>
<h1 className="text-2xl font-bold text-text-primary">
Link Expired
</h1>
<p className="text-text-muted mt-2">
This share link has expired. Share links are only valid for 24
hours.
</p>
</div>
<button
onClick={handleCreateProject}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary hover:bg-primary-hover text-white font-bold rounded-lg transition-colors"
>
<ExternalLink size={18} />
Create Your Own Video
</button>
</div>
</div>
);
}
if (status === "error") {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center space-y-6 max-w-md px-6">
<div className="w-20 h-20 mx-auto bg-error/10 rounded-full flex items-center justify-center">
<AlertCircle size={40} className="text-error" />
</div>
<div>
<h1 className="text-2xl font-bold text-text-primary">Error</h1>
<p className="text-text-muted mt-2">
{error || "Something went wrong"}
</p>
</div>
<button
onClick={() => window.location.reload()}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary hover:bg-primary-hover text-white font-bold rounded-lg transition-colors"
>
Try Again
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
<div className="max-w-4xl mx-auto px-6 py-12 space-y-8">
<div className="text-center space-y-2">
<h1 className="text-2xl font-bold text-text-primary">
{shareInfo?.filename || "Shared Video"}
</h1>
{shareInfo && (
<div className="flex items-center justify-center gap-4 text-sm text-text-muted">
<span>{(shareInfo.size / (1024 * 1024)).toFixed(1)} MB</span>
<span></span>
<div className="flex items-center gap-1">
<Clock size={14} />
<span>{formatExpiresIn(shareInfo.expiresAt)}</span>
</div>
</div>
)}
</div>
<div className="relative aspect-video bg-black rounded-xl overflow-hidden shadow-2xl">
<video src={downloadUrl} controls className="w-full h-full" poster="">
Your browser does not support the video tag.
</video>
</div>
<div className="flex items-center justify-center gap-4">
<a
href={downloadUrl}
download={shareInfo?.filename}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary hover:bg-primary-hover text-white font-bold rounded-lg transition-colors"
>
<Download size={18} />
Download
</a>
<button
onClick={handleCreateProject}
className="inline-flex items-center gap-2 px-6 py-3 bg-background-secondary hover:bg-background-tertiary border border-border text-text-primary font-medium rounded-lg transition-colors"
>
<Play size={18} />
Create Your Own
</button>
</div>
<div className="text-center">
<p className="text-xs text-text-muted">
Made with{" "}
<a href="#/editor" className="text-primary hover:underline">
Open Reel Video
</a>
</p>
</div>
</div>
</div>
);
};