From 2b8078a19b9c75634bcb6d43df96774f74c5a394 Mon Sep 17 00:00:00 2001 From: boojack Date: Thu, 28 Jul 2022 20:19:14 +0800 Subject: [PATCH] chore: add `CommonDialog` --- web/src/components/AboutSiteDialog.tsx | 4 +- web/src/components/ArchivedMemoDialog.tsx | 4 +- web/src/components/ChangePasswordDialog.tsx | 4 +- .../components/ConfirmResetOpenIdDialog.tsx | 75 ----------------- web/src/components/CreateShortcutDialog.tsx | 4 +- web/src/components/DailyReviewDialog.tsx | 4 +- .../{Dialog.tsx => Dialog/BaseDialog.tsx} | 8 +- web/src/components/Dialog/CommonDialog.tsx | 84 +++++++++++++++++++ web/src/components/Dialog/index.ts | 1 + web/src/components/MemoCardDialog.tsx | 22 ++--- web/src/components/PreviewImageDialog.tsx | 4 +- web/src/components/SettingDialog.tsx | 4 +- web/src/components/Settings/MemberSection.tsx | 17 ++-- .../components/Settings/MyAccountSection.tsx | 8 +- web/src/components/ShareMemoImageDialog.tsx | 4 +- web/src/less/base-dialog.less | 40 +++++++++ web/src/less/common-dialog.less | 31 +++++++ web/src/less/confirm-reset-openid-dialog.less | 36 -------- web/src/less/dialog.less | 46 ---------- web/src/less/memo-card-dialog.less | 16 +++- 20 files changed, 220 insertions(+), 196 deletions(-) delete mode 100644 web/src/components/ConfirmResetOpenIdDialog.tsx rename web/src/components/{Dialog.tsx => Dialog/BaseDialog.tsx} (90%) create mode 100644 web/src/components/Dialog/CommonDialog.tsx create mode 100644 web/src/components/Dialog/index.ts create mode 100644 web/src/less/base-dialog.less create mode 100644 web/src/less/common-dialog.less delete mode 100644 web/src/less/confirm-reset-openid-dialog.less delete mode 100644 web/src/less/dialog.less diff --git a/web/src/components/AboutSiteDialog.tsx b/web/src/components/AboutSiteDialog.tsx index f43cd89e..28813494 100644 --- a/web/src/components/AboutSiteDialog.tsx +++ b/web/src/components/AboutSiteDialog.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import * as api from "../helpers/api"; import Only from "./common/OnlyWhen"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import GitHubBadge from "./GitHubBadge"; import "../less/about-site-dialog.less"; @@ -63,7 +63,7 @@ const AboutSiteDialog: React.FC = ({ destroy }: Props) => { }; export default function showAboutSiteDialog(): void { - showDialog( + generateDialog( { className: "about-site-dialog", }, diff --git a/web/src/components/ArchivedMemoDialog.tsx b/web/src/components/ArchivedMemoDialog.tsx index f3c3177e..2616cfe3 100644 --- a/web/src/components/ArchivedMemoDialog.tsx +++ b/web/src/components/ArchivedMemoDialog.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import useLoading from "../hooks/useLoading"; import { memoService } from "../services"; import { useAppSelector } from "../store"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import toastHelper from "./Toast"; import ArchivedMemo from "./ArchivedMemo"; import "../less/archived-memo-dialog.less"; @@ -62,7 +62,7 @@ const ArchivedMemoDialog: React.FC = (props: Props) => { }; export default function showArchivedMemo(): void { - showDialog( + generateDialog( { className: "archived-memo-dialog", useAppContext: true, diff --git a/web/src/components/ChangePasswordDialog.tsx b/web/src/components/ChangePasswordDialog.tsx index 01641281..bdbc6776 100644 --- a/web/src/components/ChangePasswordDialog.tsx +++ b/web/src/components/ChangePasswordDialog.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { validate, ValidatorConfig } from "../helpers/validator"; import { userService } from "../services"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import toastHelper from "./Toast"; import "../less/change-password-dialog.less"; @@ -96,7 +96,7 @@ const ChangePasswordDialog: React.FC = ({ destroy }: Props) => { }; function showChangePasswordDialog() { - showDialog( + generateDialog( { className: "change-password-dialog", }, diff --git a/web/src/components/ConfirmResetOpenIdDialog.tsx b/web/src/components/ConfirmResetOpenIdDialog.tsx deleted file mode 100644 index f095ac79..00000000 --- a/web/src/components/ConfirmResetOpenIdDialog.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useEffect } from "react"; -import { userService } from "../services"; -import useLoading from "../hooks/useLoading"; -import { showDialog } from "./Dialog"; -import toastHelper from "./Toast"; -import "../less/confirm-reset-openid-dialog.less"; - -interface Props extends DialogProps {} - -const ConfirmResetOpenIdDialog: React.FC = ({ destroy }: Props) => { - const resetBtnClickLoadingState = useLoading(false); - - useEffect(() => { - // do nth - }, []); - - const handleCloseBtnClick = () => { - destroy(); - }; - - const handleConfirmBtnClick = async () => { - if (resetBtnClickLoadingState.isLoading) { - return; - } - - resetBtnClickLoadingState.setLoading(); - try { - const user = userService.getState().user as User; - await userService.patchUser({ - id: user.id, - resetOpenId: true, - }); - } catch (error) { - toastHelper.error("Request reset open API failed."); - return; - } - toastHelper.success("Reset open API succeeded."); - handleCloseBtnClick(); - }; - - return ( - <> -
-

Reset Open API

- -
-
-

- ❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset? -

-
- - Cancel - - - Reset! - -
-
- - ); -}; - -function showConfirmResetOpenIdDialog() { - showDialog( - { - className: "confirm-reset-openid-dialog", - }, - ConfirmResetOpenIdDialog - ); -} - -export default showConfirmResetOpenIdDialog; diff --git a/web/src/components/CreateShortcutDialog.tsx b/web/src/components/CreateShortcutDialog.tsx index e2265c0b..72bead74 100644 --- a/web/src/components/CreateShortcutDialog.tsx +++ b/web/src/components/CreateShortcutDialog.tsx @@ -2,7 +2,7 @@ import { memo, useCallback, useEffect, useState } from "react"; import { memoService, shortcutService } from "../services"; import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter"; import useLoading from "../hooks/useLoading"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import toastHelper from "./Toast"; import Selector from "./common/Selector"; import "../less/create-shortcut-dialog.less"; @@ -304,7 +304,7 @@ const FilterInputer: React.FC = (props: MemoFilterInpute const MemoFilterInputer: React.FC = memo(FilterInputer); export default function showCreateShortcutDialog(shortcutId?: ShortcutId): void { - showDialog( + generateDialog( { className: "create-shortcut-dialog", }, diff --git a/web/src/components/DailyReviewDialog.tsx b/web/src/components/DailyReviewDialog.tsx index 73225b90..55097981 100644 --- a/web/src/components/DailyReviewDialog.tsx +++ b/web/src/components/DailyReviewDialog.tsx @@ -4,7 +4,7 @@ import toImage from "../labs/html2image"; import useToggle from "../hooks/useToggle"; import { DAILY_TIMESTAMP } from "../helpers/consts"; import * as utils from "../helpers/utils"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import DatePicker from "./common/DatePicker"; import showPreviewImageDialog from "./PreviewImageDialog"; import DailyMemo from "./DailyMemo"; @@ -108,7 +108,7 @@ const DailyReviewDialog: React.FC = (props: Props) => { }; export default function showDailyReviewDialog(datestamp: DateStamp = Date.now()): void { - showDialog( + generateDialog( { className: "daily-review-dialog", useAppContext: true, diff --git a/web/src/components/Dialog.tsx b/web/src/components/Dialog/BaseDialog.tsx similarity index 90% rename from web/src/components/Dialog.tsx rename to web/src/components/Dialog/BaseDialog.tsx index b2af2314..bdc46520 100644 --- a/web/src/components/Dialog.tsx +++ b/web/src/components/Dialog/BaseDialog.tsx @@ -1,8 +1,8 @@ import { createRoot } from "react-dom/client"; -import { ANIMATION_DURATION } from "../helpers/consts"; import { Provider } from "react-redux"; -import store from "../store"; -import "../less/dialog.less"; +import { ANIMATION_DURATION } from "../../helpers/consts"; +import store from "../../store"; +import "../../less/base-dialog.less"; interface DialogConfig { className: string; @@ -32,7 +32,7 @@ const BaseDialog: React.FC = (props: Props) => { ); }; -export function showDialog( +export function generateDialog( config: DialogConfig, DialogComponent: React.FC, props?: Omit diff --git a/web/src/components/Dialog/CommonDialog.tsx b/web/src/components/Dialog/CommonDialog.tsx new file mode 100644 index 00000000..9e54c83f --- /dev/null +++ b/web/src/components/Dialog/CommonDialog.tsx @@ -0,0 +1,84 @@ +import { generateDialog } from "./BaseDialog"; +import "../../less/common-dialog.less"; + +type DialogStyle = "info" | "warning"; + +interface Props extends DialogProps { + title: string; + content: string; + style?: DialogStyle; + closeBtnText?: string; + confirmBtnText?: string; + onClose?: () => void; + onConfirm?: () => void; +} + +const defaultProps = { + title: "", + content: "", + style: "info", + closeBtnText: "Close", + confirmBtnText: "Confirm", + onClose: () => null, + onConfirm: () => null, +}; + +const CommonDialog: React.FC = (props: Props) => { + const { title, content, destroy, closeBtnText, confirmBtnText, onClose, onConfirm, style } = { + ...defaultProps, + ...props, + }; + + const handleCloseBtnClick = () => { + onClose(); + destroy(); + }; + + const handleConfirmBtnClick = async () => { + onConfirm(); + destroy(); + }; + + return ( + <> +
+

{title}

+ +
+
+

{content}

+
+ + {closeBtnText} + + + {confirmBtnText} + +
+
+ + ); +}; + +interface CommonDialogProps { + title: string; + content: string; + className?: string; + style?: DialogStyle; + closeBtnText?: string; + confirmBtnText?: string; + onClose?: () => void; + onConfirm?: () => void; +} + +export const showCommonDialog = (props: CommonDialogProps) => { + generateDialog( + { + className: `common-dialog ${props?.className ?? ""}`, + }, + CommonDialog, + props + ); +}; diff --git a/web/src/components/Dialog/index.ts b/web/src/components/Dialog/index.ts new file mode 100644 index 00000000..d6e65076 --- /dev/null +++ b/web/src/components/Dialog/index.ts @@ -0,0 +1 @@ +export { generateDialog } from "./BaseDialog"; diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx index 2c197b1e..60065e70 100644 --- a/web/src/components/MemoCardDialog.tsx +++ b/web/src/components/MemoCardDialog.tsx @@ -5,7 +5,7 @@ import * as utils from "../helpers/utils"; import { parseHtmlToRawText } from "../helpers/marked"; import Only from "./common/OnlyWhen"; import toastHelper from "./Toast"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import Image from "./Image"; import { formatMemoContent } from "./Memo"; import "../less/memo-card-dialog.less"; @@ -127,14 +127,16 @@ const MemoCardDialog: React.FC = (props: Props) => { return ( <> -
- - handleVisibilitySelectorChange(value as Visibility)} - /> +
+
+ + handleVisibilitySelectorChange(value as Visibility)} + /> +
@@ -221,7 +223,7 @@ const MemoCardDialog: React.FC = (props: Props) => { }; export default function showMemoCardDialog(memo: Memo): void { - showDialog( + generateDialog( { className: "memo-card-dialog", }, diff --git a/web/src/components/PreviewImageDialog.tsx b/web/src/components/PreviewImageDialog.tsx index 41a3ca0d..7e6f07d2 100644 --- a/web/src/components/PreviewImageDialog.tsx +++ b/web/src/components/PreviewImageDialog.tsx @@ -1,4 +1,4 @@ -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import * as utils from "../helpers/utils"; import "../less/preview-image-dialog.less"; @@ -36,7 +36,7 @@ const PreviewImageDialog: React.FC = ({ destroy, imgUrl }: Props) => { }; export default function showPreviewImageDialog(imgUrl: string): void { - showDialog( + generateDialog( { className: "preview-image-dialog", }, diff --git a/web/src/components/SettingDialog.tsx b/web/src/components/SettingDialog.tsx index b092583f..eb2111f2 100644 --- a/web/src/components/SettingDialog.tsx +++ b/web/src/components/SettingDialog.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useAppSelector } from "../store"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import MyAccountSection from "./Settings/MyAccountSection"; import PreferencesSection from "./Settings/PreferencesSection"; import MemberSection from "./Settings/MemberSection"; @@ -76,7 +76,7 @@ const SettingDialog: React.FC = (props: Props) => { }; export default function showSettingDialog(): void { - showDialog( + generateDialog( { className: "setting-dialog", useAppContext: true, diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index 995e7d8d..30cb7ef4 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -5,6 +5,7 @@ import { useAppSelector } from "../../store"; import * as api from "../../helpers/api"; import toastHelper from "../Toast"; import "../../less/settings/member-section.less"; +import { showCommonDialog } from "../Dialog/CommonDialog"; interface Props {} @@ -85,12 +86,18 @@ const PreferencesSection: React.FC = () => { fetchUserList(); }; - // TODO: show a dialog to confirm delete user. - const handleDeleteUserClick = async (user: User) => { - await userService.deleteUser({ - id: user.id, + const handleDeleteUserClick = (user: User) => { + showCommonDialog({ + title: `Delete ${user.name}`, + content: "❗️Are you sure you want to delete?", + style: "warning", + onConfirm: async () => { + await userService.deleteUser({ + id: user.id, + }); + fetchUserList(); + }, }); - fetchUserList(); }; return ( diff --git a/web/src/components/Settings/MyAccountSection.tsx b/web/src/components/Settings/MyAccountSection.tsx index f0203446..92baca97 100644 --- a/web/src/components/Settings/MyAccountSection.tsx +++ b/web/src/components/Settings/MyAccountSection.tsx @@ -3,8 +3,8 @@ import { useAppSelector } from "../../store"; import { userService } from "../../services"; import { validate, ValidatorConfig } from "../../helpers/validator"; import toastHelper from "../Toast"; +import { showCommonDialog } from "../Dialog/CommonDialog"; import showChangePasswordDialog from "../ChangePasswordDialog"; -import showConfirmResetOpenIdDialog from "../ConfirmResetOpenIdDialog"; import "../../less/settings/my-account-section.less"; const validateConfig: ValidatorConfig = { @@ -53,7 +53,11 @@ const MyAccountSection: React.FC = () => { }; const handleResetOpenIdBtnClick = async () => { - showConfirmResetOpenIdDialog(); + showCommonDialog({ + title: "Reset Open API", + content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?", + style: "warning", + }); }; const handlePreventDefault = (e: React.MouseEvent) => { diff --git a/web/src/components/ShareMemoImageDialog.tsx b/web/src/components/ShareMemoImageDialog.tsx index 715c1f85..49bdc81f 100644 --- a/web/src/components/ShareMemoImageDialog.tsx +++ b/web/src/components/ShareMemoImageDialog.tsx @@ -3,7 +3,7 @@ import { userService } from "../services"; import toImage from "../labs/html2image"; import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts"; import * as utils from "../helpers/utils"; -import { showDialog } from "./Dialog"; +import { generateDialog } from "./Dialog"; import Only from "./common/OnlyWhen"; import toastHelper from "./Toast"; import { formatMemoContent } from "./Memo"; @@ -107,7 +107,7 @@ const ShareMemoImageDialog: React.FC = (props: Props) => { }; export default function showShareMemoImageDialog(memo: Memo): void { - showDialog( + generateDialog( { className: "share-memo-image-dialog", }, diff --git a/web/src/less/base-dialog.less b/web/src/less/base-dialog.less new file mode 100644 index 00000000..b636da06 --- /dev/null +++ b/web/src/less/base-dialog.less @@ -0,0 +1,40 @@ +@import "./mixin.less"; + +.dialog-wrapper { + @apply fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all; + .hide-scroll-bar(); + + &.showup { + background-color: rgba(0, 0, 0, 0.6); + } + + &.showoff { + display: none; + } + + > .dialog-container { + @apply flex flex-col justify-start items-start bg-white p-4 rounded-lg; + + > .dialog-header-container { + @apply flex flex-row justify-between items-center w-full mb-4; + + > .title-text { + > .icon-text { + @apply mr-2 text-base; + } + } + + .btn { + @apply flex flex-col justify-center items-center w-6 h-6 rounded hover:bg-gray-100 hover:shadow; + } + } + + > .dialog-content-container { + @apply flex flex-col justify-start items-start w-full; + } + + > .dialog-footer-container { + @apply flex flex-row justify-end items-center w-full mt-4; + } + } +} diff --git a/web/src/less/common-dialog.less b/web/src/less/common-dialog.less new file mode 100644 index 00000000..87627903 --- /dev/null +++ b/web/src/less/common-dialog.less @@ -0,0 +1,31 @@ +@import "./mixin.less"; + +.common-dialog { + > .dialog-container { + @apply w-80; + + > .dialog-content-container { + @apply flex flex-col justify-start items-start; + + > .content-text { + @apply pt-2; + } + + > .btns-container { + @apply flex flex-row justify-end items-center w-full mt-3; + + > .btn { + @apply text-sm py-1 px-3 mr-2 rounded-md hover:opacity-80; + + &.confirm-btn { + @apply bg-red-100 border border-solid border-blue-600 text-blue-600; + + &.warning { + @apply border-red-600 text-red-600; + } + } + } + } + } + } +} diff --git a/web/src/less/confirm-reset-openid-dialog.less b/web/src/less/confirm-reset-openid-dialog.less deleted file mode 100644 index 2af5311d..00000000 --- a/web/src/less/confirm-reset-openid-dialog.less +++ /dev/null @@ -1,36 +0,0 @@ -@import "./mixin.less"; - -.confirm-reset-openid-dialog { - > .dialog-container { - @apply w-80; - - > .dialog-content-container { - .flex(column, flex-start, flex-start); - - > .warn-text { - @apply pt-2; - } - - > .btns-container { - .flex(row, flex-end, center); - @apply w-full mt-3; - - > .btn { - @apply text-sm py-1 px-3 mr-2 rounded-md; - - &:hover { - @apply opacity-80; - } - - &.confirm-btn { - @apply bg-red-100 border border-solid border-red-600 text-red-600; - - &.loading { - @apply opacity-80 cursor-wait; - } - } - } - } - } - } -} diff --git a/web/src/less/dialog.less b/web/src/less/dialog.less deleted file mode 100644 index 75bed808..00000000 --- a/web/src/less/dialog.less +++ /dev/null @@ -1,46 +0,0 @@ -@import "./mixin.less"; - -.dialog-wrapper { - .flex(column, flex-start, center); - @apply fixed top-0 left-0 w-full h-full pt-16 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all; - .hide-scroll-bar(); - - &.showup { - background-color: rgba(0, 0, 0, 0.6); - } - - &.showoff { - display: none; - } - - > .dialog-container { - .flex(column, flex-start, flex-start); - @apply bg-white p-4 rounded-lg; - - > .dialog-header-container { - .flex(row, space-between, center); - @apply w-full mb-4; - - > .title-text { - > .icon-text { - @apply mr-2 text-base; - } - } - - .btn { - .flex(column, center, center); - @apply w-6 h-6 rounded hover:bg-gray-100 hover:shadow; - } - } - - > .dialog-content-container { - .flex(column, flex-start, flex-start); - @apply w-full; - } - - > .dialog-footer-container { - .flex(row, flex-end, center); - @apply w-full mt-4; - } - } -} diff --git a/web/src/less/memo-card-dialog.less b/web/src/less/memo-card-dialog.less index aa5994a2..29d1f041 100644 --- a/web/src/less/memo-card-dialog.less +++ b/web/src/less/memo-card-dialog.less @@ -6,8 +6,20 @@ > .dialog-container { @apply w-full p-0 bg-transparent flex flex-col justify-start items-center; - > .visibility-selector-container { - @apply z-10 w-128 mb-2 flex flex-row justify-start items-center; + > .card-header-container { + @apply z-10 w-128 max-w-full flex flex-row justify-start items-center mb-2; + + > .visibility-selector-container { + @apply bg-white px-2 py-1 rounded-lg flex flex-row justify-start items-center; + + > .visibility-selector { + @apply w-32; + + > .current-value-container { + @apply border-none; + } + } + } } > .memo-card-container {