diff --git a/functions/db/databaseTypes.ts b/functions/db/databaseTypes.ts index 1d835297..96af94bd 100644 --- a/functions/db/databaseTypes.ts +++ b/functions/db/databaseTypes.ts @@ -476,16 +476,7 @@ export interface Database { folder_id: number folder_title: string folder_description: string - deck_id: number - }[] - } - get_folder_with_decks_backup: { - Args: { - usr_id: number - } - Returns: { - folder_id: number - folder_title: string + folder_author_id: number deck_id: number }[] } diff --git a/functions/db/folder/delete-folder-by-id.ts b/functions/db/folder/delete-folder-by-id.ts new file mode 100644 index 00000000..9a48a355 --- /dev/null +++ b/functions/db/folder/delete-folder-by-id.ts @@ -0,0 +1,19 @@ +import { EnvSafe } from "../../env/env-schema.ts"; +import { getDatabase } from "../get-database.ts"; +import { DatabaseException } from "../database-exception.ts"; + +export const deleteFolderById = async ( + env: EnvSafe, + folderId: number, +): Promise => { + const db = getDatabase(env); + const deleteFolderResult = await db + .from("folder") + .delete() + .eq("id", folderId) + .single(); + + if (deleteFolderResult.error) { + throw new DatabaseException(deleteFolderResult.error); + } +}; diff --git a/functions/db/folder/get-folders-with-decks-db.tsx b/functions/db/folder/get-folders-with-decks-db.tsx index e529e3c2..57d5b3e9 100644 --- a/functions/db/folder/get-folders-with-decks-db.tsx +++ b/functions/db/folder/get-folders-with-decks-db.tsx @@ -7,6 +7,7 @@ const userFoldersSchema = z.object({ folder_id: z.number(), folder_title: z.string(), folder_description: z.string().nullable(), + folder_author_id: z.number(), deck_id: z.number().nullable(), }); diff --git a/functions/delete-folder.ts b/functions/delete-folder.ts new file mode 100644 index 00000000..49991cce --- /dev/null +++ b/functions/delete-folder.ts @@ -0,0 +1,35 @@ +import { handleError } from "./lib/handle-error/handle-error.ts"; +import { getUser } from "./services/get-user.ts"; +import { createAuthFailedResponse } from "./lib/json-response/create-auth-failed-response.ts"; +import { envSchema } from "./env/env-schema.ts"; +import { createBadRequestResponse } from "./lib/json-response/create-bad-request-response.ts"; +import { getFolderByIdAndAuthorId } from "./db/folder/get-folder-by-id-and-author-id.ts"; +import { createJsonResponse } from "./lib/json-response/create-json-response.ts"; +import { deleteFolderById } from "./db/folder/delete-folder-by-id.ts"; + +export const onRequestPost = handleError(async ({ request, env }) => { + const user = await getUser(request, env); + if (!user) { + return createAuthFailedResponse(); + } + const envSafe = envSchema.parse(env); + + const url = new URL(request.url); + const folderId = url.searchParams.get("folder_id"); + if (!folderId) { + return createBadRequestResponse(); + } + + const canEdit = await getFolderByIdAndAuthorId( + envSafe, + parseInt(folderId), + user, + ); + if (!canEdit) { + return createBadRequestResponse(); + } + + await deleteFolderById(envSafe, parseInt(folderId)); + + return createJsonResponse(null); +}); diff --git a/functions/upsert-folder.ts b/functions/upsert-folder.ts index d35f522b..668f9a30 100644 --- a/functions/upsert-folder.ts +++ b/functions/upsert-folder.ts @@ -22,6 +22,9 @@ const requestSchema = z.object({ export type AddFolderRequest = z.infer; export type AddFolderResponse = { + folder: { + id: number; + } folders: UserFoldersDbType[]; }; @@ -36,19 +39,15 @@ export const onRequestPost = handleError(async ({ request, env }) => { const envSafe = envSchema.parse(env); - if (input.data.id) { - const canEdit = await getFolderByIdAndAuthorId( - envSafe, - input.data.id, - user, - ); + const { data } = input; + if (data.id) { + const canEdit = await getFolderByIdAndAuthorId(envSafe, data.id, user); if (!canEdit) { return createBadRequestResponse(); } } const db = getDatabase(envSafe); - const { data } = input; const upsertFolderResult = await db .from("folder") @@ -87,6 +86,7 @@ export const onRequestPost = handleError(async ({ request, env }) => { } return createJsonResponse({ + folder: upsertFolderResult.data, folders: await getFoldersWithDecksDb(envSafe, user.id), }); }); diff --git a/src/api/api.ts b/src/api/api.ts index 0a68daf9..f584bbe8 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -70,7 +70,7 @@ export const addDeckAccessRequest = (body: AddDeckAccessRequest) => { ); }; -export const apiDuplicateDeckRequest = (deckId: number) => { +export const duplicateDeckRequest = (deckId: number) => { return request(`/duplicate-deck?deck_id=${deckId}`, "POST"); }; @@ -102,7 +102,7 @@ export const addCardRequest = (body: AddCardRequest) => { return request("/add-card", "POST", body); }; -export const removeDeckFromMine = (body: RemoveDeckFromMineRequest) => { +export const removeDeckFromMineRequest = (body: RemoveDeckFromMineRequest) => { return request( "/remove-deck-from-mine", "POST", @@ -110,19 +110,19 @@ export const removeDeckFromMine = (body: RemoveDeckFromMineRequest) => { ); }; -export const apiDeckCatalog = () => { +export const deckCatalogRequest = () => { return request("/catalog-decks"); }; -export const apiDeckWithCards = (deckId: number) => { +export const deckWithCardsRequest = (deckId: number) => { return request(`/deck-with-cards?deck_id=${deckId}`); }; -export const apiDeckCategories = () => { +export const deckCategoriesRequest = () => { return request("/deck-categories"); }; -export const apiFolderUpsert = (body: AddFolderRequest) => { +export const folderUpsertRequest = (body: AddFolderRequest) => { return request( "/upsert-folder", "POST", @@ -130,6 +130,10 @@ export const apiFolderUpsert = (body: AddFolderRequest) => { ); }; -export const apiDecksMine = () => { +export const deleteFolderRequest = (folderId: number) => { + return request(`/delete-folder?folder_id=${folderId}`, "POST"); +}; + +export const decksMineRequest = () => { return request("/decks-mine"); }; diff --git a/src/screens/app.tsx b/src/screens/app.tsx index f36cb1e4..ff10722a 100644 --- a/src/screens/app.tsx +++ b/src/screens/app.tsx @@ -29,11 +29,7 @@ import { FolderScreen } from "./folder-review/folder-screen.tsx"; export const App = observer(() => { useRestoreFullScreenExpand(); - if ( - deckListStore.isSharedDeckLoading || - deckListStore.isDeckRemoving || - deckListStore.isReviewAllLoading - ) { + if (deckListStore.isFullScreenLoaderVisible) { return ; } diff --git a/src/screens/deck-catalog/store/deck-catalog-store.ts b/src/screens/deck-catalog/store/deck-catalog-store.ts index 8809d9aa..618ed330 100644 --- a/src/screens/deck-catalog/store/deck-catalog-store.ts +++ b/src/screens/deck-catalog/store/deck-catalog-store.ts @@ -1,5 +1,5 @@ import { makeAutoObservable } from "mobx"; -import { apiDeckCatalog, apiDeckCategories } from "../../../api/api.ts"; +import { deckCatalogRequest, deckCategoriesRequest } from "../../../api/api.ts"; import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; import { DeckCatalogResponse } from "../../../../functions/catalog-decks.ts"; import { TextField } from "../../../lib/mobx-form/text-field.ts"; @@ -46,8 +46,8 @@ export class DeckCatalogStore { } load() { - this.decks = fromPromise(decksCached(apiDeckCatalog())); - this.categories = fromPromise(categoriesCached(apiDeckCategories())); + this.decks = fromPromise(decksCached(deckCatalogRequest())); + this.categories = fromPromise(categoriesCached(deckCategoriesRequest())); } get filteredDecks() { diff --git a/src/screens/deck-review/deck-preview.tsx b/src/screens/deck-review/deck-preview.tsx index 37a77ec9..c0738038 100644 --- a/src/screens/deck-review/deck-preview.tsx +++ b/src/screens/deck-review/deck-preview.tsx @@ -11,8 +11,9 @@ import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; import { showConfirm } from "../../lib/telegram/show-confirm.ts"; import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx"; import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; -import { apiDuplicateDeckRequest } from "../../api/api.ts"; +import { duplicateDeckRequest } from "../../api/api.ts"; import { t } from "../../translations/t.ts"; +import { userStore } from "../../store/user-store.ts"; export const DeckPreview = observer(() => { const reviewStore = useReviewStore(); @@ -110,7 +111,7 @@ export const DeckPreview = observer(() => { gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))", })} > - {deckListStore.canEditDeck(deck) ? ( + {deckListStore.canEditDeck ? ( { {t("add_card_short")} ) : null} - {deckListStore.user?.is_admin && ( + {userStore.isAdmin && ( { showConfirm(t("duplicate_confirm")).then(() => { - apiDuplicateDeckRequest(deck.id).then(() => { + duplicateDeckRequest(deck.id).then(() => { screenStore.go({ type: "main" }); }); }); @@ -139,7 +140,7 @@ export const DeckPreview = observer(() => { {t("duplicate")} )} - {deckListStore.canEditDeck(deck) ? ( + {deckListStore.canEditDeck ? ( { ) : null} - {deckListStore.canEditDeck(deck) && ( + {deckListStore.canEditDeck && ( { const reviewStore = useReviewStore(); @@ -16,7 +17,7 @@ export const RepeatAllScreen = observer(() => { useMount(() => { reviewStore.startAllRepeatReview( deckListStore.myDecks, - deckListStore.user?.is_speaking_card_enabled ?? false, + userStore.isSpeakingCardsEnabled, ); }); diff --git a/src/screens/folder-form/folder-form.tsx b/src/screens/folder-form/folder-form.tsx index 32b10cf0..011c5a03 100644 --- a/src/screens/folder-form/folder-form.tsx +++ b/src/screens/folder-form/folder-form.tsx @@ -6,9 +6,7 @@ import { Input } from "../../ui/input.tsx"; import React from "react"; import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; import { screenStore } from "../../store/screen-store.ts"; -import { - useTelegramProgress -} from "../../lib/telegram/use-telegram-progress.tsx"; +import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; import { useMount } from "../../lib/react/use-mount.ts"; import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; import { assert } from "../../lib/typescript/assert.ts"; diff --git a/src/screens/folder-form/store/folder-form-store.ts b/src/screens/folder-form/store/folder-form-store.ts index 6cc6c798..56a7e55c 100644 --- a/src/screens/folder-form/store/folder-form-store.ts +++ b/src/screens/folder-form/store/folder-form-store.ts @@ -9,7 +9,7 @@ import { isFormTouched, isFormValid, } from "../../../lib/mobx-form/form-has-error.ts"; -import { apiDecksMine, apiFolderUpsert } from "../../../api/api.ts"; +import { decksMineRequest, folderUpsertRequest } from "../../../api/api.ts"; import { deckListStore } from "../../../store/deck-list-store.ts"; import { ListField } from "../../../lib/mobx-form/list-field.ts"; import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; @@ -39,7 +39,7 @@ export class FolderFormStore { assert(screen.type === "folderForm"); this.decksMine = fromPromise( - apiDecksMine().then((response) => response.decks), + decksMineRequest().then((response) => response.decks), ); if (screen.folderId) { @@ -90,16 +90,17 @@ export class FolderFormStore { this.isSending = true; - apiFolderUpsert({ + folderUpsertRequest({ id: screen.folderId, title: this.folderForm.title.value, description: this.folderForm.description.value, deckIds: this.folderForm.decks.value.map((deck) => deck.id), }) - .then(({ folders }) => { - deckListStore.optimisticUpdateFolders(folders); + .then(({ folders, folder }) => { + deckListStore.updateFolders(folders); assert(this.folderForm); formUnTouchAll(this.folderForm); + screenStore.go({ type: "folderPreview", folderId: folder.id }); }) .finally( action(() => { diff --git a/src/screens/folder-review/folder-preview.tsx b/src/screens/folder-review/folder-preview.tsx index 08428a8d..2fdbf0b0 100644 --- a/src/screens/folder-review/folder-preview.tsx +++ b/src/screens/folder-review/folder-preview.tsx @@ -15,12 +15,13 @@ import { useReviewStore } from "../deck-review/store/review-store-context.tsx"; import { SettingsRow } from "../user-settings/settings-row.tsx"; import { ListHeader } from "../../ui/list-header.tsx"; import { assert } from "../../lib/typescript/assert.ts"; +import { userStore } from "../../store/user-store.ts"; export const FolderPreview = observer(() => { const reviewStore = useReviewStore(); useBackButton(() => { - screenStore.back(); + screenStore.go({ type: "main" }); }); useTelegramProgress(() => deckListStore.isDeckCardsLoading); @@ -30,7 +31,10 @@ export const FolderPreview = observer(() => { () => { const folder = deckListStore.selectedFolder; assert(folder); - reviewStore.startFolderReview(folder.decks); + reviewStore.startFolderReview( + folder.decks, + userStore.isSpeakingCardsEnabled, + ); }, () => deckListStore.isFolderReviewVisible, ); @@ -118,7 +122,7 @@ export const FolderPreview = observer(() => { gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))", })} > - {true ? ( + {deckListStore.canEditFolder ? ( { {t("edit")} ) : null} - {true ? ( + {deckListStore.canEditFolder ? ( { - showConfirm(t("delete_deck_confirm")).then(() => { - // deckListStore.removeDeck(); + showConfirm( + "Do you want to delete the folder? Deleting folder won't remove decks inside the folder", + ).then(() => { + deckListStore.deleteFolder(); }); }} > @@ -164,10 +170,10 @@ export const FolderPreview = observer(() => { ); })} + {folder.cardsToReview.length === 0 && ( + {t("no_cards_to_review_in_deck")} + )} - {folder.cardsToReview.length === 0 && ( - {t("no_cards_to_review_in_deck")} - )} ); }); diff --git a/src/screens/share-deck/share-deck-settings.tsx b/src/screens/share-deck/share-deck-settings.tsx index 058eca7a..28db7b9d 100644 --- a/src/screens/share-deck/share-deck-settings.tsx +++ b/src/screens/share-deck/share-deck-settings.tsx @@ -3,9 +3,7 @@ import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; import { screenStore } from "../../store/screen-store.ts"; import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; import { t } from "../../translations/t.ts"; -import { - useTelegramProgress -} from "../../lib/telegram/use-telegram-progress.tsx"; +import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; import { SettingsRow } from "../user-settings/settings-row.tsx"; import { RadioSwitcher } from "../../ui/radio-switcher.tsx"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; diff --git a/src/screens/user-settings/store/user-settings-store.tsx b/src/screens/user-settings/store/user-settings-store.tsx index 1bb8fee6..d30c2c64 100644 --- a/src/screens/user-settings/store/user-settings-store.tsx +++ b/src/screens/user-settings/store/user-settings-store.tsx @@ -1,6 +1,5 @@ import { action, makeAutoObservable, when } from "mobx"; import { TextField } from "../../../lib/mobx-form/text-field.ts"; -import { deckListStore } from "../../../store/deck-list-store.ts"; import { assert } from "../../../lib/typescript/assert.ts"; import { DateTime } from "luxon"; import { formatTime } from "../generate-time-range.tsx"; @@ -9,6 +8,7 @@ import { userSettingsRequest } from "../../../api/api.ts"; import { screenStore } from "../../../store/screen-store.ts"; import { UserSettingsRequest } from "../../../../functions/user-settings.ts"; import { BooleanField } from "../../../lib/mobx-form/boolean-field.ts"; +import { userStore } from "../../../store/user-store.ts"; const DEFAULT_TIME = "12:00"; @@ -25,9 +25,9 @@ export class UserSettingsStore { } async load() { - await when(() => !!deckListStore.myInfo); - assert(deckListStore.myInfo); - const userInfo = deckListStore.myInfo.user; + await when(() => !!userStore.userInfo); + assert(userStore.userInfo); + const userInfo = userStore.userInfo; const remindDate = userInfo.last_reminded_date ? DateTime.fromISO(userInfo.last_reminded_date) : null; @@ -70,7 +70,7 @@ export class UserSettingsStore { userSettingsRequest(body) .then(() => { - deckListStore.optimisticUpdateSettings({ + userStore.updateSettings({ is_remind_enabled: body.isRemindNotifyEnabled, last_reminded_date: body.remindNotificationTime, is_speaking_card_enabled: body.isSpeakingCardEnabled, diff --git a/src/store/deck-list-store.test.ts b/src/store/deck-list-store.test.ts index 12fa4229..45873dea 100644 --- a/src/store/deck-list-store.test.ts +++ b/src/store/deck-list-store.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, test, vi } from "vitest"; import { deckListStore } from "./deck-list-store.ts"; import { MyInfoResponse } from "../../functions/my-info.ts"; import { when } from "mobx"; +import { userStore } from "./user-store.ts"; vi.mock("../api/api.ts", () => { return { @@ -130,7 +131,7 @@ describe("deck list store", () => { await when(() => !!deckListStore.myInfo); - expect(deckListStore.myId).toBe(111); + expect(userStore.myId).toBe(111); expect(deckListStore.publicDecks).toHaveLength(0); expect(deckListStore.newCardsCount).toBe(3); expect(deckListStore.selectedDeck?.cardsToReview).toMatchInlineSnapshot(` diff --git a/src/store/deck-list-store.ts b/src/store/deck-list-store.ts index 32f7452f..98edc1b6 100644 --- a/src/store/deck-list-store.ts +++ b/src/store/deck-list-store.ts @@ -1,10 +1,11 @@ import { action, makeAutoObservable, when } from "mobx"; import { addDeckToMineRequest, - apiDeckWithCards, + deckWithCardsRequest, + deleteFolderRequest, getSharedDeckRequest, myInfoRequest, - removeDeckFromMine, + removeDeckFromMineRequest, } from "../api/api.ts"; import { MyInfoResponse } from "../../functions/my-info.ts"; import { @@ -16,9 +17,9 @@ import { CardToReviewDbType } from "../../functions/db/deck/get-cards-to-review- import { assert } from "../lib/typescript/assert.ts"; import { ReviewStore } from "../screens/deck-review/store/review-store.ts"; import { reportHandledError } from "../lib/rollbar/rollbar.tsx"; -import { UserDbType } from "../../functions/db/user/upsert-user-db.ts"; import { BooleanToggle } from "../lib/mobx-form/boolean-toggle.ts"; import { UserFoldersDbType } from "../../functions/db/folder/get-folders-with-decks-db.tsx"; +import { userStore } from "./user-store.ts"; export enum StartParamType { RepeatAll = "repeat_all", @@ -44,38 +45,29 @@ export type DeckListItem = { | { type: "folder"; decks: DeckWithCardsWithReviewType[]; + authorId: number; } ); const collapsedDecksLimit = 3; export class DeckListStore { - myInfo?: MyInfoResponse; + myInfo?: Omit; isMyInfoLoading = false; - isSharedDeckLoading = false; + isFullScreenLoaderVisible = false; isSharedDeckLoaded = false; - isReviewAllLoading = false; isReviewAllLoaded = false; skeletonLoaderData = { publicCount: 3, myDecksCount: 3 }; - isDeckRemoving = false; - isDeckCardsLoading = false; isMyDecksExpanded = new BooleanToggle(false); constructor() { - makeAutoObservable( - this, - { - canEditDeck: false, - searchDeckById: false, - }, - { autoBind: true }, - ); + makeAutoObservable(this, { searchDeckById: false }, { autoBind: true }); } loadFirstTime(startParam?: string) { @@ -93,6 +85,7 @@ export class DeckListStore { .then( action((result) => { this.myInfo = result; + userStore.setUser(result.user); }), ) .finally( @@ -112,14 +105,14 @@ export class DeckListStore { return; } - this.isReviewAllLoading = true; + this.isFullScreenLoaderVisible = true; when(() => !!this.myInfo) .then(() => { screenStore.go({ type: "reviewAll" }); }) .finally( action(() => { - this.isReviewAllLoading = false; + this.isFullScreenLoaderVisible = false; this.isReviewAllLoaded = true; }), ); @@ -128,7 +121,7 @@ export class DeckListStore { return; } - this.isSharedDeckLoading = true; + this.isFullScreenLoaderVisible = true; await when(() => !!this.myInfo); getSharedDeckRequest(startParam) @@ -162,7 +155,7 @@ export class DeckListStore { }) .finally( action(() => { - this.isSharedDeckLoading = false; + this.isFullScreenLoaderVisible = false; this.isSharedDeckLoaded = true; }), ); @@ -190,7 +183,7 @@ export class DeckListStore { reviewStore.startDeckReview( deckListStore.selectedDeck, - this.user?.is_speaking_card_enabled ?? false, + userStore.isSpeakingCardsEnabled, ); } @@ -208,21 +201,12 @@ export class DeckListStore { }); } - get user() { - return this.myInfo?.user ?? null; - } - - get myId() { - return this.user?.id; - } - - canEditDeck(deck: DeckWithCardsWithReviewType) { - const isAdmin = this.user?.is_admin ?? false; - if (isAdmin) { - return true; + get canEditDeck() { + const deck = this.selectedDeck; + if (!deck) { + return false; } - - return deckListStore.myId && deck.author_id === deckListStore.myId; + return deck.author_id === userStore.myId || userStore.isAdmin; } openDeckFromCatalog(deck: DeckWithCardsDbType, isMine: boolean) { @@ -237,7 +221,7 @@ export class DeckListStore { screenStore.go({ type: "deckPublic", deckId: deck.id }); this.isDeckCardsLoading = true; - apiDeckWithCards(deck.id) + deckWithCardsRequest(deck.id) .then((deckWithCards) => { this.replaceDeck(deckWithCards); }) @@ -290,6 +274,14 @@ export class DeckListStore { return folder; } + get canEditFolder() { + const folder = this.selectedFolder; + if (!folder) { + return false; + } + return folder.authorId === userStore.myId || userStore.isAdmin; + } + get isFolderReviewVisible() { return this.selectedFolder ? this.selectedFolder.cardsToReview.length > 0 @@ -386,6 +378,7 @@ export class DeckListStore { { folderName: string; folderDescription: string | null; + folderAuthorId: number; decks: DeckWithCardsWithReviewType[]; } >(); @@ -394,6 +387,7 @@ export class DeckListStore { const mapItem = map.get(folder.folder_id) ?? { folderName: folder.folder_title, folderDescription: folder.folder_description, + folderAuthorId: folder.folder_author_id, decks: [], }; const deck = myDecks.find((deck) => deck.id === folder.deck_id); @@ -413,6 +407,7 @@ export class DeckListStore { type: "folder", name: mapItem.folderName, description: mapItem.folderDescription, + authorId: mapItem.folderAuthorId, })); } @@ -466,15 +461,44 @@ export class DeckListStore { }, 0); } + deleteFolder() { + const folder = this.selectedFolder; + if (!folder) { + return; + } + + this.isFullScreenLoaderVisible = true; + + deleteFolderRequest(folder.id) + .then( + action(() => { + screenStore.go({ type: "main" }); + myInfoRequest().then( + action((result) => { + this.myInfo = result; + }), + ); + }), + ) + .catch((e) => { + reportHandledError(`Unable to remove deck ${folder.id}`, e); + }) + .finally( + action(() => { + this.isFullScreenLoaderVisible = false; + }), + ); + } + removeDeck() { const deck = this.selectedDeck; if (!deck) { return; } - this.isDeckRemoving = true; + this.isFullScreenLoaderVisible = true; - removeDeckFromMine({ deckId: deck.id }) + removeDeckFromMineRequest({ deckId: deck.id }) .then( action(() => { screenStore.go({ type: "main" }); @@ -490,17 +514,12 @@ export class DeckListStore { }) .finally( action(() => { - this.isDeckRemoving = false; + this.isFullScreenLoaderVisible = false; }), ); } - optimisticUpdateSettings(body: Partial) { - assert(this.myInfo, "myInfo is not loaded in optimisticUpdateSettings"); - Object.assign(this.myInfo.user, body); - } - - optimisticUpdateFolders(body: UserFoldersDbType[]) { + updateFolders(body: UserFoldersDbType[]) { assert(this.myInfo, "myInfo is not loaded in optimisticUpdateFolders"); Object.assign(this.myInfo.folders, body); } diff --git a/src/store/user-store.ts b/src/store/user-store.ts new file mode 100644 index 00000000..701c8916 --- /dev/null +++ b/src/store/user-store.ts @@ -0,0 +1,38 @@ +import { makeAutoObservable } from "mobx"; +import { UserDbType } from "../../functions/db/user/upsert-user-db.ts"; +import { assert } from "../lib/typescript/assert.ts"; + +export class UserStore { + userInfo?: UserDbType; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + } + + setUser(user: UserDbType) { + this.userInfo = user; + } + + get user() { + return this.userInfo ?? null; + } + + get myId() { + return this.user?.id; + } + + get isAdmin() { + return this.user?.is_admin ?? false; + } + + get isSpeakingCardsEnabled() { + return this.user?.is_speaking_card_enabled ?? false; + } + + updateSettings(body: Partial) { + assert(this.userInfo, "myInfo is not loaded in optimisticUpdateSettings"); + Object.assign(this.userInfo, body); + } +} + +export const userStore = new UserStore();