fix(frontend): add no auth screen for clips
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { ArrowLeft, Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
|
import { ArrowLeft, Lock, Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
|
||||||
import type { Clip } from "../utils/types";
|
import type { Clip } from "../utils/types";
|
||||||
import { getClipById, getVideoBlob } from "../utils/endpoints.ts";
|
import { AuthError, getClipById, getVideoBlob } from "../utils/endpoints.ts";
|
||||||
import Box from "../components/Box.tsx";
|
import Box from "../components/Box.tsx";
|
||||||
import { dateToTimeAgo, formatTime, stringToDate } from "../utils/utils.ts";
|
import { dateToTimeAgo, formatTime, stringToDate } from "../utils/utils.ts";
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ const VideoPlayer = () => {
|
|||||||
// Data
|
// Data
|
||||||
const [videoUrl, setVideoUrl] = useState<string | undefined>(undefined);
|
const [videoUrl, setVideoUrl] = useState<string | undefined>(undefined);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [notAuthenticated, setNotAuthenticated] = useState(false);
|
||||||
const [clip, setClip] = useState<Clip | null>(null);
|
const [clip, setClip] = useState<Clip | null>(null);
|
||||||
const [timeAgo, setTimeAgo] = useState("");
|
const [timeAgo, setTimeAgo] = useState("");
|
||||||
|
|
||||||
@@ -41,11 +42,16 @@ const VideoPlayer = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) { setError("Clip ID is required."); return; }
|
if (!id) { setError("Clip ID is required."); return; }
|
||||||
|
|
||||||
getVideoBlob(id).then((blob) => setVideoUrl(URL.createObjectURL(blob)));
|
getVideoBlob(id)
|
||||||
|
.then((blob) => setVideoUrl(URL.createObjectURL(blob)))
|
||||||
|
.catch((e) => { if (e instanceof AuthError) setNotAuthenticated(true); });
|
||||||
|
|
||||||
getClipById(id)
|
getClipById(id)
|
||||||
.then(setClip)
|
.then(setClip)
|
||||||
.catch(() => setError("Failed to load clip details."));
|
.catch((e) => {
|
||||||
|
if (e instanceof AuthError) setNotAuthenticated(true);
|
||||||
|
else setError("Failed to load clip details.");
|
||||||
|
});
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -177,6 +183,27 @@ const VideoPlayer = () => {
|
|||||||
const effectiveVolume = isMuted ? 0 : volume;
|
const effectiveVolume = isMuted ? 0 : volume;
|
||||||
const VolumeIcon = effectiveVolume === 0 ? VolumeX : effectiveVolume < 0.5 ? Volume1 : Volume2;
|
const VolumeIcon = effectiveVolume === 0 ? VolumeX : effectiveVolume < 0.5 ? Volume1 : Volume2;
|
||||||
|
|
||||||
|
if (notAuthenticated) {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col items-center justify-center gap-4 text-center p-8">
|
||||||
|
<div className="w-14 h-14 rounded-full bg-gray-100 flex items-center justify-center">
|
||||||
|
<Lock size={24} className="text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-semibold text-gray-900">Not authenticated</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">You don't have access to this clip.</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-900 transition-colors duration-150"
|
||||||
|
>
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
Go back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col p-4 gap-3">
|
<div className="h-full flex flex-col p-4 gap-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import type {VideoMetadata, APIResponse, User, Clip, ProgressResult } from "./ty
|
|||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL;
|
const API_URL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
export class AuthError extends Error {
|
||||||
|
constructor() { super("Not authenticated"); this.name = "AuthError"; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login function
|
* Login function
|
||||||
* @param GoogleToken - The Google token received from the frontend.
|
* @param GoogleToken - The Google token received from the frontend.
|
||||||
@@ -227,6 +231,7 @@ const getClipById = async (id: string): Promise<Clip | null> => {
|
|||||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {credentials: "include",});
|
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {credentials: "include",});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||||
throw new Error(`Failed to fetch clip: ${response.status}`);
|
throw new Error(`Failed to fetch clip: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +248,7 @@ const getVideoBlob = async(id: string): Promise<Blob> => {
|
|||||||
const response = await fetch(API_URL + `/api/v1/download/clip/${id}`, {credentials: "include",});
|
const response = await fetch(API_URL + `/api/v1/download/clip/${id}`, {credentials: "include",});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||||
throw new Error(`Failed to fetch video: ${id}: ${response.status}`)
|
throw new Error(`Failed to fetch video: ${id}: ${response.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user