From bb1918f28b09f000bfdd91ee6f82b3c399828774 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Wed, 6 May 2026 20:11:20 +0200 Subject: [PATCH 1/6] fix: show progress on history cards --- apps/web/src/components/continue-card.tsx | 6 ++--- apps/web/src/components/history-card.tsx | 2 ++ .../web/src/components/video-progress-bar.tsx | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/components/video-progress-bar.tsx diff --git a/apps/web/src/components/continue-card.tsx b/apps/web/src/components/continue-card.tsx index ab94928..89eb502 100644 --- a/apps/web/src/components/continue-card.tsx +++ b/apps/web/src/components/continue-card.tsx @@ -2,13 +2,13 @@ import { Link } from "@tanstack/react-router"; import { useWatchPrefetch } from "../hooks/use-watch-prefetch"; import { formatDuration } from "../lib/format"; import type { HistoryItem } from "../types/user"; +import { VideoProgressBar } from "./video-progress-bar"; type ContinueCardProps = { item: HistoryItem; }; export function ContinueCard({ item }: ContinueCardProps) { - const pct = Math.min(100, Math.round((item.progress / item.duration) * 100)); const prefetch = useWatchPrefetch(item.url); return ( @@ -30,9 +30,7 @@ export function ContinueCard({ item }: ContinueCardProps) { {formatDuration(item.duration)} -
-
-
+
diff --git a/apps/web/src/components/history-card.tsx b/apps/web/src/components/history-card.tsx index e1941bc..ec188d8 100644 --- a/apps/web/src/components/history-card.tsx +++ b/apps/web/src/components/history-card.tsx @@ -4,6 +4,7 @@ import { formatDuration } from "../lib/format"; import { proxyImage } from "../lib/proxy"; import type { HistoryItem } from "../types/user"; import { HistoryChannelAvatar } from "./history-channel-avatar"; +import { VideoProgressBar } from "./video-progress-bar"; function XIcon() { return ( @@ -67,6 +68,7 @@ export function HistoryCard({ item, onRemove, index }: HistoryCardProps) { {formatDuration(item.duration)} )} + - )} - {onboardingMode && ( - - )} -
- -
- - {!lastStep && ( - - )} - {lastStep && ( - - )} -
- - ); -} diff --git a/apps/web/src/components/onboarding-channel-option-row.tsx b/apps/web/src/components/onboarding-channel-option-row.tsx deleted file mode 100644 index 7988e5c..0000000 --- a/apps/web/src/components/onboarding-channel-option-row.tsx +++ /dev/null @@ -1,52 +0,0 @@ -type Props = { - name: string; - avatarUrl: string; - fallbackLabel: string; - active: boolean; - subtitle?: string; - onClick: () => void; -}; - -function initialFromLabel(value: string): string { - const text = value.trim(); - if (!text) return "?"; - return text[0]?.toUpperCase() ?? "?"; -} - -export function OnboardingChannelOptionRow({ - name, - avatarUrl, - fallbackLabel, - active, - subtitle, - onClick, -}: Props) { - return ( - - ); -} diff --git a/apps/web/src/components/onboarding-channel-search-block.tsx b/apps/web/src/components/onboarding-channel-search-block.tsx deleted file mode 100644 index 9869b75..0000000 --- a/apps/web/src/components/onboarding-channel-search-block.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { OnboardingChannelCandidate } from "../hooks/use-onboarding-channel-search"; -import { OnboardingChannelOptionRow } from "./onboarding-channel-option-row"; - -type Props = { - query: string; - loading: boolean; - resolving: boolean; - channels: OnboardingChannelCandidate[]; - selectedKeys: Set; - onChangeQuery: (value: string) => void; - onSelect: (channel: OnboardingChannelCandidate) => void; -}; - -export function OnboardingChannelSearchBlock({ - query, - loading, - resolving, - channels, - selectedKeys, - onChangeQuery, - onSelect, -}: Props) { - return ( -
-

Find channels quickly

- onChangeQuery(e.target.value)} - placeholder="Search a YouTube channel" - className="mt-2 h-11 sm:h-10 w-full rounded-lg border border-border-strong bg-app px-3 text-sm text-fg" - /> - {loading &&

Searching channels...

} - {channels.length > 0 && ( -
- {channels.map((channel, index) => ( -
- onSelect(channel)} - /> -
- ))} -
- )} - {resolving &&

Resolving avatars...

} -
- ); -} diff --git a/apps/web/src/components/onboarding-channel-selected-list.tsx b/apps/web/src/components/onboarding-channel-selected-list.tsx deleted file mode 100644 index 89f5172..0000000 --- a/apps/web/src/components/onboarding-channel-selected-list.tsx +++ /dev/null @@ -1,26 +0,0 @@ -type Props = { - channels: string[]; - onRemove: (channel: string) => void; -}; - -export function OnboardingChannelSelectedList({ channels, onRemove }: Props) { - if (channels.length === 0) return null; - - return ( -
-

Selected channels

-
- {channels.map((channel) => ( - - ))} -
-
- ); -} diff --git a/apps/web/src/components/onboarding-channel-suggested-block.tsx b/apps/web/src/components/onboarding-channel-suggested-block.tsx deleted file mode 100644 index dea00e8..0000000 --- a/apps/web/src/components/onboarding-channel-suggested-block.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { SubscriptionItem } from "../types/user"; -import { OnboardingChannelOptionRow } from "./onboarding-channel-option-row"; - -type Props = { - channels: SubscriptionItem[]; - loading: boolean; - resolving: boolean; - selectedKeys: Set; - onSelect: (channelUrl: string) => void; -}; - -export function OnboardingChannelSuggestedBlock({ - channels, - loading, - resolving, - selectedKeys, - onSelect, -}: Props) { - return ( -
-

- Suggested from subscriptions -

- {loading &&

Loading suggestions...

} - {!loading && channels.length === 0 && ( -

No subscriptions available yet.

- )} - {channels.length > 0 && ( -
- {channels.map((channel, index) => ( -
- onSelect(channel.channelUrl)} - /> -
- ))} -
- )} - {resolving &&

Resolving avatars...

} -
- ); -} diff --git a/apps/web/src/components/onboarding-channels-step.tsx b/apps/web/src/components/onboarding-channels-step.tsx deleted file mode 100644 index 41abb88..0000000 --- a/apps/web/src/components/onboarding-channels-step.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { OnboardingChannelCandidate } from "../hooks/use-onboarding-channel-search"; -import type { SubscriptionItem } from "../types/user"; -import { OnboardingChannelSearchBlock } from "./onboarding-channel-search-block"; -import { OnboardingChannelSelectedList } from "./onboarding-channel-selected-list"; -import { OnboardingChannelSuggestedBlock } from "./onboarding-channel-suggested-block"; - -type Props = { - channelQuery: string; - selectedChannels: string[]; - suggestedChannels: SubscriptionItem[]; - searchedChannels: OnboardingChannelCandidate[]; - suggestionsLoading: boolean; - searchLoading: boolean; - avatarResolving: boolean; - onChangeQuery: (value: string) => void; - onRemoveChannel: (channel: string) => void; - onSelectSuggestion: (channelUrl: string) => void; - onSelectSearched: (channel: OnboardingChannelCandidate) => void; -}; - -export function OnboardingChannelsStep({ - channelQuery, - selectedChannels, - suggestedChannels, - searchedChannels, - suggestionsLoading, - searchLoading, - avatarResolving, - onChangeQuery, - onRemoveChannel, - onSelectSuggestion, - onSelectSearched, -}: Props) { - const selectedKeys = new Set(selectedChannels.map((channel) => channel.toLowerCase())); - - return ( -
-
-

- Step 2 - Favorite channels -

-

Add channels you trust

-

- We use this to stabilize your first recommendations before history takes over. -

-
- - - - - - -
- ); -} diff --git a/apps/web/src/components/onboarding-error.tsx b/apps/web/src/components/onboarding-error.tsx deleted file mode 100644 index bdb020a..0000000 --- a/apps/web/src/components/onboarding-error.tsx +++ /dev/null @@ -1,22 +0,0 @@ -type Props = { - onRetry: () => void; -}; - -export function OnboardingError({ onRetry }: Props) { - return ( -
-

Onboarding

-

Unable to load recommendations setup

-

- We could not load onboarding data from the server. Retry to continue. -

- -
- ); -} diff --git a/apps/web/src/components/onboarding-header.tsx b/apps/web/src/components/onboarding-header.tsx deleted file mode 100644 index f4ed496..0000000 --- a/apps/web/src/components/onboarding-header.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { OnboardingProgress } from "./onboarding-progress"; - -type Props = { - mode: "onboarding" | "settings"; - step: number; -}; - -export function OnboardingHeader({ mode, step }: Props) { - const onboardingMode = mode === "onboarding"; - - return ( -
-

First login

-

- {onboardingMode ? "Tune your recommendations" : "Recommendation profile"} -

-

- {onboardingMode - ? "Quick setup in two steps. You can skip now and fine tune later from settings." - : "Adjust your interests and channels. Changes apply immediately to recommendation seeding."} -

-
- -
-
- ); -} diff --git a/apps/web/src/components/onboarding-interests-step.tsx b/apps/web/src/components/onboarding-interests-step.tsx deleted file mode 100644 index c752f8b..0000000 --- a/apps/web/src/components/onboarding-interests-step.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type { RecommendationOnboardingTopicsResponse } from "../types/api"; - -type Props = { - topics: RecommendationOnboardingTopicsResponse | undefined; - selectedTopics: string[]; - activeGroupId: string | null; - onSelectGroup: (groupId: string) => void; - onToggleTopic: (topic: string) => void; -}; - -export function OnboardingInterestsStep({ - topics, - selectedTopics, - activeGroupId, - onSelectGroup, - onToggleTopic, -}: Props) { - const minTopics = topics?.minTopics ?? 3; - const selectedTopicKeys = new Set(selectedTopics.map((topic) => topic.toLowerCase())); - const activeGroup = - topics?.groups.find((group) => group.id === activeGroupId) ?? topics?.groups[0]; - - return ( -
-
-

- Step 1 - Interests -

-

What should we prioritize?

-

- Pick at least {minTopics} topics. You can change this later in settings. -

-
- -
-

Selected topics

-

- {selectedTopics.length} - {` / ${minTopics} minimum`} -

-
- -
- {topics?.groups.map((group) => { - const selected = group.id === activeGroup?.id; - return ( - - ); - })} -
- - {activeGroup && ( -
- {activeGroup.topics.map((topic) => { - const selected = selectedTopicKeys.has(topic.toLowerCase()); - return ( - - ); - })} -
- )} -
- ); -} diff --git a/apps/web/src/components/onboarding-loading.tsx b/apps/web/src/components/onboarding-loading.tsx deleted file mode 100644 index 2b53e90..0000000 --- a/apps/web/src/components/onboarding-loading.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export function OnboardingLoading() { - return ( -
-
-
-
-
- ); -} diff --git a/apps/web/src/components/onboarding-progress.tsx b/apps/web/src/components/onboarding-progress.tsx deleted file mode 100644 index 7d3cbb2..0000000 --- a/apps/web/src/components/onboarding-progress.tsx +++ /dev/null @@ -1,26 +0,0 @@ -type Props = { - currentStep: number; -}; - -const STEPS = ["Interests", "Channels"]; - -export function OnboardingProgress({ currentStep }: Props) { - return ( -
- {STEPS.map((label, index) => { - const active = index === currentStep; - const done = index < currentStep; - return ( -
-
- - {label} - -
- ); - })} -
- ); -} diff --git a/apps/web/src/components/onboarding-workflow.tsx b/apps/web/src/components/onboarding-workflow.tsx deleted file mode 100644 index a8f4d43..0000000 --- a/apps/web/src/components/onboarding-workflow.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useOnboardingWorkflow } from "../hooks/use-onboarding-workflow"; -import type { useRecommendationOnboardingActions } from "../hooks/use-recommendation-onboarding"; -import type { - RecommendationOnboardingStateResponse, - RecommendationOnboardingTopicsResponse, -} from "../types/api"; -import { OnboardingActions } from "./onboarding-actions"; -import { OnboardingChannelsStep } from "./onboarding-channels-step"; -import { OnboardingHeader } from "./onboarding-header"; -import { OnboardingInterestsStep } from "./onboarding-interests-step"; -import { Toast } from "./toast"; - -type Props = { - state: RecommendationOnboardingStateResponse | undefined; - topics: RecommendationOnboardingTopicsResponse | undefined; - actions: ReturnType; - mode?: "onboarding" | "settings"; -}; - -export function OnboardingWorkflow({ state, topics, actions, mode = "onboarding" }: Props) { - const flow = useOnboardingWorkflow({ state, topics, actions }); - - return ( -
- - - - {flow.step === 0 && ( -
- -
- )} - - {flow.step === 1 && ( -
- -
- )} - - {flow.error &&

{flow.error}

} - - flow.setStep((current) => Math.max(0, current - 1))} - onSkip={() => void flow.skip()} - onSave={() => void flow.saveAndReapply()} - onNext={() => flow.setStep(1)} - onFinish={() => void flow.complete()} - /> -
- ); -} diff --git a/apps/web/src/components/recommendation-settings-shell.tsx b/apps/web/src/components/recommendation-settings-shell.tsx deleted file mode 100644 index e38803c..0000000 --- a/apps/web/src/components/recommendation-settings-shell.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - useRecommendationOnboardingActions, - useRecommendationOnboardingState, - useRecommendationOnboardingTopics, -} from "../hooks/use-recommendation-onboarding"; -import { OnboardingError } from "./onboarding-error"; -import { OnboardingLoading } from "./onboarding-loading"; -import { OnboardingWorkflow } from "./onboarding-workflow"; - -type Props = { - mode?: "onboarding" | "settings"; -}; - -export function RecommendationSettingsShell({ mode = "onboarding" }: Props) { - const stateQuery = useRecommendationOnboardingState(true); - const topicsQuery = useRecommendationOnboardingTopics(true); - const actions = useRecommendationOnboardingActions(); - - if (stateQuery.isPending || topicsQuery.isPending) return ; - - if (stateQuery.isError || topicsQuery.isError) { - return ( - { - void stateQuery.refetch(); - void topicsQuery.refetch(); - }} - /> - ); - } - - return ( - - ); -} diff --git a/apps/web/src/components/shorts-actions.tsx b/apps/web/src/components/shorts-actions.tsx index cca73b1..48a6a94 100644 --- a/apps/web/src/components/shorts-actions.tsx +++ b/apps/web/src/components/shorts-actions.tsx @@ -1,18 +1,10 @@ -import { Ban, Clock3, MessageCircle, Share2, Star, UserMinus } from "lucide-react"; -import { useEffect, useState } from "react"; +import { Clock3, MessageCircle, Share2, Star } from "lucide-react"; import { useAuth } from "../hooks/use-auth"; import { useFavoritesPlaylist } from "../hooks/use-favorites-playlist"; -import { useSettings } from "../hooks/use-settings"; import { useShareUrl } from "../hooks/use-share-url"; import { useWatchLaterPlaylist } from "../hooks/use-watch-later-playlist"; -import { - sendRecommendationFeedback, - trackRecommendationEvent, -} from "../lib/recommendation-tracker"; -import { useShortsFeedbackStore } from "../stores/shorts-feedback-store"; import type { VideoStream } from "../types/stream"; import { ShortsActionButton } from "./shorts-action-button"; -import { Toast } from "./toast"; type Props = { stream: VideoStream; @@ -23,12 +15,7 @@ type Props = { export function ShortsActions({ stream, onOpenComments, className, compact }: Props) { const { isAuthed } = useAuth(); - const { settings } = useSettings(); - const shortsIntent = "auto" as const; const { copied, share } = useShareUrl(); - const [toastMsg, setToastMsg] = useState(null); - const hideVideo = useShortsFeedbackStore((s) => s.hideVideo); - const hideChannel = useShortsFeedbackStore((s) => s.hideChannel); const { add: addFavorite, remove: removeFavorite, @@ -45,12 +32,6 @@ export function ShortsActions({ stream, onOpenComments, className, compact }: Pr const favorited = isInFavorites(stream.id); const watchLater = isInWatchLater(stream.id); - useEffect(() => { - if (!toastMsg) return; - const timer = setTimeout(() => setToastMsg(null), 1800); - return () => clearTimeout(timer); - }, [toastMsg]); - function requireAuth(): boolean { if (isAuthed) return true; const redirect = `/shorts?v=${encodeURIComponent(stream.id)}`; @@ -70,10 +51,6 @@ export function ShortsActions({ stream, onOpenComments, className, compact }: Pr thumbnail: stream.thumbnail, duration: stream.duration, }); - trackRecommendationEvent("favorite", stream, { - serviceId: settings.defaultService, - intent: shortsIntent, - }); } async function toggleWatchLater() { @@ -88,10 +65,6 @@ export function ShortsActions({ stream, onOpenComments, className, compact }: Pr thumbnail: stream.thumbnail, duration: stream.duration, }); - trackRecommendationEvent("watch_later_add", stream, { - serviceId: settings.defaultService, - intent: shortsIntent, - }); } function handleShare() { @@ -99,19 +72,6 @@ export function ShortsActions({ stream, onOpenComments, className, compact }: Pr void share(watchUrl); } - function markNotInterested() { - sendRecommendationFeedback("not_interested", stream); - hideVideo(stream.id); - setToastMsg("Understood. We will fine-tune your Shorts."); - } - - function showLessFromChannel() { - if (!stream.channelUrl) return; - sendRecommendationFeedback("less_from_channel", stream); - hideChannel(stream.channelUrl); - setToastMsg("Okay. We will show less from this channel."); - } - return (
void toggleWatchLater()} /> - - -
); } diff --git a/apps/web/src/components/shorts-player-shell.tsx b/apps/web/src/components/shorts-player-shell.tsx index b0de8f3..17eea99 100644 --- a/apps/web/src/components/shorts-player-shell.tsx +++ b/apps/web/src/components/shorts-player-shell.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { ShortsPlayerStage } from "../components/shorts-player-stage"; import { ShortsShellLoader } from "../components/shorts-shell-loader"; import { useMobile } from "../hooks/use-mobile"; @@ -13,11 +13,8 @@ import { getOriginalAudioTrackId, getPreferredDefaultAudioTrackId, } from "../lib/audio-track"; -import { trackRecommendationEvent } from "../lib/recommendation-tracker"; import { useShortsNavigation } from "../lib/shorts-navigation"; -import { trackShortsAutoAdvance, trackShortsUserMove } from "../lib/shorts-tracking"; import { useUiStore } from "../stores/ui-store"; -import type { VideoStream } from "../types/stream"; type Props = { targetUrl?: string; @@ -27,30 +24,13 @@ export function ShortsPlayerShell({ targetUrl }: Props) { const isMobile = useMobile(); const { shorts, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } = useShortsFeed(); const { settings, update, query: settingsQuery } = useSettings(); - const shortsIntent = "auto" as const; const sidebarCollapsed = useUiStore((s) => s.sidebarCollapsed); const playerRef = useRef(null); - const activeEnteredAtRef = useRef(Date.now()); - const activeStreamRef = useRef(null); const [commentsOpen, setCommentsOpen] = useState(false); const settingsReady = (settingsQuery.isSuccess && !settingsQuery.isPlaceholderData) || settingsQuery.isError; - const onMove = (delta: number, reason: "user" | "auto") => { - const stream = activeStreamRef.current; - if (reason !== "user" || delta === 0 || !stream) return; - trackShortsUserMove(stream, activeEnteredAtRef.current, settings.defaultService, shortsIntent); - }; - const handleAutoNext = () => { - const stream = activeStreamRef.current; - if (stream) - trackShortsAutoAdvance( - stream, - activeEnteredAtRef.current, - settings.defaultService, - shortsIntent, - ); moveBy(1, "auto"); }; const { index, moveBy, moveTo, onWheel, onTouchStart, onTouchEnd } = useShortsNavigation( @@ -58,22 +38,9 @@ export function ShortsPlayerShell({ targetUrl }: Props) { hasNextPage, isFetchingNextPage, fetchNextPage, - onMove, ); const { active, activeId, stream, streamQuery, current, errorMessage, isMemberOnlyShort } = useShortsActiveStream({ shorts, index }); - useEffect(() => { - if (!active) { - activeStreamRef.current = null; - return; - } - activeStreamRef.current = active; - activeEnteredAtRef.current = Date.now(); - trackRecommendationEvent("impression", active, { - serviceId: settings.defaultService, - intent: shortsIntent, - }); - }, [active, settings.defaultService, shortsIntent]); const originalAudioTrackId = getOriginalAudioTrackId(stream); const preferredDefaultAudioTrackId = getPreferredDefaultAudioTrackId(stream); const originalAudioLocale = getOriginalAudioLocale(stream); diff --git a/apps/web/src/components/recommendation-feedback-dropdown.tsx b/apps/web/src/components/video-block-actions-dropdown.tsx similarity index 77% rename from apps/web/src/components/recommendation-feedback-dropdown.tsx rename to apps/web/src/components/video-block-actions-dropdown.tsx index be3e63b..aeb51dc 100644 --- a/apps/web/src/components/recommendation-feedback-dropdown.tsx +++ b/apps/web/src/components/video-block-actions-dropdown.tsx @@ -1,27 +1,20 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; -import type { VideoStream } from "../types/stream"; const MARGIN = 8; type Props = { - stream: VideoStream; anchorEl: HTMLElement | null; onClose: () => void; - onNotInterested: () => void; - onLessFromChannel?: () => void; onToggleVideoBlock?: () => void; onToggleChannelBlock?: () => void; videoBlocked?: boolean; channelBlocked?: boolean; }; -export function RecommendationFeedbackDropdown({ - stream, +export function VideoBlockActionsDropdown({ anchorEl, onClose, - onNotInterested, - onLessFromChannel, onToggleVideoBlock, onToggleChannelBlock, videoBlocked, @@ -73,27 +66,6 @@ export function RecommendationFeedbackDropdown({ style={panelStyle} className="fixed z-50 w-56 overflow-hidden rounded-lg border border-border-strong bg-surface shadow-2xl" > - - {onToggleVideoBlock && ( diff --git a/apps/web/src/components/video-card-feedback-panel.tsx b/apps/web/src/components/video-card-feedback-panel.tsx index 06477ff..1d7ae06 100644 --- a/apps/web/src/components/video-card-feedback-panel.tsx +++ b/apps/web/src/components/video-card-feedback-panel.tsx @@ -1,9 +1,8 @@ import { useAuth } from "../hooks/use-auth"; import { useBlocked } from "../hooks/use-blocked"; -import { sendRecommendationFeedback } from "../lib/recommendation-tracker"; import { goto } from "../lib/route-redirect"; import type { VideoStream } from "../types/stream"; -import { RecommendationFeedbackDropdown } from "./recommendation-feedback-dropdown"; +import { VideoBlockActionsDropdown } from "./video-block-actions-dropdown"; type Props = { stream: VideoStream; @@ -49,25 +48,9 @@ export function VideoCardFeedbackPanel({ stream, anchorEl, onClose }: Props) { } return ( - - sendRecommendationFeedback("not_interested", { - id: stream.id, - channelUrl: stream.channelUrl, - }) - } - onLessFromChannel={ - stream.channelUrl - ? () => - sendRecommendationFeedback("less_from_channel", { - id: stream.id, - channelUrl: stream.channelUrl, - }) - : undefined - } onToggleVideoBlock={toggleVideoBlock} onToggleChannelBlock={stream.channelUrl ? toggleChannelBlock : undefined} videoBlocked={videoBlocked} diff --git a/apps/web/src/components/watch-more-actions.tsx b/apps/web/src/components/watch-more-actions.tsx index 38ebbdd..31ef727 100644 --- a/apps/web/src/components/watch-more-actions.tsx +++ b/apps/web/src/components/watch-more-actions.tsx @@ -1,9 +1,8 @@ import { useRef, useState } from "react"; import { useBlocked } from "../hooks/use-blocked"; -import { sendRecommendationFeedback } from "../lib/recommendation-tracker"; import { goto } from "../lib/route-redirect"; import type { VideoStream } from "../types/stream"; -import { RecommendationFeedbackDropdown } from "./recommendation-feedback-dropdown"; +import { VideoBlockActionsDropdown } from "./video-block-actions-dropdown"; import { MoreIcon } from "./watch-icons"; type Props = { @@ -55,20 +54,6 @@ export function WatchMoreActions({ stream, isAuthed, onSaved, className }: Props onSaved(`Channel blocked: ${stream.channelName}`); } - function handleNotInterested() { - sendRecommendationFeedback("not_interested", { id: stream.id, channelUrl: stream.channelUrl }); - onSaved("We'll show fewer videos like this"); - } - - function handleLessFromChannel() { - if (!stream.channelUrl) return; - sendRecommendationFeedback("less_from_channel", { - id: stream.id, - channelUrl: stream.channelUrl, - }); - onSaved(`We'll show less from ${stream.channelName}`); - } - return ( <> - Enabled -
-
- -
-

What is tracked

+

Stored data

    -
  • Event type: impression, click, watch, short_skip.
  • -
  • Video metadata: videoUrl, uploaderUrl, title.
  • -
  • Optional watch details: watchRatio, watchDurationMs.
  • -
  • Server timestamp: occurredAt.
  • -
  • Recommendation context, for example intent or context key.
  • +
  • Watch history.
  • +
  • Search history.
  • +
  • Subscriptions.
  • +
  • Playback progress.
-

What is sent

+

Controls

    -
  • eventType
  • -
  • videoUrl, uploaderUrl, title
  • -
  • Optional: watchRatio, watchDurationMs
  • -
  • Optional context hints: serviceId, intent, contextKey
  • -
  • Home uses intent and currently defaults to intent=auto.
  • +
  • Clear watch history in Settings.
  • +
  • Clear search history in Settings.
  • +
  • Unsubscribe from all channels in Settings.
diff --git a/apps/web/src/routes/settings.tsx b/apps/web/src/routes/settings.tsx index bff5dcc..454ea28 100644 --- a/apps/web/src/routes/settings.tsx +++ b/apps/web/src/routes/settings.tsx @@ -16,24 +16,6 @@ function SettingsPage() { {settings.defaultService === 0 && } -
-

- Recommendations -

-
-
- Tune recommendation profile - Adjust interests and favorite channels. -
- -
-

Migration

diff --git a/apps/web/src/routes/settings/recommendations.tsx b/apps/web/src/routes/settings/recommendations.tsx deleted file mode 100644 index 46336f7..0000000 --- a/apps/web/src/routes/settings/recommendations.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { RecommendationSettingsShell } from "../../components/recommendation-settings-shell"; - -function RecommendationsSettingsPage() { - return ; -} - -export const Route = createFileRoute("/settings/recommendations")({ - component: RecommendationsSettingsPage, -}); diff --git a/apps/web/src/stores/recommendation-tracking-store.ts b/apps/web/src/stores/recommendation-tracking-store.ts deleted file mode 100644 index 052f24c..0000000 --- a/apps/web/src/stores/recommendation-tracking-store.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { create } from "zustand"; -import { persist } from "zustand/middleware"; - -type State = { - enabled: boolean; - setEnabled: (enabled: boolean) => void; -}; - -export const useRecommendationTrackingStore = create()( - persist( - (set) => ({ - enabled: true, - setEnabled: (enabled) => set({ enabled }), - }), - { name: "recommendation-tracking" }, - ), -); diff --git a/apps/web/src/stores/shorts-feedback-store.ts b/apps/web/src/stores/shorts-feedback-store.ts deleted file mode 100644 index 3448486..0000000 --- a/apps/web/src/stores/shorts-feedback-store.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { create } from "zustand"; - -type ShortsFeedbackState = { - hiddenVideoIds: string[]; - hiddenChannelUrls: string[]; - hideVideo: (videoUrl: string) => void; - hideChannel: (channelUrl: string) => void; - reset: () => void; -}; - -export const useShortsFeedbackStore = create((set) => ({ - hiddenVideoIds: [], - hiddenChannelUrls: [], - hideVideo: (videoUrl) => - set((state) => { - if (state.hiddenVideoIds.includes(videoUrl)) return state; - return { hiddenVideoIds: [...state.hiddenVideoIds, videoUrl] }; - }), - hideChannel: (channelUrl) => - set((state) => { - if (state.hiddenChannelUrls.includes(channelUrl)) return state; - return { hiddenChannelUrls: [...state.hiddenChannelUrls, channelUrl] }; - }), - reset: () => set({ hiddenVideoIds: [], hiddenChannelUrls: [] }), -})); diff --git a/apps/web/src/types/api.ts b/apps/web/src/types/api.ts index a2afd61..88d9fa2 100644 --- a/apps/web/src/types/api.ts +++ b/apps/web/src/types/api.ts @@ -87,24 +87,6 @@ export type HomeRecommendationsResponse = { hasMore: boolean; }; -type RecommendationOnboardingTopicGroup = { - id: string; - label: string; - topics: string[]; -}; - -export type RecommendationOnboardingTopicsResponse = { - minTopics: number; - groups: RecommendationOnboardingTopicGroup[]; -}; - -export type RecommendationOnboardingStateResponse = { - requiresOnboarding: boolean; - completedAt: number | null; - selectedTopics: string[]; - selectedChannels: string[]; -}; - export type SubscriptionFeedPage = { videos: VideoItem[]; nextpage: string | null; diff --git a/apps/web/src/types/user.ts b/apps/web/src/types/user.ts index 441deb2..d24d3bd 100644 --- a/apps/web/src/types/user.ts +++ b/apps/web/src/types/user.ts @@ -53,7 +53,6 @@ export type SettingsItem = { defaultSubtitleLanguage: string; defaultAudioLanguage: string; preferOriginalLanguage: boolean; - recommendationPersonalizationEnabled: boolean; }; export type SearchHistoryItem = { From 4bfa9a426be4b96cf8a23603a70f358b74648398 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Wed, 6 May 2026 20:12:50 +0200 Subject: [PATCH 6/6] chore: update generated route tree --- apps/web/src/routeTree.gen.ts | 62 +++-------------------------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index ac0f2cf..d3008bd 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -19,7 +19,6 @@ import { Route as RegisterRouteImport } from './routes/register' import { Route as ProfileRouteImport } from './routes/profile' import { Route as PrivacyRouteImport } from './routes/privacy' import { Route as PlaylistsRouteImport } from './routes/playlists' -import { Route as OnboardingRouteImport } from './routes/onboarding' import { Route as LoginRouteImport } from './routes/login' import { Route as ImportRouteImport } from './routes/import' import { Route as HistoryRouteImport } from './routes/history' @@ -27,7 +26,6 @@ import { Route as ChannelRouteImport } from './routes/channel' import { Route as AdminConsoleRouteImport } from './routes/admin-console' import { Route as IndexRouteImport } from './routes/index' import { Route as ImportIndexRouteImport } from './routes/import/index' -import { Route as SettingsRecommendationsRouteImport } from './routes/settings/recommendations' import { Route as PlaylistsIdRouteImport } from './routes/playlists_.$id' import { Route as ImportYoutubeRouteImport } from './routes/import/youtube' import { Route as ImportPipepipeRouteImport } from './routes/import/pipepipe' @@ -82,11 +80,6 @@ const PlaylistsRoute = PlaylistsRouteImport.update({ path: '/playlists', getParentRoute: () => rootRouteImport, } as any) -const OnboardingRoute = OnboardingRouteImport.update({ - id: '/onboarding', - path: '/onboarding', - getParentRoute: () => rootRouteImport, -} as any) const LoginRoute = LoginRouteImport.update({ id: '/login', path: '/login', @@ -122,11 +115,6 @@ const ImportIndexRoute = ImportIndexRouteImport.update({ path: '/', getParentRoute: () => ImportRoute, } as any) -const SettingsRecommendationsRoute = SettingsRecommendationsRouteImport.update({ - id: '/recommendations', - path: '/recommendations', - getParentRoute: () => SettingsRoute, -} as any) const PlaylistsIdRoute = PlaylistsIdRouteImport.update({ id: '/playlists_/$id', path: '/playlists/$id', @@ -150,21 +138,19 @@ export interface FileRoutesByFullPath { '/history': typeof HistoryRoute '/import': typeof ImportRouteWithChildren '/login': typeof LoginRoute - '/onboarding': typeof OnboardingRoute '/playlists': typeof PlaylistsRoute '/privacy': typeof PrivacyRoute '/profile': typeof ProfileRoute '/register': typeof RegisterRoute '/reset-password': typeof ResetPasswordRoute '/search': typeof SearchRoute - '/settings': typeof SettingsRouteWithChildren + '/settings': typeof SettingsRoute '/shorts': typeof ShortsRoute '/subscriptions': typeof SubscriptionsRoute '/watch': typeof WatchRoute '/import/pipepipe': typeof ImportPipepipeRoute '/import/youtube': typeof ImportYoutubeRoute '/playlists/$id': typeof PlaylistsIdRoute - '/settings/recommendations': typeof SettingsRecommendationsRoute '/import/': typeof ImportIndexRoute } export interface FileRoutesByTo { @@ -173,21 +159,19 @@ export interface FileRoutesByTo { '/channel': typeof ChannelRoute '/history': typeof HistoryRoute '/login': typeof LoginRoute - '/onboarding': typeof OnboardingRoute '/playlists': typeof PlaylistsRoute '/privacy': typeof PrivacyRoute '/profile': typeof ProfileRoute '/register': typeof RegisterRoute '/reset-password': typeof ResetPasswordRoute '/search': typeof SearchRoute - '/settings': typeof SettingsRouteWithChildren + '/settings': typeof SettingsRoute '/shorts': typeof ShortsRoute '/subscriptions': typeof SubscriptionsRoute '/watch': typeof WatchRoute '/import/pipepipe': typeof ImportPipepipeRoute '/import/youtube': typeof ImportYoutubeRoute '/playlists/$id': typeof PlaylistsIdRoute - '/settings/recommendations': typeof SettingsRecommendationsRoute '/import': typeof ImportIndexRoute } export interface FileRoutesById { @@ -198,21 +182,19 @@ export interface FileRoutesById { '/history': typeof HistoryRoute '/import': typeof ImportRouteWithChildren '/login': typeof LoginRoute - '/onboarding': typeof OnboardingRoute '/playlists': typeof PlaylistsRoute '/privacy': typeof PrivacyRoute '/profile': typeof ProfileRoute '/register': typeof RegisterRoute '/reset-password': typeof ResetPasswordRoute '/search': typeof SearchRoute - '/settings': typeof SettingsRouteWithChildren + '/settings': typeof SettingsRoute '/shorts': typeof ShortsRoute '/subscriptions': typeof SubscriptionsRoute '/watch': typeof WatchRoute '/import/pipepipe': typeof ImportPipepipeRoute '/import/youtube': typeof ImportYoutubeRoute '/playlists_/$id': typeof PlaylistsIdRoute - '/settings/recommendations': typeof SettingsRecommendationsRoute '/import/': typeof ImportIndexRoute } export interface FileRouteTypes { @@ -224,7 +206,6 @@ export interface FileRouteTypes { | '/history' | '/import' | '/login' - | '/onboarding' | '/playlists' | '/privacy' | '/profile' @@ -238,7 +219,6 @@ export interface FileRouteTypes { | '/import/pipepipe' | '/import/youtube' | '/playlists/$id' - | '/settings/recommendations' | '/import/' fileRoutesByTo: FileRoutesByTo to: @@ -247,7 +227,6 @@ export interface FileRouteTypes { | '/channel' | '/history' | '/login' - | '/onboarding' | '/playlists' | '/privacy' | '/profile' @@ -261,7 +240,6 @@ export interface FileRouteTypes { | '/import/pipepipe' | '/import/youtube' | '/playlists/$id' - | '/settings/recommendations' | '/import' id: | '__root__' @@ -271,7 +249,6 @@ export interface FileRouteTypes { | '/history' | '/import' | '/login' - | '/onboarding' | '/playlists' | '/privacy' | '/profile' @@ -285,7 +262,6 @@ export interface FileRouteTypes { | '/import/pipepipe' | '/import/youtube' | '/playlists_/$id' - | '/settings/recommendations' | '/import/' fileRoutesById: FileRoutesById } @@ -296,14 +272,13 @@ export interface RootRouteChildren { HistoryRoute: typeof HistoryRoute ImportRoute: typeof ImportRouteWithChildren LoginRoute: typeof LoginRoute - OnboardingRoute: typeof OnboardingRoute PlaylistsRoute: typeof PlaylistsRoute PrivacyRoute: typeof PrivacyRoute ProfileRoute: typeof ProfileRoute RegisterRoute: typeof RegisterRoute ResetPasswordRoute: typeof ResetPasswordRoute SearchRoute: typeof SearchRoute - SettingsRoute: typeof SettingsRouteWithChildren + SettingsRoute: typeof SettingsRoute ShortsRoute: typeof ShortsRoute SubscriptionsRoute: typeof SubscriptionsRoute WatchRoute: typeof WatchRoute @@ -382,13 +357,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PlaylistsRouteImport parentRoute: typeof rootRouteImport } - '/onboarding': { - id: '/onboarding' - path: '/onboarding' - fullPath: '/onboarding' - preLoaderRoute: typeof OnboardingRouteImport - parentRoute: typeof rootRouteImport - } '/login': { id: '/login' path: '/login' @@ -438,13 +406,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ImportIndexRouteImport parentRoute: typeof ImportRoute } - '/settings/recommendations': { - id: '/settings/recommendations' - path: '/recommendations' - fullPath: '/settings/recommendations' - preLoaderRoute: typeof SettingsRecommendationsRouteImport - parentRoute: typeof SettingsRoute - } '/playlists_/$id': { id: '/playlists_/$id' path: '/playlists/$id' @@ -484,18 +445,6 @@ const ImportRouteChildren: ImportRouteChildren = { const ImportRouteWithChildren = ImportRoute._addFileChildren(ImportRouteChildren) -interface SettingsRouteChildren { - SettingsRecommendationsRoute: typeof SettingsRecommendationsRoute -} - -const SettingsRouteChildren: SettingsRouteChildren = { - SettingsRecommendationsRoute: SettingsRecommendationsRoute, -} - -const SettingsRouteWithChildren = SettingsRoute._addFileChildren( - SettingsRouteChildren, -) - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AdminConsoleRoute: AdminConsoleRoute, @@ -503,14 +452,13 @@ const rootRouteChildren: RootRouteChildren = { HistoryRoute: HistoryRoute, ImportRoute: ImportRouteWithChildren, LoginRoute: LoginRoute, - OnboardingRoute: OnboardingRoute, PlaylistsRoute: PlaylistsRoute, PrivacyRoute: PrivacyRoute, ProfileRoute: ProfileRoute, RegisterRoute: RegisterRoute, ResetPasswordRoute: ResetPasswordRoute, SearchRoute: SearchRoute, - SettingsRoute: SettingsRouteWithChildren, + SettingsRoute: SettingsRoute, ShortsRoute: ShortsRoute, SubscriptionsRoute: SubscriptionsRoute, WatchRoute: WatchRoute,