fix groups and order initialization for new accounts

This commit is contained in:
Pas 2025-07-31 10:14:50 -06:00
parent 100d7cfe40
commit 0acb043960
10 changed files with 141 additions and 53 deletions

View file

@ -10,12 +10,12 @@ export interface BookmarkMetaInput {
year: number;
poster?: string;
type: string;
group?: string[];
}
export interface BookmarkInput {
tmdbId: string;
meta: BookmarkMetaInput;
group?: string[];
}
export function bookmarkMediaToInput(
@ -30,6 +30,7 @@ export function bookmarkMediaToInput(
year: item.year ?? 0,
},
tmdbId,
group: item.group,
};
}

View file

@ -33,8 +33,8 @@ export interface BookmarkResponse {
year: number;
poster?: string;
type: "show" | "movie";
group?: string[];
};
group: string[];
updatedAt: string;
}
@ -63,6 +63,7 @@ export function bookmarkResponsesToEntries(responses: BookmarkResponse[]) {
const entries = responses.map((bookmark) => {
const item: BookmarkMediaItem = {
...bookmark.meta,
group: bookmark.group.length > 0 ? bookmark.group : undefined,
updatedAt: new Date(bookmark.updatedAt).getTime(),
};
return [bookmark.tmdbId, item] as const;

View file

@ -11,6 +11,7 @@ import {
} from "@/backend/accounts/user";
import { useAuthStore } from "@/stores/auth";
import { useBookmarkStore } from "@/stores/bookmarks";
import { useGroupOrderStore } from "@/stores/groupOrder";
import { useLanguageStore } from "@/stores/language";
import { usePreferencesStore } from "@/stores/preferences";
import { useProgressStore } from "@/stores/progress";
@ -102,7 +103,7 @@ export function useAuthData() {
replaceItems(progressResponsesToEntries(progress));
if (groupOrder?.groupOrder) {
useBookmarkStore.getState().setGroupOrder(groupOrder.groupOrder);
useGroupOrderStore.getState().setGroupOrder(groupOrder.groupOrder);
}
if (settings.applicationLanguage) {

View file

@ -25,6 +25,7 @@ import App from "@/setup/App";
import { conf } from "@/setup/config";
import { useAuthStore } from "@/stores/auth";
import { BookmarkSyncer } from "@/stores/bookmarks/BookmarkSyncer";
import { GroupSyncer } from "@/stores/groupOrder/GroupSyncer";
import { changeAppLanguage, useLanguageStore } from "@/stores/language";
import { ProgressSyncer } from "@/stores/progress/ProgressSyncer";
import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer";
@ -185,6 +186,7 @@ root.render(
<ThemeProvider applyGlobal>
<ProgressSyncer />
<BookmarkSyncer />
<GroupSyncer />
<SettingsSyncer />
<TheRouter>
<MigrationRunner />

View file

@ -16,6 +16,7 @@ import { useIsMobile } from "@/hooks/useIsMobile";
import { CarouselNavButtons } from "@/pages/discover/components/CarouselNavButtons";
import { useAuthStore } from "@/stores/auth";
import { useBookmarkStore } from "@/stores/bookmarks";
import { useGroupOrderStore } from "@/stores/groupOrder";
import { useProgressStore } from "@/stores/progress";
import { MediaItem } from "@/utils/mediaTypes";
@ -64,8 +65,8 @@ export function BookmarksCarousel({
const account = useAuthStore((s) => s.account);
// Group order editing state
const groupOrder = useBookmarkStore((s) => s.groupOrder);
const setGroupOrder = useBookmarkStore((s) => s.setGroupOrder);
const groupOrder = useGroupOrderStore((s) => s.groupOrder);
const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder);
const editOrderModal = useModal("bookmark-edit-order-carousel");
const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]);
@ -323,7 +324,9 @@ export function BookmarksCarousel({
// Save to backend
if (backendUrl && account) {
useBookmarkStore.getState().saveGroupOrderToBackend(backendUrl, account);
useGroupOrderStore
.getState()
.saveGroupOrderToBackend(backendUrl, account);
}
};

View file

@ -16,6 +16,7 @@ import { Heading2, Paragraph } from "@/components/utils/Text";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useAuthStore } from "@/stores/auth";
import { useBookmarkStore } from "@/stores/bookmarks";
import { useGroupOrderStore } from "@/stores/groupOrder";
import { useProgressStore } from "@/stores/progress";
import { MediaItem } from "@/utils/mediaTypes";
@ -42,8 +43,8 @@ export function BookmarksPart({
const { t } = useTranslation();
const progressItems = useProgressStore((s) => s.items);
const bookmarks = useBookmarkStore((s) => s.bookmarks);
const groupOrder = useBookmarkStore((s) => s.groupOrder);
const setGroupOrder = useBookmarkStore((s) => s.setGroupOrder);
const groupOrder = useGroupOrderStore((s) => s.groupOrder);
const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder);
const removeBookmark = useBookmarkStore((s) => s.removeBookmark);
const [editing, setEditing] = useState(false);
const [gridRef] = useAutoAnimate<HTMLDivElement>();
@ -278,7 +279,9 @@ export function BookmarksPart({
// Save to backend
if (backendUrl && account) {
useBookmarkStore.getState().saveGroupOrderToBackend(backendUrl, account);
useGroupOrderStore
.getState()
.saveGroupOrderToBackend(backendUrl, account);
}
};

View file

@ -32,9 +32,9 @@ async function syncBookmarks(
title: item.title ?? "",
type: item.type ?? "",
year: item.year ?? NaN,
group: item.group,
},
tmdbId: item.tmdbId,
group: item.group,
});
continue;
}

View file

@ -2,9 +2,6 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
import { getGroupOrder, updateGroupOrder } from "@/backend/accounts/groupOrder";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { AccountWithToken, useAuthStore } from "@/stores/auth";
import { PlayerMeta } from "@/stores/player/slices/source";
export interface BookmarkMediaItem {
@ -30,20 +27,10 @@ export interface BookmarkUpdateItem {
export interface BookmarkStore {
bookmarks: Record<string, BookmarkMediaItem>;
updateQueue: BookmarkUpdateItem[];
groupOrder: string[];
addBookmark(meta: PlayerMeta): void;
addBookmarkWithGroups(meta: PlayerMeta, groups?: string[]): void;
removeBookmark(id: string): void;
replaceBookmarks(items: Record<string, BookmarkMediaItem>): void;
setGroupOrder(order: string[]): void;
saveGroupOrderToBackend(
backendUrl: string,
account: AccountWithToken,
): Promise<void>;
loadGroupOrderFromBackend(
backendUrl: string,
account: AccountWithToken,
): Promise<void>;
clear(): void;
clearUpdateQueue(): void;
removeUpdateItem(id: string): void;
@ -56,7 +43,6 @@ export const useBookmarkStore = create(
immer<BookmarkStore>((set) => ({
bookmarks: {},
updateQueue: [],
groupOrder: [],
removeBookmark(id) {
set((s) => {
updateId += 1;
@ -135,35 +121,6 @@ export const useBookmarkStore = create(
s.updateQueue = [...s.updateQueue.filter((v) => v.id !== id)];
});
},
setGroupOrder(order: string[]) {
set((s) => {
s.groupOrder = order;
});
},
async saveGroupOrderToBackend(
backendUrl: string,
account: AccountWithToken,
) {
if (!account || !backendUrl) {
throw new Error("No authenticated account or backend URL");
}
const currentState = useBookmarkStore.getState();
await updateGroupOrder(backendUrl, account, currentState.groupOrder);
},
async loadGroupOrderFromBackend(
backendUrl: string,
account: AccountWithToken,
) {
if (!account || !backendUrl) {
throw new Error("No authenticated account or backend URL");
}
const response = await getGroupOrder(backendUrl, account);
set((s) => {
s.groupOrder = response.groupOrder;
});
},
})),
{
name: "__MW::bookmarks",

View file

@ -0,0 +1,55 @@
import { useEffect, useRef } from "react";
import { updateGroupOrder } from "@/backend/accounts/groupOrder";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useAuthStore } from "@/stores/auth";
import { useGroupOrderStore } from "@/stores/groupOrder";
const syncIntervalMs = 5 * 1000;
export function GroupSyncer() {
const url = useBackendUrl();
const groupOrder = useGroupOrderStore((s) => s.groupOrder);
const lastSyncedOrder = useRef<string[]>([]);
const isInitialized = useRef(false);
// Initialize lastSyncedOrder on first render
useEffect(() => {
if (!isInitialized.current) {
lastSyncedOrder.current = [...groupOrder];
isInitialized.current = true;
}
}, [groupOrder]);
useEffect(() => {
const interval = setInterval(() => {
(async () => {
if (!url) return;
const user = useAuthStore.getState();
if (!user.account) return; // not logged in, dont sync to server
// Check if group order has changed since last sync
const currentOrder = useGroupOrderStore.getState().groupOrder;
const hasChanged =
JSON.stringify(currentOrder) !==
JSON.stringify(lastSyncedOrder.current);
if (hasChanged) {
try {
await updateGroupOrder(url, user.account, currentOrder);
lastSyncedOrder.current = [...currentOrder];
} catch (err) {
console.error("Failed to sync group order:", err);
}
}
})();
}, syncIntervalMs);
return () => {
clearInterval(interval);
};
}, [url]);
return null;
}

View file

@ -0,0 +1,65 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
import { getGroupOrder, updateGroupOrder } from "@/backend/accounts/groupOrder";
import { AccountWithToken } from "@/stores/auth";
export interface GroupOrderStore {
groupOrder: string[];
setGroupOrder(order: string[]): void;
saveGroupOrderToBackend(
backendUrl: string,
account: AccountWithToken,
): Promise<void>;
loadGroupOrderFromBackend(
backendUrl: string,
account: AccountWithToken,
): Promise<void>;
clear(): void;
}
export const useGroupOrderStore = create(
persist(
immer<GroupOrderStore>((set) => ({
groupOrder: [],
setGroupOrder(order: string[]) {
set((s) => {
s.groupOrder = order;
});
},
async saveGroupOrderToBackend(
backendUrl: string,
account: AccountWithToken,
) {
if (!account || !backendUrl) {
throw new Error("No authenticated account or backend URL");
}
const currentState = useGroupOrderStore.getState();
await updateGroupOrder(backendUrl, account, currentState.groupOrder);
},
async loadGroupOrderFromBackend(
backendUrl: string,
account: AccountWithToken,
) {
if (!account || !backendUrl) {
throw new Error("No authenticated account or backend URL");
}
const response = await getGroupOrder(backendUrl, account);
set((s) => {
s.groupOrder = response.groupOrder;
});
},
clear() {
set((s) => {
s.groupOrder = [];
});
},
})),
{
name: "__MW::groupOrder",
},
),
);