chore: add CommonDialog

This commit is contained in:
boojack 2022-07-28 20:19:14 +08:00
parent 5617118fa8
commit 2b8078a19b
20 changed files with 220 additions and 196 deletions

View File

@ -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<Props> = ({ destroy }: Props) => {
};
export default function showAboutSiteDialog(): void {
showDialog(
generateDialog(
{
className: "about-site-dialog",
},

View File

@ -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: Props) => {
};
export default function showArchivedMemo(): void {
showDialog(
generateDialog(
{
className: "archived-memo-dialog",
useAppContext: true,

View File

@ -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<Props> = ({ destroy }: Props) => {
};
function showChangePasswordDialog() {
showDialog(
generateDialog(
{
className: "change-password-dialog",
},

View File

@ -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<Props> = ({ 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 (
<>
<div className="dialog-header-container">
<p className="title-text">Reset Open API</p>
<button className="btn close-btn" onClick={handleCloseBtnClick}>
<i className="fa-solid fa-xmark fa-lg"></i>
</button>
</div>
<div className="dialog-content-container">
<p className="warn-text">
The existing API will be invalidated and a new one will be generated, are you sure you want to reset?
</p>
<div className="btns-container">
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
Cancel
</span>
<span className={`btn confirm-btn ${resetBtnClickLoadingState.isLoading ? "loading" : ""}`} onClick={handleConfirmBtnClick}>
Reset!
</span>
</div>
</div>
</>
);
};
function showConfirmResetOpenIdDialog() {
showDialog(
{
className: "confirm-reset-openid-dialog",
},
ConfirmResetOpenIdDialog
);
}
export default showConfirmResetOpenIdDialog;

View File

@ -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<MemoFilterInputerProps> = (props: MemoFilterInpute
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer);
export default function showCreateShortcutDialog(shortcutId?: ShortcutId): void {
showDialog(
generateDialog(
{
className: "create-shortcut-dialog",
},

View File

@ -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: Props) => {
};
export default function showDailyReviewDialog(datestamp: DateStamp = Date.now()): void {
showDialog(
generateDialog(
{
className: "daily-review-dialog",
useAppContext: true,

View File

@ -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: Props) => {
);
};
export function showDialog<T extends DialogProps>(
export function generateDialog<T extends DialogProps>(
config: DialogConfig,
DialogComponent: React.FC<T>,
props?: Omit<T, "destroy">

View File

@ -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: Props) => {
const { title, content, destroy, closeBtnText, confirmBtnText, onClose, onConfirm, style } = {
...defaultProps,
...props,
};
const handleCloseBtnClick = () => {
onClose();
destroy();
};
const handleConfirmBtnClick = async () => {
onConfirm();
destroy();
};
return (
<>
<div className="dialog-header-container">
<p className="title-text">{title}</p>
<button className="btn close-btn" onClick={handleCloseBtnClick}>
<i className="fa-solid fa-xmark fa-lg"></i>
</button>
</div>
<div className="dialog-content-container">
<p className="content-text">{content}</p>
<div className="btns-container">
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
{closeBtnText}
</span>
<span className={`btn confirm-btn ${style}`} onClick={handleConfirmBtnClick}>
{confirmBtnText}
</span>
</div>
</div>
</>
);
};
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
);
};

View File

@ -0,0 +1 @@
export { generateDialog } from "./BaseDialog";

View File

@ -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: Props) => {
return (
<>
<Only when={!userService.isVisitorMode()}>
<div className="visibility-selector-container">
<i className="fa-solid fa-eye text-white mr-2"></i>
<Selector
className="w-32"
dataSource={visibilityList}
value={memo.visibility}
handleValueChanged={(value) => handleVisibilitySelectorChange(value as Visibility)}
/>
<div className="card-header-container">
<div className="visibility-selector-container">
<i className="fa-solid fa-eye mr-2"></i>
<Selector
className="visibility-selector"
dataSource={visibilityList}
value={memo.visibility}
handleValueChanged={(value) => handleVisibilitySelectorChange(value as Visibility)}
/>
</div>
</div>
</Only>
<div className="memo-card-container">
@ -221,7 +223,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
};
export default function showMemoCardDialog(memo: Memo): void {
showDialog(
generateDialog(
{
className: "memo-card-dialog",
},

View File

@ -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<Props> = ({ destroy, imgUrl }: Props) => {
};
export default function showPreviewImageDialog(imgUrl: string): void {
showDialog(
generateDialog(
{
className: "preview-image-dialog",
},

View File

@ -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: Props) => {
};
export default function showSettingDialog(): void {
showDialog(
generateDialog(
{
className: "setting-dialog",
useAppContext: true,

View File

@ -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<Props> = () => {
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 (

View File

@ -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<Props> = () => {
};
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) => {

View File

@ -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: Props) => {
};
export default function showShareMemoImageDialog(memo: Memo): void {
showDialog(
generateDialog(
{
className: "share-memo-image-dialog",
},

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}
}
}
}

View File

@ -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;
}
}
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 {