Skip to content

Commit

Permalink
Allow to remove card
Browse files Browse the repository at this point in the history
  • Loading branch information
kubk committed Jan 11, 2024
1 parent 0048727 commit 8ba027e
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 35 deletions.
19 changes: 19 additions & 0 deletions functions/db/card/delete-cards-in-ids.ts
Original file line number Diff line number Diff line change
@@ -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 deleteCardsInIds = async (
env: EnvSafe,
cardsToRemoveIds: number[],
) => {
const db = getDatabase(env);

const deleteCardsResult = await db
.from("deck_card")
.delete()
.in("id", cardsToRemoveIds);

if (deleteCardsResult.error) {
throw new DatabaseException(deleteCardsResult.error);
}
};
7 changes: 6 additions & 1 deletion functions/upsert-deck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
CardToReviewDbType,
getCardsToReviewDb,
} from "./db/deck/get-cards-to-review-db.ts";
import { deleteCardsInIds } from "./db/card/delete-cards-in-ids.ts";

const requestSchema = z.object({
id: z.number().nullable().optional(),
Expand All @@ -33,6 +34,7 @@ const requestSchema = z.object({
speakLocale: z.string().nullable().optional(),
speakField: z.string().nullable().optional(),
folderId: z.number().nullable().optional(),
cardsToRemoveIds: z.array(z.number()).optional(),
cards: z.array(
z.object({
front: z.string(),
Expand Down Expand Up @@ -95,7 +97,6 @@ export const onRequestPost = handleError(async ({ request, env }) => {
throw new DatabaseException(upsertDeckResult.error);
}

// Supabase returns an array as a result of upsert, that's why it gets validated against an array here
const upsertedDeck = deckSchema.parse(upsertDeckResult.data);

const updateCardsResult = await db.from("deck_card").upsert(
Expand Down Expand Up @@ -129,6 +130,10 @@ export const onRequestPost = handleError(async ({ request, env }) => {
throw new DatabaseException(createCardsResult.error);
}

if (input.data.id && input.data.cardsToRemoveIds?.length) {
await deleteCardsInIds(envSafe, input.data.cardsToRemoveIds);
}

// If create deck
if (!input.data.id) {
await addDeckToMineDb(envSafe, {
Expand Down
36 changes: 28 additions & 8 deletions src/screens/deck-form/card-form-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ import { CardFormType } from "./store/deck-form-store.ts";
import { HintTransparent } from "../../ui/hint-transparent.tsx";
import { t } from "../../translations/t.ts";
import { Screen } from "../shared/screen.tsx";
import { CenteredUnstyledButton } from "../../ui/centered-unstyled-button.tsx";
import { isFormValid } from "../../lib/mobx-form/form-has-error.ts";
import { css } from "@emotion/css";
import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx";
import { ButtonGrid } from "../../ui/button-grid.tsx";

type Props = {
cardForm: CardFormType;
onPreviewClick: () => void;
onDeleteClick?: () => void;
};

export const CardFormView = observer((props: Props) => {
const { cardForm, onPreviewClick } = props;
const { cardForm, onPreviewClick, onDeleteClick } = props;

return (
<Screen title={cardForm ? t("edit_card") : t("add_card")}>
<Screen title={cardForm.id ? t("edit_card") : t("add_card")}>
<Label text={t("card_front_title")} isRequired>
<Input field={cardForm.front} rows={3} type={"textarea"} />
<HintTransparent>{t("card_front_side_hint")}</HintTransparent>
Expand All @@ -34,11 +37,28 @@ export const CardFormView = observer((props: Props) => {
<HintTransparent>{t("card_field_example_hint")}</HintTransparent>
</Label>

{isFormValid(cardForm) && (
<CenteredUnstyledButton onClick={onPreviewClick}>
{t("card_preview")}
</CenteredUnstyledButton>
)}
<div className={css({ marginTop: 12 })}>
<ButtonGrid>
{isFormValid(cardForm) && (
<ButtonSideAligned
icon={"mdi-eye-check-outline mdi-24px"}
outline
onClick={onPreviewClick}
>
{t("card_preview")}
</ButtonSideAligned>
)}
{onDeleteClick && cardForm.id && (
<ButtonSideAligned
icon={"mdi-delete-outline mdi-24px"}
outline
onClick={onDeleteClick}
>
{t("delete")}
</ButtonSideAligned>
)}
</ButtonGrid>
</div>
</Screen>
);
});
5 changes: 2 additions & 3 deletions src/screens/deck-form/card-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ export const CardForm = observer(() => {
return (
<CardFormView
cardForm={cardForm}
onPreviewClick={() => {
deckFormStore.isCardPreviewSelected.setTrue();
}}
onPreviewClick={deckFormStore.isCardPreviewSelected.setTrue}
onDeleteClick={deckFormStore.markCardAsRemoved}
/>
);
});
43 changes: 40 additions & 3 deletions src/screens/deck-form/store/deck-form-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type DeckFormType = {
speakingCardsLocale: TextField<string | null>;
speakingCardsField: TextField<DeckSpeakFieldEnum | null>;
folderId?: number;
cardsToRemoveIds: number[];
};

export const createDeckTitleField = (value: string) => {
Expand All @@ -62,6 +63,7 @@ const createUpdateForm = (
back: createCardSideField(card.back),
example: new TextField(card.example || ""),
})),
cardsToRemoveIds: [],
};
};

Expand Down Expand Up @@ -127,6 +129,7 @@ export class DeckFormStore {
speakingCardsLocale: new TextField(null),
speakingCardsField: new TextField(null),
folderId: screen.folder?.id ?? undefined,
cardsToRemoveIds: [],
};
}
}
Expand Down Expand Up @@ -278,7 +281,7 @@ export class DeckFormStore {
return;
}

this.onDeckSave()?.finally(
this.onDeckSave().finally(
action(() => {
this.cardFormIndex = undefined;
this.cardFormType = undefined;
Expand Down Expand Up @@ -312,16 +315,49 @@ export class DeckFormStore {
}
}

async markCardAsRemoved() {
const result = await showConfirm(t("deck_form_remove_card_confirm"));
if (!result) {
return;
}

const selectedCard = this.cardForm;
if (!selectedCard) {
return;
}
assert(this.form, "markCardAsRemoved: form is empty");
if (!selectedCard.id) {
return;
}
this.form.cardsToRemoveIds.push(selectedCard.id);

deckListStore.isFullScreenLoaderVisible = true;

this.onDeckSave()
.then(
action(() => {
this.isCardList = true;
this.cardFormIndex = undefined;
this.cardFormType = undefined;
}),
)
.finally(
action(() => {
deckListStore.isFullScreenLoaderVisible = false;
}),
);
}

onDeckSave() {
assert(this.form, "onDeckSave: form is empty");

if (this.form.cards.length === 0) {
showAlert(t("deck_form_no_cards_alert"));
return;
return Promise.reject();
}

if (!isFormValid(this.form)) {
return;
return Promise.reject();
}
this.isSending = true;

Expand All @@ -341,6 +377,7 @@ export class DeckFormStore {
speakLocale: this.form.speakingCardsLocale.value,
speakField: this.form.speakingCardsField.value,
folderId: this.form.folderId,
cardsToRemoveIds: this.form.cardsToRemoveIds,
})
.then(
action(({ deck, folders, cardsToReview }) => {
Expand Down
11 changes: 3 additions & 8 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.ts
import { duplicateDeckRequest } from "../../api/api.ts";
import { t } from "../../translations/t.ts";
import { userStore } from "../../store/user-store.ts";
import { ButtonGrid } from "../../ui/button-grid.tsx";

export const DeckPreview = observer(() => {
const reviewStore = useReviewStore();
Expand Down Expand Up @@ -122,13 +123,7 @@ export const DeckPreview = observer(() => {
</div>
)}

<div
className={css({
gap: 16,
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
})}
>
<ButtonGrid>
{deckListStore.canEditDeck ? (
<ButtonSideAligned
icon={"mdi-plus-circle mdi-24px"}
Expand Down Expand Up @@ -199,7 +194,7 @@ export const DeckPreview = observer(() => {
{t("delete")}
</ButtonSideAligned>
) : null}
</div>
</ButtonGrid>
</div>
{deck.cardsToReview.length === 0 && (
<Hint>{t("no_cards_to_review_in_deck")}</Hint>
Expand Down
11 changes: 3 additions & 8 deletions src/screens/folder-review/folder-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ListHeader } from "../../ui/list-header.tsx";
import { assert } from "../../lib/typescript/assert.ts";
import { userStore } from "../../store/user-store.ts";
import { DeckRowWithCardsToReview } from "../shared/deck-row-with-cards-to-review/deck-row-with-cards-to-review.tsx";
import { ButtonGrid } from "../../ui/button-grid.tsx";

export const FolderPreview = observer(() => {
const reviewStore = useReviewStore();
Expand Down Expand Up @@ -126,13 +127,7 @@ export const FolderPreview = observer(() => {
</div>
)}

<div
className={css({
gap: 16,
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
})}
>
<ButtonGrid>
{deckListStore.canEditFolder ? (
<ButtonSideAligned
icon={"mdi-plus-circle mdi-24px"}
Expand Down Expand Up @@ -187,7 +182,7 @@ export const FolderPreview = observer(() => {
>
{t("delete")}
</ButtonSideAligned>
</div>
</ButtonGrid>
</div>
<div
className={css({
Expand Down
16 changes: 12 additions & 4 deletions src/translations/t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const en = {
add_card: "Add card",
edit_card: "Edit card",
deck_preview: "Deck preview",
card_preview: "Card preview",
card_preview: "Preview",
add_card_short: "Add card",
add_deck_short: "Deck",
card_front_title: "Front side",
Expand Down Expand Up @@ -81,6 +81,8 @@ const en = {
duplicate_confirm: "Are you sure to duplicate this deck?",
delete_deck_confirm:
"Are you sure to remove the deck from your collection? This action can't be undone",
deck_form_remove_card_confirm:
"Are you sure you want to remove the card? All the card reviews from all the users will be lost",
delete: "Delete",
no_cards_to_review_in_deck: `Amazing work! 🌟 You've reviewed all the cards in this deck for now. Come back later for more.`,
no_cards_to_review_all: `Amazing work! 🌟 You've repeated all the cards for today. Come back later for more.`,
Expand Down Expand Up @@ -137,7 +139,7 @@ const ru: Translation = {
share_no_links_for_folder:
"Вы еще не создали одноразовых ссылок для этой папки",
choose_what_to_create: "Выберите что создать",
card_preview: "Предпросмотр карточки",
card_preview: "Предпросмотр",
deck: "Колода",
add_deck_short: "Колода",
deck_description: "Коллекция карточек",
Expand Down Expand Up @@ -174,6 +176,8 @@ const ru: Translation = {
category: "Категория",
any_category: "Любая",
deck_search_not_found: "Нет подходящих колод",
deck_form_remove_card_confirm:
"Вы уверены, что хотите удалить карточку? Все повторения этой карточки будут удалены у всех пользователей",
deck_search_not_found_description:
"Попробуйте изменить фильтры чтобы увидеть больше",
category_English: "Английский",
Expand Down Expand Up @@ -261,7 +265,7 @@ const ru: Translation = {
};

const es: Translation = {
card_preview: "Vista previa de la tarjeta",
card_preview: "Vista previa",
share_no_links_for_folder:
"No has creado ningún enlace de un solo uso para esta carpeta",
share_folder_settings: "Compartir carpeta",
Expand All @@ -287,6 +291,8 @@ const es: Translation = {
no_personal_decks_explore:
"o explorar los mazos públicos a continuación. ¡Feliz aprendizaje! 😊",
add_deck: "Añadir mazo",
deck_form_remove_card_confirm:
"¿Estás seguro de que quieres eliminar la tarjeta? Todas las revisiones de tarjetas de todos los usuarios se perderán",
edit_deck: "Editar mazo",
edit_card: "Editar tarjeta",
add: "Añadir",
Expand Down Expand Up @@ -395,7 +401,7 @@ const es: Translation = {
const ptBr: Translation = {
share_folder_settings: "Compartilhar pasta",
share_no_links_for_folder: "Você ainda não criou nenhum link para esta pasta",
card_preview: "Visualização do cartão",
card_preview: "Visualização",
review_folder: "Revisar pasta",
add_deck_short: "Baralho",
choose_what_to_create: "Escolha o que criar",
Expand All @@ -414,6 +420,8 @@ const ptBr: Translation = {
deck_preview: "Visualização do baralho",
no_personal_decks_start:
"Você ainda não tem nenhum baralho pessoal. Sinta-se à vontade para",
deck_form_remove_card_confirm:
"Tem certeza de que deseja remover o cartão? Todas as revisões de cartões de todos os usuários serão perdidas",
no_personal_decks_create: "criar um",
no_personal_decks_explore:
"ou explorar os baralhos públicos abaixo. Bom aprendizado! 😊",
Expand Down
19 changes: 19 additions & 0 deletions src/ui/button-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from "@emotion/css";
import { ReactNode } from "react";

type Props = { children: ReactNode };

export const ButtonGrid = (props: Props) => {
const { children } = props;
return (
<div
className={css({
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: 10,
})}
>
{children}
</div>
);
};

0 comments on commit 8ba027e

Please sign in to comment.