diff --git a/packages/react/src/App.tsx b/packages/react/src/App.tsx index 089f5a39c..3a2bee33a 100644 --- a/packages/react/src/App.tsx +++ b/packages/react/src/App.tsx @@ -3,6 +3,7 @@ import { useThemeInit } from "./hooks/useTheme"; import { useSyncTFunction } from "./store/i18n"; import { Suspense, useEffect } from "react"; import { routes } from "./routes/router"; +import React from "react"; export function App() { useThemeInit(); diff --git a/packages/react/src/Kitchensink.tsx b/packages/react/src/Kitchensink.tsx index 6cc48aee8..64068f59e 100644 --- a/packages/react/src/Kitchensink.tsx +++ b/packages/react/src/Kitchensink.tsx @@ -8,6 +8,7 @@ import { Checkbox } from "./shadcn/ui/checkbox"; export function Kitchensink() { const [count, setCount] = useState(0); + if (count) throw new Error("Test error"); return (

This page is for testing components and styling.

@@ -27,7 +28,9 @@ export function Kitchensink() {

Color and variants:

- + diff --git a/packages/react/src/components/about/EmailForm.tsx b/packages/react/src/components/about/EmailForm.tsx index a0384bc42..f70f29eb8 100644 --- a/packages/react/src/components/about/EmailForm.tsx +++ b/packages/react/src/components/about/EmailForm.tsx @@ -1,4 +1,4 @@ -import { useReportMutation } from "@/services/reports.service"; +import { useContactReportMutation } from "@/services/reports.service"; import { Button } from "@/shadcn/ui/button"; import { Form, @@ -42,7 +42,7 @@ export function AboutFaqEmailForm() { }, }); - const { mutate } = useReportMutation({ type: "contact" }); + const { mutate } = useContactReportMutation(); return (
diff --git a/packages/react/src/components/channel/ChannelPicker.tsx b/packages/react/src/components/channel/ChannelPicker.tsx index 6b25b4716..e6a8cd8ee 100644 --- a/packages/react/src/components/channel/ChannelPicker.tsx +++ b/packages/react/src/components/channel/ChannelPicker.tsx @@ -22,6 +22,12 @@ import { } from "react-hook-form"; import { useTranslation } from "react-i18next"; +const { currentValueAtom, debouncedValueAtom } = atomWithDebounce( + "", + 300, + true, +); + interface VtuberPickerProps< T extends FieldValues, FieldName extends FieldPath, @@ -32,12 +38,6 @@ interface VtuberPickerProps< onSelect: (value: SearchAutoCompleteChannel) => void; } -const { currentValueAtom, debouncedValueAtom } = atomWithDebounce( - "", - 300, - true, -); - export function ChannelPicker< T extends FieldValues, FieldName extends FieldPath, @@ -59,8 +59,7 @@ export function ChannelPicker< - + +
+ + - {t("component.apiError.logoutAndClearCache")} - -
- - {error?.message} - - - {error?.stack} - - - + + Debug Information +
+ + + + {error?.message} + + + {error?.stack} + + + + +
+ +
+ + + + © Holodex + +
); } diff --git a/packages/react/src/components/common/TwitterFeed.tsx b/packages/react/src/components/common/TwitterFeed.tsx index 2458bc7d3..822989f47 100644 --- a/packages/react/src/components/common/TwitterFeed.tsx +++ b/packages/react/src/components/common/TwitterFeed.tsx @@ -1,4 +1,11 @@ -import { DetailedHTMLProps, HTMLAttributes, useEffect, useRef } from "react"; +import { cn } from "@/lib/utils"; +import { + DetailedHTMLProps, + HTMLAttributes, + useEffect, + useRef, + useState, +} from "react"; import { useScript } from "usehooks-ts"; declare global { @@ -6,17 +13,13 @@ declare global { var twttr: any; } -const html = ``; - export function TwitterFeed( props: DetailedHTMLProps, HTMLDivElement>, ) { const ref = useRef(null); const status = useScript("https://platform.twitter.com/widgets.js"); + const html = ``; useEffect(() => { if (status === "ready") window.twttr?.widgets.load(); }, [status]); @@ -25,3 +28,67 @@ export function TwitterFeed(
); } + +export function StatusTweetEmbed({ + ...props +}: DetailedHTMLProps, HTMLDivElement>) { + const ref = useRef(null); + const [tweetUrl, setTweetUrl] = useState(null); + const [error, setError] = useState(null); + const status = useScript("https://platform.twitter.com/widgets.js"); + + // Fetch the latest status + useEffect(() => { + fetch("https://ext.holodex.net/api/status") + .then((res) => res.text()) + .then((url) => setTweetUrl(url.trim())) + .catch((err) => setError("Failed to load status")); + }, []); + + // Create tweet embed when both tweet URL and Twitter script are ready + useEffect(() => { + if (status === "ready" && tweetUrl && window.twttr && ref.current) { + // Clear previous content + ref.current.innerHTML = ""; + + window.twttr.widgets + .createTweet( + // Extract tweet ID from URL + tweetUrl.split("/").pop()!, + ref.current, + { + theme: "light", + width: 550, + align: "center", + conversation: "none", // Hide replies + }, + ) + .catch(() => setError("Failed to load tweet")); + } + }, [status, tweetUrl]); + + if (error) { + return ( + + ); + } + + // if (!tweetUrl || status !== "ready") { + // return ( + //
+ // Loading status... + //
+ // ); + // } + + return ( +
+ Loading... +
+ ); +} diff --git a/packages/react/src/components/layout/Frame.tsx b/packages/react/src/components/layout/Frame.tsx index 98aed96f0..c8402fcd2 100644 --- a/packages/react/src/components/layout/Frame.tsx +++ b/packages/react/src/components/layout/Frame.tsx @@ -15,7 +15,7 @@ import { Toaster } from "@/shadcn/ui/toaster"; import { orgAtom } from "@/store/org"; import { miniPlayerAtom } from "@/store/player"; import clsx from "clsx"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { Suspense, useEffect } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; @@ -25,6 +25,8 @@ import { MiniPlayer } from "../player/MiniPlayer"; import { Footer } from "./Footer"; import SelectionFooter from "./SelectionFooter"; import { selectionModeAtom } from "@/hooks/useVideoSelection"; +import { videoReportAtom } from "@/store/video"; +import React from "react"; export function LocationAwareReactivity() { const location = useLocation(); @@ -51,6 +53,8 @@ export function LocationAwareReactivity() { return <>; } +const LazyVideoReportDialog = React.lazy(() => import("../video/VideoReport")); + export function Frame() { console.log("rerendered frame!"); const resize = useSetAtom(onResizeAtom); @@ -79,6 +83,8 @@ export function Frame() { return () => window.removeEventListener("resize", resize); }, []); + const [reportedVideo, setReportedVideo] = useAtom(videoReportAtom); + return (
@@ -92,6 +98,14 @@ export function Frame() { + {reportedVideo && ( + setReportedVideo(null)} + video={reportedVideo} + /> + )} + {isMobile &&