feat: lots updates
This commit is contained in:
@@ -3,11 +3,12 @@ import toast from "react-hot-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import Modal from "../Modal";
|
||||
import Button from "../StyledButton";
|
||||
import Button from "../styled/Button";
|
||||
import ChannelIcon from "../ChannelIcon";
|
||||
import Contact from "../Contact";
|
||||
import StyledWrapper from "./styled";
|
||||
import StyledCheckbox from "../../component/StyledCheckbox";
|
||||
import StyledToggle from "../../component/styled/Toggle";
|
||||
import StyledCheckbox from "../../component/styled/Checkbox";
|
||||
import useFilteredUsers from "../../hook/useFilteredUsers";
|
||||
import { addChannel } from "../../../app/slices/channels";
|
||||
|
||||
@@ -141,13 +142,10 @@ export default function ChannelModal({ personal = false, closeModal }) {
|
||||
{
|
||||
<div className="private">
|
||||
<span className="txt normal">Private Channel</span>
|
||||
<input
|
||||
disabled={!currentUser?.is_admin}
|
||||
onChange={handleToggle}
|
||||
checked={!is_public}
|
||||
type="checkbox"
|
||||
name="private"
|
||||
id="private"
|
||||
<StyledToggle
|
||||
data-checked={!is_public}
|
||||
data-disabled={!currentUser?.is_admin}
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { deleteChannel } from "../../../app/slices/channels";
|
||||
import Modal from "../Modal";
|
||||
// import BASE_URL from "../../app/config";
|
||||
import { useLazyRemoveChannelQuery } from "../../../app/services/channel";
|
||||
import Button from "../StyledButton";
|
||||
import Button from "../styled/Button";
|
||||
const StyledConfirm = styled.div`
|
||||
padding: 32px;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
useGetChannelQuery,
|
||||
useUpdateChannelMutation,
|
||||
} from "../../../app/services/channel";
|
||||
import Input from "../StyledInput";
|
||||
import Textarea from "../StyledTextarea";
|
||||
import Input from "../styled/Input";
|
||||
import Label from "../styled/Label";
|
||||
import Textarea from "../styled/Textarea";
|
||||
import SaveTip from "../SaveTip";
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
@@ -27,12 +28,6 @@ const StyledWrapper = styled.div`
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
label {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.name {
|
||||
padding-left: 36px;
|
||||
background: url(https://static.nicegoodthings.com/project/rustchat/icon.hash.svg);
|
||||
@@ -94,7 +89,7 @@ export default function Overview({ id = 0 }) {
|
||||
<StyledWrapper>
|
||||
<div className="inputs">
|
||||
<div className="input">
|
||||
<label htmlFor="name">Channel Name</label>
|
||||
<Label htmlFor="name">Channel Name</Label>
|
||||
<Input
|
||||
className="name"
|
||||
data-type="name"
|
||||
@@ -106,11 +101,11 @@ export default function Overview({ id = 0 }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<label htmlFor="desc">Channel Topic</label>
|
||||
<Label htmlFor="desc">Channel Topic</Label>
|
||||
<Textarea
|
||||
data-type="description"
|
||||
onChange={handleChange}
|
||||
value={description}
|
||||
value={description ?? ""}
|
||||
rows={4}
|
||||
name="name"
|
||||
id="name"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useEffect } from "react";
|
||||
import { getMessaging, getToken } from "firebase/messaging";
|
||||
|
||||
export default function Notification() {
|
||||
useEffect(() => {
|
||||
// Get registration token. Initially this makes a network call, once retrieved
|
||||
// subsequent calls to getToken will return from cache.
|
||||
const messaging = getMessaging();
|
||||
getToken(messaging, { vapidKey: "<YOUR_PUBLIC_VAPID_KEY_HERE>" })
|
||||
.then((currentToken) => {
|
||||
if (currentToken) {
|
||||
// Send the token to your server and update the UI if necessary
|
||||
// ...
|
||||
} else {
|
||||
// Show permission request UI
|
||||
console.log(
|
||||
"No registration token available. Request permission to generate one."
|
||||
);
|
||||
// ...
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("An error occurred while retrieving token. ", err);
|
||||
// ...
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <div>Notification</div>;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useOutsideClick } from "rooks";
|
||||
import toast from "react-hot-toast";
|
||||
import useCopy from "../hook/useCopy";
|
||||
import { useLazyDeleteContactQuery } from "../../app/services/contact";
|
||||
import Contact from "./Contact";
|
||||
import StyledMenu from "./StyledMenu";
|
||||
import toast from "react-hot-toast";
|
||||
const StyledWrapper = styled.section`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -84,6 +85,7 @@ const StyledWrapper = styled.section`
|
||||
}
|
||||
`;
|
||||
export default function ManageMembers({ members = [] }) {
|
||||
const [copied, copy] = useCopy();
|
||||
const [remove, { isSuccess: removeSuccess }] = useLazyDeleteContactQuery();
|
||||
const wrapperRef = useRef(null);
|
||||
const [menuVisible, setMenuVisible] = useState(null);
|
||||
@@ -106,7 +108,9 @@ export default function ManageMembers({ members = [] }) {
|
||||
toast.success("delete successfully");
|
||||
}
|
||||
}, [removeSuccess]);
|
||||
|
||||
const handleCopy = (str) => {
|
||||
copy(str);
|
||||
};
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="intro">
|
||||
@@ -139,11 +143,13 @@ export default function ManageMembers({ members = [] }) {
|
||||
alt="dots icon"
|
||||
/>
|
||||
{menuVisible == uid && (
|
||||
<StyledMenu
|
||||
ref={wrapperRef}
|
||||
className="menu animate__animated animate__flipInY animate__faster"
|
||||
>
|
||||
<li className="item">Copy Email</li>
|
||||
<StyledMenu ref={wrapperRef} className="menu">
|
||||
<li
|
||||
className="item"
|
||||
onClick={handleCopy.bind(null, email)}
|
||||
>
|
||||
{copied ? "Copied" : `Copy Email`}
|
||||
</li>
|
||||
<li className="item">Mute</li>
|
||||
<li className="item underline">Change Nickname</li>
|
||||
<li className="item danger">Ban</li>
|
||||
|
||||
+34
-9
@@ -2,27 +2,28 @@ import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import toast from "react-hot-toast";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import BASE_URL from "../../app/config";
|
||||
import { useRenewMutation } from "../../app/services/auth";
|
||||
import useNotification from "./useNotification";
|
||||
import BASE_URL from "../../../app/config";
|
||||
import { useRenewMutation } from "../../../app/services/auth";
|
||||
import {
|
||||
setChannels,
|
||||
addChannel,
|
||||
deleteChannel,
|
||||
} from "../../app/slices/channels";
|
||||
} from "../../../app/slices/channels";
|
||||
import {
|
||||
updateUsersByLogs,
|
||||
updateUsersStatus,
|
||||
} from "../../app/slices/contacts";
|
||||
} from "../../../app/slices/contacts";
|
||||
import {
|
||||
updateToken,
|
||||
clearAuthData,
|
||||
setUsersVersion,
|
||||
setAfterMid,
|
||||
updateLoginedUserByLogs,
|
||||
} from "../../app/slices/auth.data";
|
||||
} from "../../../app/slices/auth.data";
|
||||
|
||||
import { addChannelMsg } from "../../app/slices/message.channel";
|
||||
import { addUserMsg } from "../../app/slices/message.user";
|
||||
import { addChannelMsg } from "../../../app/slices/message.channel";
|
||||
import { addUserMsg } from "../../../app/slices/message.user";
|
||||
const getQueryString = (params = {}) => {
|
||||
const sp = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, val]) => {
|
||||
@@ -33,6 +34,7 @@ const getQueryString = (params = {}) => {
|
||||
return sp.toString();
|
||||
};
|
||||
const NotificationHub = ({ usersVersion = 0, afterMid = 0 }) => {
|
||||
const { enableNotification, showNotification } = useNotification();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { token, refreshToken, user: currUser } = useSelector(
|
||||
@@ -42,6 +44,10 @@ const NotificationHub = ({ usersVersion = 0, afterMid = 0 }) => {
|
||||
renewToken,
|
||||
{ data, isSuccess: refreshTokenSuccess },
|
||||
] = useRenewMutation();
|
||||
useEffect(() => {
|
||||
enableNotification();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let sse = null;
|
||||
if (token) {
|
||||
@@ -160,23 +166,42 @@ const NotificationHub = ({ usersVersion = 0, afterMid = 0 }) => {
|
||||
// channel msg
|
||||
const { gid, ...rest } = data;
|
||||
console.log("compare", rest, currUser, rest.from_uid != currUser.uid);
|
||||
const isSelf = rest.from_uid == currUser.uid;
|
||||
dispatch(
|
||||
addChannelMsg({
|
||||
id: gid,
|
||||
// 自己发的 就不用标记未读
|
||||
unread: rest.from_uid != currUser.uid,
|
||||
unread: !isSelf,
|
||||
...rest,
|
||||
})
|
||||
);
|
||||
// group message notification
|
||||
if (!isSelf) {
|
||||
showNotification({
|
||||
body: rest.content,
|
||||
data: {
|
||||
path: `/chat/channel/${gid}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// user msg
|
||||
const isSelf = data.from_uid == currUser.uid;
|
||||
dispatch(
|
||||
addUserMsg({
|
||||
id: data.from_uid,
|
||||
unread: data.from_uid != currUser.uid,
|
||||
unread: !isSelf,
|
||||
...data,
|
||||
})
|
||||
);
|
||||
if (!isSelf) {
|
||||
showNotification({
|
||||
body: data.content,
|
||||
data: {
|
||||
path: `/chat/dm/${data.from_uid}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// 更新after_mid
|
||||
dispatch(setAfterMid({ mid: data.mid }));
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useEffect, useState } from "react";
|
||||
// import { useNavigate } from "react-router-dom";
|
||||
const isSafariBrowser = () =>
|
||||
navigator.userAgent.indexOf("Safari") > -1 &&
|
||||
navigator.userAgent.indexOf("Chrome") <= -1;
|
||||
export default function useNotification() {
|
||||
// const navigate = useNavigate();
|
||||
// granted default denied /
|
||||
const [status, setStatus] = useState(Notification.permission);
|
||||
const [pageVisible, setPageVisible] = useState(true);
|
||||
useEffect(() => {
|
||||
const visibleChangeHandler = () => {
|
||||
setPageVisible(document.visibilityState === "visible");
|
||||
};
|
||||
const notifyPermissionChangeHandler = (state) => {
|
||||
setStatus(state);
|
||||
};
|
||||
document.addEventListener("visibilitychange", visibleChangeHandler);
|
||||
if (!isSafariBrowser) {
|
||||
navigator.permissions
|
||||
.query({ name: "notifications" })
|
||||
.then(function (permissionStatus) {
|
||||
console.log(
|
||||
"notifications permission status is ",
|
||||
permissionStatus.state
|
||||
);
|
||||
permissionStatus.onchange = notifyPermissionChangeHandler.bind(
|
||||
null,
|
||||
permissionStatus.state
|
||||
);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("visibilitychange", visibleChangeHandler);
|
||||
};
|
||||
}, []);
|
||||
const enableNotification = () => {
|
||||
if (status !== "granted") {
|
||||
Notification.requestPermission().then((permission) => {
|
||||
console.log(permission);
|
||||
setStatus(permission);
|
||||
});
|
||||
}
|
||||
};
|
||||
const showNotification = (payload = {}) => {
|
||||
console.log("show notify", payload, pageVisible);
|
||||
if (status !== "granted" || pageVisible) return;
|
||||
const {
|
||||
title = "New Message",
|
||||
body = "You have one new message",
|
||||
icon = "https://static.nicegoodthings.com/project/ext/webrowse.logo.png",
|
||||
} = payload;
|
||||
new Notification(title, { body, icon });
|
||||
// const n = new Notification(title, { body, icon });
|
||||
// n.onclick = (evt) => {
|
||||
// const { data } = evt.target;
|
||||
|
||||
// console.log("notify evt", evt);
|
||||
// if (data && data.path) {
|
||||
// navigate(data.path);
|
||||
// }
|
||||
// };
|
||||
};
|
||||
return { status, enableNotification, showNotification };
|
||||
}
|
||||
@@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
/* gap: 20px; */
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useSendMsgMutation } from "../../../../app/services/contact";
|
||||
import { addChannelMsg } from "../../../../app/slices/message.channel";
|
||||
import { addUserMsg } from "../../../../app/slices/message.user";
|
||||
import Modal from "../../Modal";
|
||||
import Button from "../../StyledButton";
|
||||
import Button from "../../styled/Button";
|
||||
import StyledWrapper from "./styled";
|
||||
|
||||
export default function UploadModal({
|
||||
|
||||
@@ -8,7 +8,7 @@ import { clearAuthData } from "../../../app/slices/auth.data";
|
||||
import { toggleSetting } from "../../../app/slices/ui";
|
||||
// import BASE_URL from "../../app/config";
|
||||
import { useLazyLogoutQuery } from "../../../app/services/auth";
|
||||
import Button from "../StyledButton";
|
||||
import Button from "../styled/Button";
|
||||
const StyledConfirm = styled.div`
|
||||
padding: 32px;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
@@ -58,7 +58,7 @@ export default function LogoutConfirmModal({ closeModal }) {
|
||||
<div className="btns">
|
||||
<Button onClick={closeModal}>Cancel</Button>
|
||||
<Button onClick={handleLogout} className="danger">
|
||||
{isLoading ? "Logouting" : `Log Out`}
|
||||
{isLoading ? "Logging out" : `Log Out`}
|
||||
</Button>
|
||||
</div>
|
||||
</StyledConfirm>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// import React from "react";
|
||||
import styled from "styled-components";
|
||||
import useNotification from "../NotificationHub/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>
|
||||
);
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
useUpdateLogoMutation,
|
||||
} from "../../../app/services/server";
|
||||
import LogoUploader from "../AvatarUploader";
|
||||
import Input from "../StyledInput";
|
||||
import Textarea from "../StyledTextarea";
|
||||
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`
|
||||
@@ -61,12 +62,6 @@ const StyledWrapper = styled.div`
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
label {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -143,7 +138,7 @@ export default function Overview() {
|
||||
</div>
|
||||
<div className="inputs">
|
||||
<div className="input">
|
||||
<label htmlFor="name">Server Name</label>
|
||||
<Label htmlFor="name">Server Name</Label>
|
||||
<Input
|
||||
data-type="name"
|
||||
onChange={handleChange}
|
||||
@@ -154,11 +149,11 @@ export default function Overview() {
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<label htmlFor="desc">Server Description</label>
|
||||
<Label htmlFor="desc">Server Description</Label>
|
||||
<Textarea
|
||||
data-type="description"
|
||||
onChange={handleChange}
|
||||
value={description}
|
||||
value={description ?? ""}
|
||||
rows={4}
|
||||
name="name"
|
||||
id="name"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
// import { useDispatch } from "react-redux";
|
||||
import Input from "../StyledInput";
|
||||
import Input from "../styled/Input";
|
||||
// import BASE_URL from "../../app/config";
|
||||
import { useUpdateInfoMutation } from "../../../app/services/contact";
|
||||
import Button from "../StyledButton";
|
||||
import Button from "../styled/Button";
|
||||
const StyledEdit = styled.div`
|
||||
width: 440px;
|
||||
padding: 32px;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// import { useState, useEffect } from "react";
|
||||
import StyledContainer from "./StyledContainer";
|
||||
// import { useSelector } from "react-redux";
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// import { useState, useEffect } from "react";
|
||||
import StyledContainer from "./StyledContainer";
|
||||
// import { useSelector } from "react-redux";
|
||||
import Input from "../../styled/Input";
|
||||
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}
|
||||
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>
|
||||
<Input
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// import { useState, useEffect } from "react";
|
||||
import useConfig from "./useConfig";
|
||||
import StyledContainer from "./StyledContainer";
|
||||
import Input from "../../styled/Input";
|
||||
import Toggle from "../../styled/Toggle";
|
||||
import Label from "../../styled/Label";
|
||||
import SaveTip from "../../SaveTip";
|
||||
export default function ConfigSMTP() {
|
||||
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 };
|
||||
});
|
||||
};
|
||||
|
||||
// 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
|
||||
data-type="host"
|
||||
onChange={handleChange}
|
||||
value={host}
|
||||
name="host"
|
||||
placeholder="SMTP Host"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="desc">Port</Label>
|
||||
<Input
|
||||
type={"number"}
|
||||
data-type="port"
|
||||
onChange={handleChange}
|
||||
value={port}
|
||||
name="port"
|
||||
placeholder="SMTP Port"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="desc">From</Label>
|
||||
<Input
|
||||
data-type="from"
|
||||
onChange={handleChange}
|
||||
value={from}
|
||||
name="from"
|
||||
placeholder="SMTP From"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="desc">Username</Label>
|
||||
<Input
|
||||
data-type="username"
|
||||
onChange={handleChange}
|
||||
value={username}
|
||||
name="username"
|
||||
placeholder="SMTP Username"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="desc">Password</Label>
|
||||
<Input
|
||||
data-type="password"
|
||||
onChange={handleChange}
|
||||
value={password}
|
||||
name="password"
|
||||
placeholder="SMTP Password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
|
||||
{/* <button onClick={handleUpdate} className="btn">update</button> */}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledContainer;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { isObjectEqual } from "../../../utils";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
useGetAgoraConfigQuery,
|
||||
useGetFirebaseConfigQuery,
|
||||
useGetSMTPConfigQuery,
|
||||
useUpdateSMTPConfigMutation,
|
||||
useUpdateAgoraConfigMutation,
|
||||
useUpdateFirebaseConfigMutation,
|
||||
} from "../../../../app/services/server";
|
||||
export default function useConfig(config = "smtp") {
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [values, setValues] = useState({});
|
||||
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 = {
|
||||
smtp: SMTP,
|
||||
agora: Agora,
|
||||
firebase: Firebase,
|
||||
};
|
||||
const updateFns = {
|
||||
smtp: updateSMTPConfig,
|
||||
agora: updateAgoraConfig,
|
||||
firebase: updateFirebaseConfig,
|
||||
};
|
||||
const refetchs = {
|
||||
smtp: refetchSMTP,
|
||||
agora: refetchAgora,
|
||||
firebase: refetchFirebase,
|
||||
};
|
||||
const updateds = {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import { useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSetting } from "../../../app/slices/ui";
|
||||
import StyledSettingContainer from "../StyledSettingContainer";
|
||||
import getNavs from "./navs";
|
||||
import useNavs from "./navs";
|
||||
import LogoutConfirmModal from "./LogoutConfirmModal";
|
||||
|
||||
export default function Setting({ contacts = [] }) {
|
||||
const navs = getNavs(contacts);
|
||||
export default function Setting() {
|
||||
const navs = useNavs();
|
||||
const flatenNavs = navs
|
||||
.map(({ items }) => {
|
||||
return items;
|
||||
@@ -33,7 +33,7 @@ export default function Setting({ contacts = [] }) {
|
||||
updateNav={updateNav}
|
||||
nav={currNav}
|
||||
closeModal={close}
|
||||
title="Setting"
|
||||
title="Settings"
|
||||
navs={navs}
|
||||
dangers={[{ title: "Log Out", handler: toggleLogoutConfrim }]}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import MyAccount from "./MyAccount";
|
||||
import Overview from "./Overview";
|
||||
import ConfigFirebase from "./config/Firebase";
|
||||
import ConfigSMTP from "./config/SMTP";
|
||||
import Notifications from "./Notifications";
|
||||
import ManageMembers from "../ManageMembers";
|
||||
const getNavs = (members = []) => {
|
||||
import { useSelector } from "react-redux";
|
||||
import ConfigAgora from "./config/Agora";
|
||||
const useNavs = () => {
|
||||
const { contacts } = useSelector((store) => {
|
||||
return {
|
||||
currUser: store.authData.user,
|
||||
channels: store.channels,
|
||||
contacts: store.contacts,
|
||||
};
|
||||
});
|
||||
const navs = [
|
||||
{
|
||||
title: "General",
|
||||
@@ -16,8 +28,9 @@ const getNavs = (members = []) => {
|
||||
title: "Roles",
|
||||
},
|
||||
{
|
||||
name: "security",
|
||||
title: "Security",
|
||||
name: "notification",
|
||||
title: "Notification",
|
||||
component: <Notifications />,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -41,7 +54,27 @@ const getNavs = (members = []) => {
|
||||
{
|
||||
name: "members",
|
||||
title: "Members",
|
||||
component: <ManageMembers members={members} />,
|
||||
component: <ManageMembers members={contacts} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Configuration",
|
||||
items: [
|
||||
{
|
||||
name: "firebase",
|
||||
title: "Firebase",
|
||||
component: <ConfigFirebase />,
|
||||
},
|
||||
{
|
||||
name: "agora",
|
||||
title: "Agora",
|
||||
component: <ConfigAgora />,
|
||||
},
|
||||
{
|
||||
name: "smtp",
|
||||
title: "SMTP",
|
||||
component: <ConfigSMTP />,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -49,4 +82,4 @@ const getNavs = (members = []) => {
|
||||
return navs;
|
||||
};
|
||||
|
||||
export default getNavs;
|
||||
export default useNavs;
|
||||
|
||||
@@ -35,7 +35,7 @@ const StyledWrapper = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #6b7280;
|
||||
margin-bottom: -2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.item {
|
||||
cursor: pointer;
|
||||
@@ -73,7 +73,7 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
export default function StyledSettingContainer({
|
||||
closeModal,
|
||||
title = "Setting",
|
||||
title = "Settings",
|
||||
navs = [],
|
||||
dangers = [],
|
||||
nav,
|
||||
|
||||
@@ -8,8 +8,15 @@ const StyledInput = styled.input`
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #78787c;
|
||||
color: #333;
|
||||
padding: 8px;
|
||||
&:disabled {
|
||||
color: #78787c;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
&::placeholder {
|
||||
color: #78787c;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledInput;
|
||||
@@ -0,0 +1,9 @@
|
||||
import styled from "styled-components";
|
||||
const StyledLabel = styled.label`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #6b7280;
|
||||
`;
|
||||
|
||||
export default StyledLabel;
|
||||
@@ -1,5 +1,6 @@
|
||||
import styled from "styled-components";
|
||||
const StyledTextarea = styled.textarea`
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
@@ -8,9 +9,16 @@ const StyledTextarea = styled.textarea`
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #78787c;
|
||||
padding: 8px;
|
||||
color: #333;
|
||||
resize: unset;
|
||||
&:disabled {
|
||||
color: #78787c;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
&::placeholder {
|
||||
color: #78787c;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledTextarea;
|
||||
@@ -0,0 +1,33 @@
|
||||
import styled from "styled-components";
|
||||
const StyledToggle = styled.div`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
background-color: #1fe1f9;
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease-in;
|
||||
&:after {
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
&[data-checked="false"] {
|
||||
background-color: #f2f4f7;
|
||||
&:after {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
&[data-disabled="true"] {
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledToggle;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useState } from "react";
|
||||
const useCopy = () => {
|
||||
const copyToClipboard = (str) => {
|
||||
const el = document.createElement("textarea");
|
||||
el.value = str;
|
||||
el.setAttribute("readonly", "");
|
||||
el.style.position = "absolute";
|
||||
el.style.left = "-9999px";
|
||||
document.body.appendChild(el);
|
||||
const selected =
|
||||
document.getSelection().rangeCount > 0
|
||||
? document.getSelection().getRangeAt(0)
|
||||
: false;
|
||||
el.select();
|
||||
const success = document.execCommand("copy");
|
||||
document.body.removeChild(el);
|
||||
if (selected) {
|
||||
document.getSelection().removeAllRanges();
|
||||
document.getSelection().addRange(selected);
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copy = (text) => {
|
||||
let inter = 0;
|
||||
if (!copied) {
|
||||
setCopied(copyToClipboard(text));
|
||||
inter = setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 500);
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(inter);
|
||||
};
|
||||
};
|
||||
|
||||
return [copied, copy];
|
||||
};
|
||||
|
||||
export default useCopy;
|
||||
+6
-2
@@ -1,6 +1,10 @@
|
||||
export const isObjectEqual = (obj1, obj2) => {
|
||||
let o1 = Object.entries(obj1).sort().toString();
|
||||
let o2 = Object.entries(obj2).sort().toString();
|
||||
let o1 = Object.entries(obj1 ?? {})
|
||||
.sort()
|
||||
.toString();
|
||||
let o2 = Object.entries(obj2 ?? {})
|
||||
.sort()
|
||||
.toString();
|
||||
return o1 === o2;
|
||||
};
|
||||
export const getNonNullValues = (obj, whiteList = ["log_id"]) => {
|
||||
|
||||
Reference in New Issue
Block a user