diff --git a/packages/react/src/components/topic/TopicPicker.tsx b/packages/react/src/components/topic/TopicPicker.tsx
new file mode 100644
index 000000000..89a574ec1
--- /dev/null
+++ b/packages/react/src/components/topic/TopicPicker.tsx
@@ -0,0 +1,87 @@
+import { useState, useEffect } from "react";
+import { ChevronsUpDown } from "lucide-react";
+import { Button } from "@/shadcn/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/shadcn/ui/command";
+import { Popover, PopoverTrigger, PopoverContent } from "@/shadcn/ui/popover";
+import { useTranslation } from "react-i18next";
+import { useAtom, useAtomValue } from "jotai/react";
+import { useSearchAutoCompleteMutation } from "@/services/search.service";
+import atomWithDebounce from "@/lib/atomWithDebounce";
+
+const { debouncedValueAtom, currentValueAtom } = atomWithDebounce("", 300);
+
+interface TopicPickerProps {
+ onSelect: (topicId: string) => void;
+}
+
+export function TopicPicker({ onSelect }: TopicPickerProps) {
+ const [deboundcedValue, setDebouncedValue] = useAtom(debouncedValueAtom);
+ const currentValue = useAtomValue(currentValueAtom);
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+
+ const { data, isPending, mutate } = useSearchAutoCompleteMutation();
+
+ useEffect(() => {
+ mutate({
+ q: deboundcedValue,
+ t: "topic",
+ n: 10,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [deboundcedValue]);
+
+ return (
+
+
+
+
+
+
+
+
+ {t("component.topicPicker.notFound")}
+
+ {data?.topic?.map(({ id }) => (
+ {
+ onSelect(topicId);
+ setOpen(false);
+ }}
+ >
+ {id}
+
+ ))}
+ {isPending && (
+
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/packages/react/src/locales/en/ui.yml b/packages/react/src/locales/en/ui.yml
index a7980ca31..64a473121 100644
--- a/packages/react/src/locales/en/ui.yml
+++ b/packages/react/src/locales/en/ui.yml
@@ -212,6 +212,10 @@ component:
pickDate: Pick a date...
toast:
copiedToClipboard: Copied to clipboard
+ topicPicker:
+ searchLabel: Search topics...
+ notFound: No topic found
+ pickLabel: Pick topics...
views:
channel:
video: Videos
@@ -323,9 +327,9 @@ views:
gridSizeLabel: Thumbnail/Grid Size
gridSizeMsg: Change thumbnail/video grid size on Home/Favorites page
gridSize:
- - Large
- - Medium
- - Small
+ - Grid
+ - List
+ - Dense List
hideCollabStreamsLabel: Hide Collab Streams
hideCollabStreamsMsg: Hide collab streams from your favorites feed
hidePlaceholderStreams: Hide Placeholder Streams
@@ -339,6 +343,10 @@ views:
languageSearch: Search language...
languageNotfound: No language found
userNotLinked: Not linked
+ defaultHomepage:
+ lastVisitedOrgHome: Last Visited Org Home
+ favoritesWhenLoggedIn: Favorites (When Logged In)
+ hideFeaturesLabel: Hide Features
app:
update_available: An update is available
update_btn: Update
diff --git a/packages/react/src/routes/router.tsx b/packages/react/src/routes/router.tsx
index 474167cee..3a975fca1 100644
--- a/packages/react/src/routes/router.tsx
+++ b/packages/react/src/routes/router.tsx
@@ -11,6 +11,8 @@ const Settings = React.lazy(() => import("./settings"));
const SettingsLang = React.lazy(() => import("./settings/lang"));
const SettingsTheme = React.lazy(() => import("./settings/theme"));
const SettingsUser = React.lazy(() => import("./settings/user"));
+const SettingsHomepage = React.lazy(() => import("./settings/homepage"));
+const SettingsBlocked = React.lazy(() => import("./settings/blocked"));
const About = React.lazy(() => import("./about"));
const AboutGeneral = React.lazy(() => import("./about/general"));
const AboutChangelog = React.lazy(() => import("./about/changelog"));
@@ -97,6 +99,14 @@ const router = createBrowserRouter([
path: "user",
element: ,
},
+ {
+ path: "homepage",
+ element: ,
+ },
+ {
+ path: "blocked",
+ element: ,
+ },
// Add children routes similar to above pattern
],
},
diff --git a/packages/react/src/routes/settings/blocked.tsx b/packages/react/src/routes/settings/blocked.tsx
new file mode 100644
index 000000000..c320ade9b
--- /dev/null
+++ b/packages/react/src/routes/settings/blocked.tsx
@@ -0,0 +1,8 @@
+import { blockedChannelsAtom } from "@/store/settings";
+import { useAtom } from "jotai";
+
+export default function SettingsBlocked() {
+ const [blockedChannels, setBlockedChannels] = useAtom(blockedChannelsAtom);
+
+ return
;
+}
diff --git a/packages/react/src/routes/settings/homepage.tsx b/packages/react/src/routes/settings/homepage.tsx
new file mode 100644
index 000000000..ddd883d8a
--- /dev/null
+++ b/packages/react/src/routes/settings/homepage.tsx
@@ -0,0 +1,160 @@
+import { SettingsItem } from "@/components/settings/SettingsItem";
+import { TopicPicker } from "@/components/topic/TopicPicker";
+import { cn } from "@/lib/utils";
+import { Badge } from "@/shadcn/ui/badge";
+import { Label } from "@/shadcn/ui/label";
+import { RadioGroup, RadioGroupItem } from "@/shadcn/ui/radio-group";
+import { Switch } from "@/shadcn/ui/switch";
+import {
+ defaultOpenAtom,
+ hideCollabStreamsAtom,
+ hidePlaceholderAtom,
+ hideThumbnailAtom,
+ homeViewModeAtom,
+ ignoredTopicsAtom,
+} from "@/store/settings";
+import { useAtom } from "jotai";
+import { useTranslation } from "react-i18next";
+
+export default function SettingsHomepage() {
+ const { t } = useTranslation();
+ const [defaultOpen, setDefaultOpen] = useAtom(defaultOpenAtom);
+ const [homeViewMode, setHomeViewMode] = useAtom(homeViewModeAtom);
+ const [ignoredTopics, setIgnoredTopics] = useAtom(ignoredTopicsAtom);
+ const [hideThumbnail, setHideThumbnail] = useAtom(hideThumbnailAtom);
+ const [hideCollab, setHideCollab] = useAtom(hideCollabStreamsAtom);
+ const [hidePlaceholder, setHidePlaceholder] = useAtom(hidePlaceholderAtom);
+
+ const defaultPages = [
+ {
+ value: "Home",
+ label: t("views.settings.defaultHomepage.lastVisitedOrgHome"),
+ },
+ {
+ value: "Favorites",
+ label: t("views.settings.defaultHomepage.favoritesWhenLoggedIn"),
+ },
+ ];
+
+ const gridSizes = [
+ {
+ value: "grid",
+ label: t("views.settings.gridSize.0"),
+ },
+ {
+ value: "list",
+ label: t("views.settings.gridSize.1"),
+ },
+ {
+ value: "denseList",
+ label: t("views.settings.gridSize.2"),
+ },
+ ];
+
+ const hideFeatures = [
+ {
+ label: t("views.settings.hideVideoThumbnailsLabel"),
+ value: hideThumbnail,
+ onChange: () => setHideThumbnail(!hideThumbnail),
+ },
+ {
+ label: t("views.settings.hideCollabStreamsLabel"),
+ value: hideCollab,
+ onChange: () => setHideCollab(!hideCollab),
+ },
+ {
+ label: t("views.settings.hidePlaceholderStreams"),
+ value: hidePlaceholder,
+ onChange: () => setHidePlaceholder(!hidePlaceholder),
+ },
+ ];
+
+ return (
+
+
+ setDefaultOpen(val)}
+ >
+ {defaultPages.map(({ label, value }, index) => (
+
+ ))}
+
+
+
+
+ setHomeViewMode(val)
+ }
+ >
+ {gridSizes.map(({ label, value }) => (
+
+ ))}
+
+
+
+
+
+ setIgnoredTopics((currentTopics) => [...currentTopics, topic])
+ }
+ />
+
+ {ignoredTopics.map((topic) => (
+
+ setIgnoredTopics((currentTopics) =>
+ currentTopics.filter((t) => t !== topic),
+ )
+ }
+ >
+ {topic}
+
+
+ ))}
+
+
+
+
+
+ {hideFeatures.map(({ label, value, onChange }, index) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/packages/react/src/routes/settings/lang.tsx b/packages/react/src/routes/settings/lang.tsx
index f1d2246f6..985ef232b 100644
--- a/packages/react/src/routes/settings/lang.tsx
+++ b/packages/react/src/routes/settings/lang.tsx
@@ -92,11 +92,11 @@ export default function SettingsLang() {
-
+
{TL_LANGS.map(({ text, value }, index) => (