refactor: setting modals to pages

This commit is contained in:
zerosoul
2022-04-12 18:21:59 +08:00
parent bcfadfb2d4
commit 20373c4376
28 changed files with 473 additions and 477 deletions
@@ -1,46 +0,0 @@
// import React from "react";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { useNavigate, useMatch } from "react-router-dom";
import Modal from "../Modal";
// import BASE_URL from "../../app/config";
import { useLazyRemoveChannelQuery } from "../../../app/services/channel";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
export default function DeleteConfirmModal({ id, closeModal }) {
const navigateTo = useNavigate();
const pathMatched = useMatch(`/chat/channel/${id}`);
const [deleteChannel, { isLoading, isSuccess }] = useLazyRemoveChannelQuery();
const handleDelete = () => {
deleteChannel(id);
};
useEffect(() => {
if (isSuccess) {
toast.success("delete channel successfully!");
if (pathMatched) {
navigateTo("/chat");
}
closeModal();
}
}, [isSuccess, id, pathMatched]);
if (!id) return null;
return (
<Modal id="modal-modal">
<StyledModal
title="Delete Channel"
description="Are you sure want to delete this channel?"
buttons={
<>
{" "}
<Button onClick={closeModal.bind(null, undefined)}>Cancel</Button>
<Button onClick={handleDelete} className="danger">
{isLoading ? "Deleting" : `Delete`}
</Button>
</>
}
// className="animate__animated animate__fadeInDown animate__faster"
></StyledModal>
</Modal>
);
}
@@ -1,129 +0,0 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import toast from "react-hot-toast";
import {
useGetChannelQuery,
useUpdateChannelMutation,
} from "../../../app/services/channel";
import Input from "../styled/Input";
import Label from "../styled/Label";
import Textarea from "../styled/Textarea";
import SaveTip from "../SaveTip";
import channelIcon from "../../../assets/icons/channel.svg?url";
import { useSelector } from "react-redux";
const StyledWrapper = styled.div`
position: relative;
width: 512px;
height: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.inputs {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
.input {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.name {
padding-left: 36px;
background-image: url(${channelIcon});
background-size: 20px;
background-position-x: 8px;
background-position-y: 8px;
background-repeat: no-repeat;
}
}
}
`;
export default function Overview({ id = 0 }) {
const loginUser = useSelector(
(store) => store.contacts.byId[store.authData.uid]
);
const { data, refetch } = useGetChannelQuery(id);
const [changed, setChanged] = useState(false);
const [values, setValues] = useState(null);
const [updateChannel, { isSuccess: updated }] = useUpdateChannelMutation();
const handleUpdate = () => {
const { name, description } = values;
updateChannel({ id, name, description });
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
setValues((prev) => {
return { ...prev, [type]: newValue };
});
};
const handleReset = () => {
console.log("reset", data);
setValues(data);
};
useEffect(() => {
if (data) {
setValues(data);
}
}, [data]);
useEffect(() => {
if (data && values) {
const { name, description } = values;
const { name: oName, description: oDescription } = data;
if (oName !== name || oDescription !== description) {
setChanged(true);
} else {
setChanged(false);
}
}
}, [data, values]);
useEffect(() => {
if (updated) {
toast.success("Channel updated!");
refetch();
}
}, [updated]);
if (!values || !id) return null;
const { name, description } = values;
const isAdmin = loginUser.is_admin;
return (
<StyledWrapper>
<div className="inputs">
<div className="input">
<Label htmlFor="name">Channel Name</Label>
<Input
disabled={!isAdmin}
className="name"
data-type="name"
onChange={handleChange}
value={name}
name="name"
id="name"
placeholder="Channel Name"
/>
</div>
<div className="input">
<Label htmlFor="desc">Channel Topic</Label>
<Textarea
disabled={!isAdmin}
data-type="description"
onChange={handleChange}
value={description ?? ""}
rows={4}
name="name"
id="name"
placeholder="Let everyone know how to use this channel."
/>
</div>
</div>
{changed && (
<SaveTip saveHandler={handleUpdate} resetHandler={handleReset} />
)}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledWrapper>
);
}
@@ -1,52 +0,0 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { toggleChannelSetting } from "../../../app/slices/ui";
import StyledSettingContainer from "../StyledSettingContainer";
import DeleteConfirmModal from "./DeleteConfirmModal";
import useNavs from "./navs";
export default function ChannelSetting({ id = 0 }) {
const navs = useNavs(id);
const flatenNavs = navs
.map(({ items }) => {
return items;
})
.flat();
const [currNav, setCurrNav] = useState(flatenNavs[0]);
const [deleteConfirm, setDeleteConfirm] = useState(false);
const dispatch = useDispatch();
const close = () => {
dispatch(toggleChannelSetting());
};
const toggleDeleteConfrim = () => {
setDeleteConfirm((prev) => !prev);
};
const updateNav = (name) => {
const tmp = flatenNavs.find((n) => n.name == name);
if (tmp) {
setCurrNav(tmp);
}
};
if (!id) return null;
return (
<>
<StyledSettingContainer
updateNav={updateNav}
nav={currNav}
closeModal={close}
title="Channel Setting"
navs={navs}
dangers={[
{
title: "Delete Channel",
handler: toggleDeleteConfrim,
},
]}
>
{currNav.component}
</StyledSettingContainer>
{deleteConfirm && (
<DeleteConfirmModal closeModal={toggleDeleteConfrim} id={id} />
)}
</>
);
}
@@ -1,42 +0,0 @@
import Overview from "./Overview";
import ManageMembers from "../ManageMembers";
const useNavs = (channelId) => {
const navs = [
{
title: "General",
items: [
{
name: "overview",
title: "Overview",
component: <Overview id={channelId} />,
},
{
name: "members",
title: "Members",
component: <ManageMembers cid={channelId} />,
},
// {
// name: "permissions",
// title: "Permissions",
// },
// {
// name: "invites",
// title: "Invites",
// },
// {
// name: "integrations",
// title: "Integrations",
// },
],
},
// {
// title: "User Management",
// items: [
// ],
// },
];
return navs;
};
export default useNavs;
@@ -1,80 +0,0 @@
// import React from "react";
import { useEffect, useState } from "react";
import styled from "styled-components";
import { useDispatch } from "react-redux";
import { toggleSetting } from "../../../app/slices/ui";
// import BASE_URL from "../../app/config";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
import Checkbox from "../styled/Checkbox";
import useLogout from "../../hook/useLogout";
const StyledConfirm = styled(StyledModal)`
.clear {
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #6b7280;
display: flex;
justify-content: flex-end;
align-items: center;
.txt {
cursor: pointer;
color: orange;
margin-right: 12px;
}
input {
cursor: pointer;
}
}
`;
import Modal from "../Modal";
export default function LogoutConfirmModal({ closeModal }) {
const dispatch = useDispatch();
const [clearLocal, setClearLocal] = useState(false);
const { logout, exited, exiting, clearLocalData } = useLogout();
const handleLogout = () => {
logout();
};
const handleCheck = (evt) => {
setClearLocal(evt.target.checked);
};
useEffect(() => {
if (exited) {
if (clearLocal) {
console.log("clear all store");
clearLocalData();
}
// closeModal();
dispatch(toggleSetting());
}
}, [exited, clearLocal]);
return (
<Modal id="modal-modal">
<StyledConfirm
title="Log Out"
description="Are you sure want to log out this account?"
buttons={
<>
<Button onClick={closeModal}>Cancel</Button>
<Button onClick={handleLogout} className="danger">
{exiting ? "Logging out" : `Log Out`}
</Button>
</>
}
// className="animate__animated animate__fadeInDown animate__faster"
>
<div className="clear">
<label htmlFor="clear_cb" className="txt">
Clear local data
</label>
<Checkbox
name="clear_cb"
checked={clearLocal}
onChange={handleCheck}
/>
</div>
</StyledConfirm>
</Modal>
);
}
-186
View File
@@ -1,186 +0,0 @@
import { useEffect, useState } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import toast from "react-hot-toast";
import { useUpdateAvatarMutation } from "../../../app/services/contact";
import AvatarUploader from "../AvatarUploader";
import ProfileBasicEditModal from "./ProfileBasicEditModal";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 32px;
.card {
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
width: 512px;
background: #f3f4f6;
border-radius: 20px;
.name {
margin-top: 8px;
margin-bottom: 64px;
font-weight: bold;
font-size: 18px;
line-height: 28px;
color: #27272a;
.uid {
font-weight: normal;
color: #52525b;
}
}
.row {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 24px;
.info {
display: flex;
flex-direction: column;
.label {
font-weight: 600;
font-size: 12px;
line-height: 20px;
text-transform: uppercase;
color: #52525b;
}
.txt {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #52525b;
.gray {
color: #78787c;
}
}
}
.btn {
background: #1fe1f9;
border: 1px solid #1fe1f9;
}
}
}
.danger {
display: flex;
flex-direction: column;
align-items: flex-start;
.head {
font-weight: bold;
font-size: 16px;
line-height: 24px;
color: #374151;
}
.desc {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #616161;
margin-bottom: 16px;
}
.btn {
background: #ef4444;
border: 1px solid #ef4444;
}
}
.btn {
color: #fff;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
padding: 8px 14px;
background: #1fe1f9;
border: 1px solid #1fe1f9;
box-sizing: border-box;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
}
`;
const EditModalInfo = {
name: {
label: "Username",
title: "Change your username",
intro: "Enter a new username.",
},
email: {
label: "Email",
title: "Change your email",
intro: "Enter a new email.",
},
};
export default function MyAccount() {
const [editModal, setEditModal] = useState(null);
const [
uploadAvatar,
{ isSuccess: uploadSuccess },
] = useUpdateAvatarMutation();
const loginUser = useSelector((store) => {
return store.contacts.byId[store.authData.uid];
});
useEffect(() => {
if (uploadSuccess) {
toast.success("update avatar successfully!");
}
}, [uploadSuccess]);
const handleBasicEdit = (evt) => {
const { edit } = evt.target.dataset;
setEditModal(edit);
};
const closeBasicEditModal = () => {
setEditModal(null);
};
if (!loginUser) return null;
const { uid, avatar, name, email } = loginUser;
return (
<>
<StyledWrapper>
<div className="card">
<AvatarUploader url={avatar} name={name} uploadImage={uploadAvatar} />
<div className="name">
{name} <span className="uid">#{uid}</span>
</div>
<div className="row">
<div className="info">
<span className="label">username</span>
<span className="txt">
{name} <span className="gray"> #{uid}</span>
</span>
</div>
{/* <div className="right"> */}
<button data-edit="name" onClick={handleBasicEdit} className="btn">
Edit
</button>
{/* </div> */}
</div>
<div className="row">
<div className="info">
<span className="label">email</span>
<span className="txt">{email}</span>
</div>
{/* <div className="right"> */}
<button data-edit="email" onClick={handleBasicEdit} className="btn">
Edit
</button>
{/* </div> */}
</div>
</div>
<div className="danger">
<h4 className="head">Account Removal</h4>
<div className="desc">
Disabling your account means you can recover it at any time after
taking this action.
</div>
<button className="btn">Delete Account</button>
</div>
</StyledWrapper>
{editModal && (
<ProfileBasicEditModal
valueKey={editModal}
{...EditModalInfo[editModal]}
value={eval(editModal)}
closeModal={closeBasicEditModal}
/>
)}
</>
);
}
@@ -1,26 +0,0 @@
// import React from "react";
import styled from "styled-components";
import useNotification from "../../hook/useNotification";
import StyledToggle from "../styled/Toggle";
import Label from "../styled/Label";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
`;
export default function Notifications() {
const { status, enableNotification } = useNotification();
const handleEnableNotify = () => {
if (status !== "granted") {
enableNotification();
}
};
return (
<StyledWrapper>
<Label>Notification Setting:</Label>
<StyledToggle
data-checked={status == "granted"}
onClick={handleEnableNotify}
/>
</StyledWrapper>
);
}
-179
View File
@@ -1,179 +0,0 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import {
useGetServerQuery,
useUpdateServerMutation,
useUpdateLogoMutation,
} from "../../../app/services/server";
import LogoUploader from "../AvatarUploader";
import Input from "../styled/Input";
import Label from "../styled/Label";
import Textarea from "../styled/Textarea";
import SaveTip from "../SaveTip";
import toast from "react-hot-toast";
const StyledWrapper = styled.div`
position: relative;
width: 512px;
height: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.logo {
display: flex;
gap: 16px;
.preview {
width: 96px;
height: 96px;
}
.upload {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
.tip {
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #374151;
}
.btn {
padding: 8px 14px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #1fe1f9;
background: #ecfeff;
border: 1px solid #ecfeff;
box-sizing: border-box;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
}
}
}
.inputs {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
.input {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
`;
export default function Overview() {
const loginUser = useSelector((store) => {
return store.contacts.byId[store.authData.uid];
});
const [changed, setChanged] = useState(false);
const [values, setValues] = useState(null);
const { data, refetch } = useGetServerQuery();
const [updateServer, { isSuccess: updated }] = useUpdateServerMutation();
const [uploadLogo, { isSuccess: uploadSuccess }] = useUpdateLogoMutation();
const handleUpdate = () => {
const { name, description } = values;
updateServer({ name, description });
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
setValues((prev) => {
return { ...prev, [type]: newValue };
});
};
const handleReset = () => {
console.log("reset", data);
setValues(data);
};
useEffect(() => {
if (uploadSuccess) {
toast.success("update logo successfully");
refetch();
}
}, [uploadSuccess]);
useEffect(() => {
if (data) {
setValues(data);
}
}, [data]);
useEffect(() => {
if (data && values) {
const { name, description } = values;
const { name: oName, description: oDescription } = data;
if (oName !== name || oDescription !== description) {
setChanged(true);
} else {
setChanged(false);
}
}
}, [data, values]);
useEffect(() => {
if (updated) {
toast.success("Server updated!");
refetch();
}
}, [updated]);
if (!values) return null;
const { name, description, logo } = values;
const isAdmin = loginUser?.is_admin;
return (
<StyledWrapper>
<div className="logo">
<div className="preview">
<LogoUploader
disabled={!isAdmin}
url={uploadSuccess ? `${logo}?t=${new Date().getTime()}` : logo}
name={name}
uploadImage={uploadLogo}
/>
</div>
{isAdmin && (
<div className="upload">
<div className="tip">
Minimum size is 128x128, We recommend at least 512x512 for the
server. Max size limited to 5M.
</div>
<button className="btn">Upload Image</button>
</div>
)}
</div>
<div className="inputs">
<div className="input">
<Label htmlFor="name">Server Name</Label>
<Input
disabled={!isAdmin}
data-type="name"
onChange={handleChange}
value={name}
name="name"
id="name"
placeholder="Server Name"
/>
</div>
<div className="input">
<Label htmlFor="desc">Server Description</Label>
<Textarea
disabled={!isAdmin}
data-type="description"
onChange={handleChange}
value={description ?? ""}
rows={4}
name="name"
id="name"
placeholder="Tell the world a bit about this server"
/>
</div>
</div>
{changed && (
<SaveTip saveHandler={handleUpdate} resetHandler={handleReset} />
)}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledWrapper>
);
}
@@ -1,73 +0,0 @@
// import React from "react";
import { useEffect, useState } from "react";
import styled from "styled-components";
// import { useDispatch } from "react-redux";
import Input from "../styled/Input";
// import BASE_URL from "../../app/config";
import { useUpdateInfoMutation } from "../../../app/services/contact";
import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
const StyledEdit = styled(StyledModal)`
.input {
margin: 48px 0;
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
label {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #6b7280;
}
}
`;
import Modal from "../Modal";
import toast from "react-hot-toast";
export default function ProfileBasicEditModal({
label = "Username",
valueKey = "name",
value = "",
title = "Change your username",
intro = "Enter a new username and your existing password.",
closeModal,
}) {
const [input, setInput] = useState(value);
// const dispatch = useDispatch();
const [update, { isLoading, isSuccess }] = useUpdateInfoMutation();
const handleChange = (evt) => {
setInput(evt.target.value);
};
const handleUpdate = () => {
update({ [valueKey]: input });
};
useEffect(() => {
if (isSuccess) {
// todo
toast.success("update user info successfully");
closeModal();
}
}, [isSuccess]);
return (
<Modal id="modal-modal">
<StyledEdit
title={title}
description={intro}
buttons={
<>
<Button onClick={closeModal}>Cancel</Button>
<Button onClick={handleUpdate}>
{isLoading ? "Updating" : `Done`}
</Button>
</>
}
className="animate__animated animate__fadeInDown animate__faster"
>
<div className="input">
<label htmlFor={valueKey}>{label}</label>
<Input name={valueKey} value={input} onChange={handleChange}></Input>
</div>
</StyledEdit>
</Modal>
);
}
@@ -1,118 +0,0 @@
// import { useState, useEffect } from "react";
import StyledContainer from "./StyledContainer";
import Input from "../../styled/Input";
import Textarea from "../../styled/Textarea";
import Label from "../../styled/Label";
import Toggle from "../../styled/Toggle";
import SaveTip from "../../SaveTip";
import useConfig from "./useConfig";
export default function ConfigAgora() {
const {
changed,
reset,
values,
setValues,
toggleEnable,
updateConfig,
} = useConfig("agora");
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig(values);
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
setValues((prev) => {
return { ...prev, [type]: newValue };
});
};
// if (!values) return null;
const {
url,
project_id,
app_id,
app_certificate,
rtm_key,
rtm_secret,
enabled = false,
} = values ?? {};
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Enable</Label>
<Toggle onClick={toggleEnable} data-checked={enabled}></Toggle>
</div>
<div className="input">
<Label htmlFor="url">Agora Url</Label>
<Input
disabled={!enabled}
data-type="url"
onChange={handleChange}
value={url || "https://api.agora.io"}
name="url"
placeholder="Agora URL"
/>
</div>
<div className="input">
<Label htmlFor="project_id">Project ID</Label>
<Input
disabled={!enabled}
// type={"number"}
data-type="project_id"
onChange={handleChange}
value={project_id}
name="project_id"
placeholder="Project ID"
/>
</div>
<div className="input">
<Label htmlFor="app_id">App ID</Label>
<Input
disabled={!enabled}
data-type="app_id"
onChange={handleChange}
value={app_id}
name="app_id"
placeholder="APP ID"
/>
</div>
<div className="input">
<Label htmlFor="app_certificate">APP Certificate</Label>
<Input
disabled={!enabled}
data-type="app_certificate"
onChange={handleChange}
value={app_certificate}
name="app_certificate"
placeholder="APP Certificate"
/>
</div>
<div className="input">
<Label htmlFor="rtm_key">RTM Key</Label>
<Textarea
disabled={!enabled}
data-type="rtm_key"
onChange={handleChange}
value={rtm_key}
name="rtm_key"
placeholder="RTM Key"
/>
</div>
<div className="input">
<Label htmlFor="rtm_secret">RTM Secret</Label>
<Textarea
disabled={!enabled}
data-type="rtm_secret"
onChange={handleChange}
value={rtm_secret}
name="rtm_secret"
placeholder="RTM Secret"
/>
</div>
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
@@ -1,90 +0,0 @@
// import { useState, useEffect } from "react";
import StyledContainer from "./StyledContainer";
import Input from "../../styled/Input";
import Textarea from "../../styled/Textarea";
import Toggle from "../../styled/Toggle";
import Label from "../../styled/Label";
import SaveTip from "../../SaveTip";
import useConfig from "./useConfig";
export default function ConfigFirebase() {
const {
values,
toggleEnable,
updateConfig,
setValues,
reset,
changed,
} = useConfig("firebase");
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig(values);
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
setValues((prev) => {
return { ...prev, [type]: newValue };
});
};
// if (!values) return null;
const { token_url, project_id, private_key, client_email, enabled = false } =
values ?? {};
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Enable</Label>
<Toggle onClick={toggleEnable} data-checked={enabled}></Toggle>
</div>
<div className="input">
<Label htmlFor="name">Token Url</Label>
<Input
disabled={!enabled}
data-type="token_url"
onChange={handleChange}
value={token_url || "https://oauth2.googleapis.com/token"}
name="token_url"
placeholder="Token URL"
/>
</div>
<div className="input">
<Label htmlFor="desc">Project ID</Label>
<Input
disabled={!enabled}
data-type="project_id"
onChange={handleChange}
value={project_id}
name="project_id"
placeholder="Project ID"
/>
</div>
<div className="input">
<Label htmlFor="desc">Private Key</Label>
<Textarea
rows={10}
disabled={!enabled}
data-type="private_key"
onChange={handleChange}
value={private_key}
name="private_key"
placeholder="Private key"
/>
</div>
<div className="input">
<Label htmlFor="desc">Client Email</Label>
<Input
disabled={!enabled}
data-type="client_email"
onChange={handleChange}
value={client_email}
name="client_email"
placeholder="Client Email address"
/>
</div>
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
@@ -1,115 +0,0 @@
// import { useState, useEffect } from "react";
import StyledContainer from './StyledContainer';
// import Input from "../../styled/Input";
import Textarea from '../../styled/Textarea';
import Toggle from '../../styled/Toggle';
import Label from '../../styled/Label';
import SaveTip from '../../SaveTip';
import useConfig from './useConfig';
export default function Logins() {
const { values, updateConfig, setValues, reset, changed } = useConfig('login');
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig(values);
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
const items = newValue ? newValue.split('\n') : [];
setValues((prev) => {
return { ...prev, [type]: items };
});
};
const handleToggle = (val) => {
setValues((prev) => {
return { ...prev, ...val };
});
};
if (!values) return null;
const { google, metamask, password, oidc = [] } = values ?? {};
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Password</Label>
<Toggle
onClick={handleToggle.bind(null, { password: !password })}
data-checked={password}
></Toggle>
</div>
<div className="input row">
<Label>Google</Label>
<Toggle onClick={handleToggle.bind(null, { google: !google })} data-checked={google}></Toggle>
</div>
<div className="input row">
<Label>Metamask</Label>
<Toggle
onClick={handleToggle.bind(null, { metamask: !metamask })}
data-checked={metamask}
></Toggle>
</div>
<div className="input">
<Label htmlFor="desc">OIDC</Label>
<Textarea
rows={10}
data-type="oidc"
onChange={handleChange}
value={oidc.join('\n')}
name="oidc"
placeholder="Input issuer list, one line, one issuer"
/>
</div>
{/* <div className="input">
<Label htmlFor="name">Token Url</Label>
<Input
disabled={!enabled}
data-type="token_url"
onChange={handleChange}
value={token_url || "https://oauth2.googleapis.com/token"}
name="token_url"
placeholder="Token URL"
/>
</div>
<div className="input">
<Label htmlFor="desc">Project ID</Label>
<Input
disabled={!enabled}
type={"number"}
data-type="project_id"
onChange={handleChange}
value={project_id}
name="project_id"
placeholder="Project ID"
/>
</div>
<div className="input">
<Label htmlFor="desc">Private Key</Label>
<Textarea
rows={10}
disabled={!enabled}
data-type="private_key"
onChange={handleChange}
value={private_key}
name="private_key"
placeholder="Private key"
/>
</div>
<div className="input">
<Label htmlFor="desc">Client Email</Label>
<Input
disabled={!enabled}
data-type="client_email"
onChange={handleChange}
value={client_email}
name="client_email"
placeholder="Client Email address"
/>
</div> */}
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
-161
View File
@@ -1,161 +0,0 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
const StyledTest = styled.div`
display: flex;
gap: 16px;
white-space: nowrap;
margin-top: 24px;
`;
import { useSendTestEmailMutation } from "../../../../app/services/server";
import iconQuestion from "../../../../assets/icons/question.svg?url";
import useConfig from "./useConfig";
import StyledContainer from "./StyledContainer";
import Input from "../../styled/Input";
import Button from "../../styled/Button";
import Toggle from "../../styled/Toggle";
import Label from "../../styled/Label";
import SaveTip from "../../SaveTip";
import toast from "react-hot-toast";
export default function ConfigSMTP() {
const [testEmail, setTestEmail] = useState("");
const [sendTestEmail, { isSuccess, isError }] = useSendTestEmailMutation();
const {
reset,
updateConfig,
values,
setValues,
changed,
toggleEnable,
} = useConfig("smtp");
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig({ ...values, port: Number(values.port ?? 0) });
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
setValues((prev) => {
return { ...prev, [type]: newValue };
});
};
const handleTestEmailChange = (evt) => {
const newValue = evt.target.value;
setTestEmail(newValue);
};
const handleTestClick = () => {
console.log("test");
sendTestEmail({
to: testEmail,
subject: "test title",
content: "test content",
});
};
useEffect(() => {
if (isSuccess) {
toast.success("Send Test Email Successfully");
}
if (isError) {
toast.error("Send Test Email Fail");
}
}, [isSuccess, isError]);
// if (!values) return null;
const { host, port, from, username, password, enabled = false } =
values ?? {};
console.log("values", values);
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Enable</Label>
<Toggle onClick={toggleEnable} data-checked={enabled}></Toggle>
</div>
<div className="input">
<Label htmlFor="name">Host</Label>
<Input
disabled={!enabled}
data-type="host"
onChange={handleChange}
value={host}
name="host"
placeholder="SMTP Host"
/>
</div>
<div className="input">
<Label htmlFor="desc">Port</Label>
<Input
disabled={!enabled}
type={"number"}
data-type="port"
onChange={handleChange}
value={port}
name="port"
placeholder="SMTP Port"
/>
</div>
<div className="input">
<Label htmlFor="desc">From</Label>
<Input
disabled={!enabled}
data-type="from"
onChange={handleChange}
value={from}
name="from"
placeholder="SMTP From"
/>
</div>
<div className="input">
<Label htmlFor="desc">Username</Label>
<Input
disabled={!enabled}
data-type="username"
onChange={handleChange}
value={username}
name="username"
placeholder="SMTP Username"
/>
</div>
<div className="input">
<Label htmlFor="desc">Password</Label>
<Input
type={"password"}
disabled={!enabled}
data-type="password"
onChange={handleChange}
value={password}
name="password"
placeholder="SMTP Password"
/>
</div>
</div>
<div className="tip">
<img src={iconQuestion} alt="question icon" />
<a
href="https://rustchat.com/doc/smtp-setting"
target="_blank"
className="link"
rel="noreferrer"
>
How to set up SMTP?
</a>
</div>
<StyledTest>
<Input
type={"email"}
disabled={!enabled}
onChange={handleTestEmailChange}
value={testEmail}
name="email"
placeholder="test@email.com"
/>
<Button disabled={!enabled || !testEmail} onClick={handleTestClick}>
Send Test Email
</Button>
</StyledTest>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
@@ -1,40 +0,0 @@
import styled from "styled-components";
const StyledContainer = styled.div`
position: relative;
width: 512px;
height: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.inputs {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
.input {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
&.row {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
}
.tip {
display: flex;
gap: 8px;
align-items: center;
.link {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #06b6d4;
}
}
`;
export default StyledContainer;
@@ -1,92 +0,0 @@
import { useEffect, useState } from 'react';
import { isObjectEqual } from '../../../utils';
import toast from 'react-hot-toast';
import {
useGetAgoraConfigQuery,
useGetFirebaseConfigQuery,
useGetSMTPConfigQuery,
useGetLoginConfigQuery,
useUpdateLoginConfigMutation,
useUpdateSMTPConfigMutation,
useUpdateAgoraConfigMutation,
useUpdateFirebaseConfigMutation
} from '../../../../app/services/server';
export default function useConfig(config = 'smtp') {
const [changed, setChanged] = useState(false);
const [values, setValues] = useState({});
const { data: Login, refetch: refetchLogin } = useGetLoginConfigQuery();
const [updateLoginConfig, { isSuccess: LoginUpdated }] = useUpdateLoginConfigMutation();
const { data: SMTP, refetch: refetchSMTP } = useGetSMTPConfigQuery();
const [updateSMTPConfig, { isSuccess: SMTPUpdated }] = useUpdateSMTPConfigMutation();
const { data: Agora, refetch: refetchAgora } = useGetAgoraConfigQuery();
const [updateAgoraConfig, { isSuccess: AgoraUpdated }] = useUpdateAgoraConfigMutation();
const { data: Firebase, refetch: refetchFirebase } = useGetFirebaseConfigQuery();
const [updateFirebaseConfig, { isSuccess: FirebaseUpdated }] = useUpdateFirebaseConfigMutation();
const datas = {
login: Login,
smtp: SMTP,
agora: Agora,
firebase: Firebase
};
const updateFns = {
login: updateLoginConfig,
smtp: updateSMTPConfig,
agora: updateAgoraConfig,
firebase: updateFirebaseConfig
};
const refetchs = {
smtp: refetchSMTP,
agora: refetchAgora,
firebase: refetchFirebase,
login: refetchLogin
};
const updateds = {
login: LoginUpdated,
smtp: SMTPUpdated,
agora: AgoraUpdated,
firebase: FirebaseUpdated
};
const data = datas[config];
const updateConfig = updateFns[config];
const refetch = refetchs[config];
const updated = updateds[config];
const reset = () => {
setValues(data ?? {});
};
const toggleEnable = () => {
setValues((prev) => {
return { ...prev, enabled: !prev.enabled };
});
};
useEffect(() => {
if (updated) {
toast.success('Configuration Updated!');
refetch();
}
}, [updated]);
useEffect(() => {
console.log('wtf', data);
// if (data) {
setValues(data ?? {});
// }
}, [data]);
useEffect(() => {
// if (data && values) {
if (!isObjectEqual(data, values)) {
setChanged(true);
} else {
setChanged(false);
}
// }
}, [data, values]);
return {
reset,
changed,
updateConfig,
values,
setValues,
toggleEnable
};
}
-45
View File
@@ -1,45 +0,0 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { toggleSetting } from "../../../app/slices/ui";
import StyledSettingContainer from "../StyledSettingContainer";
import useNavs from "./navs";
import LogoutConfirmModal from "./LogoutConfirmModal";
export default function Setting() {
const navs = useNavs();
const flatenNavs = navs
.map(({ items }) => {
return items;
})
.flat();
const [currNav, setCurrNav] = useState(flatenNavs[0]);
const [logoutConfirm, setLogoutConfirm] = useState(false);
const dispatch = useDispatch();
const close = () => {
dispatch(toggleSetting());
};
const toggleLogoutConfrim = () => {
setLogoutConfirm((prev) => !prev);
};
const updateNav = (name) => {
const tmp = flatenNavs.find((n) => n.name == name);
if (tmp) {
setCurrNav(tmp);
}
};
return (
<>
<StyledSettingContainer
updateNav={updateNav}
nav={currNav}
closeModal={close}
title="Settings"
navs={navs}
dangers={[{ title: "Log Out", handler: toggleLogoutConfrim }]}
>
{currNav.component}
</StyledSettingContainer>
{logoutConfirm && <LogoutConfirmModal closeModal={toggleLogoutConfrim} />}
</>
);
}
-104
View File
@@ -1,104 +0,0 @@
import { useSelector } from 'react-redux';
import MyAccount from './MyAccount';
import Overview from './Overview';
import Logins from './config/Logins';
import ConfigFirebase from './config/Firebase';
import ConfigSMTP from './config/SMTP';
import Notifications from './Notifications';
import ManageMembers from '../ManageMembers';
import FAQ from '../FAQ';
import ConfigAgora from './config/Agora';
const navs = [
{
title: 'General',
items: [
{
name: 'overview',
title: 'Overview',
component: <Overview />
},
{
name: 'members',
title: 'Members',
component: <ManageMembers />,
admin: true
},
{
name: 'notification',
title: 'Notification',
component: <Notifications />
}
]
},
{
title: 'User',
items: [
{
name: 'my_account',
title: 'My Account',
component: <MyAccount />
}
]
},
{
title: 'Configuration',
items: [
{
name: 'firebase',
title: 'Firebase',
component: <ConfigFirebase />
},
{
name: 'agora',
title: 'Agora',
component: <ConfigAgora />
},
{
name: 'smtp',
title: 'SMTP',
component: <ConfigSMTP />
},
{
name: 'social_login',
title: 'Social Login',
component: <Logins />
}
],
admin: true
},
{
title: 'About',
items: [
{
name: 'faq',
title: 'FAQ',
component: <FAQ />
},
{
name: 'terms',
title: 'Terms & Privacy',
component: 'Terms & Privacy'
},
{
name: 'feedback',
title: 'Feedback',
component: 'feedback'
}
]
}
];
const useNavs = () => {
const loginUser = useSelector((store) => {
return store.contacts.byId[store.authData.uid];
});
const Navs = navs.filter((nav) => {
if (loginUser.is_admin) {
return true;
} else {
return !nav.admin;
}
});
return Navs;
};
export default useNavs;
+36 -39
View File
@@ -1,10 +1,9 @@
// import React from 'react'
import styled from "styled-components";
import Modal from "./Modal";
import backIcon from "../../assets/icons/arrow.left.svg?url";
const StyledWrapper = styled.div`
width: 100%;
height: 100%;
width: 100vw;
height: 100vh;
display: flex;
> .left {
max-height: 100vh;
@@ -89,47 +88,45 @@ export default function StyledSettingContainer({
updateNav(name);
};
return (
<Modal>
<StyledWrapper>
<div className="left">
<h2 onClick={closeModal} className="title">
{title}
</h2>
{navs.map(({ title, items }) => {
return (
<ul key={title} data-title={title} className="items">
{items.map(({ name, title }) => {
return (
<li
key={name}
onClick={handleNavClick.bind(null, name)}
className={`item ${name == nav?.name ? "curr" : ""}`}
>
{title}
</li>
);
})}
</ul>
);
})}
{dangers.length ? (
<ul className="items danger">
{dangers.map((d) => {
const { title, handler } = d;
<StyledWrapper>
<div className="left">
<h2 onClick={closeModal} className="title">
{title}
</h2>
{navs.map(({ title, items }) => {
return (
<ul key={title} data-title={title} className="items">
{items.map(({ name, title }) => {
return (
<li key={title} onClick={handler} className="item">
<li
key={name}
onClick={handleNavClick.bind(null, name)}
className={`item ${name == nav?.name ? "curr" : ""}`}
>
{title}
</li>
);
})}
</ul>
) : null}
</div>
<div className="right">
{nav && <h4 className="title">{nav.title}</h4>}
{children}
</div>
</StyledWrapper>
</Modal>
);
})}
{dangers.length ? (
<ul className="items danger">
{dangers.map((d) => {
const { title, handler } = d;
return (
<li key={title} onClick={handler} className="item">
{title}
</li>
);
})}
</ul>
) : null}
</div>
<div className="right">
{nav && <h4 className="title">{nav.title}</h4>}
{children}
</div>
</StyledWrapper>
);
}