feat: lots updates

This commit is contained in:
zerosoul
2022-03-02 17:21:16 +08:00
parent 834c379e7c
commit b6b0a0c5ef
38 changed files with 1971 additions and 501 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
// const BASE_URL = `${location.origin}/api`;
const BASE_URL = `https://privoce.rustchat.com/api`;
const BASE_URL = `https://dev.rustchat.com/api`;
// const BASE_URL = `https://rustchat.net/api`;
export const ContentTypes = {
text: "text/plain",
+2 -1
View File
@@ -33,11 +33,12 @@ export const authApi = createApi({
}),
// 获取openid
getOpenid: builder.mutation({
query: ({ issuer_url }) => ({
query: ({ issuer_url, redirect_uri }) => ({
url: "/token/openid/authorize",
method: "POST",
body: {
issuer_url,
redirect_uri,
},
}),
}),
+37 -1
View File
@@ -20,6 +20,36 @@ export const serverApi = createApi({
return data;
},
}),
getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` }),
}),
updateFirebaseConfig: builder.mutation({
query: (data) => ({
url: `admin/fcm/config`,
method: "POST",
body: data,
}),
}),
getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` }),
}),
updateAgoraConfig: builder.mutation({
query: (data) => ({
url: `admin/agora/config`,
method: "POST",
body: data,
}),
}),
getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` }),
}),
updateSMTPConfig: builder.mutation({
query: (data) => ({
url: `admin/smtp/config`,
method: "POST",
body: data,
}),
}),
updateLogo: builder.mutation({
query: (data) => ({
headers: {
@@ -33,7 +63,7 @@ export const serverApi = createApi({
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
method: "PUT",
method: "POST",
body: data,
}),
}),
@@ -41,6 +71,12 @@ export const serverApi = createApi({
});
export const {
useUpdateFirebaseConfigMutation,
useGetFirebaseConfigQuery,
useGetSMTPConfigQuery,
useUpdateSMTPConfigMutation,
useGetAgoraConfigQuery,
useUpdateAgoraConfigMutation,
useGetServerQuery,
useUpdateServerMutation,
useUpdateLogoMutation,
+7 -9
View File
@@ -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>;
}
+13 -7
View File
@@ -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>
@@ -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 };
}
+1
View File
@@ -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>
);
}
+6 -11
View File
@@ -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,
};
}
+4 -4
View File
@@ -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 }]}
>
+38 -5
View File
@@ -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;
+9
View File
@@ -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;
+33
View File
@@ -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;
+42
View File
@@ -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
View File
@@ -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"]) => {
+3 -1
View File
@@ -1,9 +1,11 @@
// import React from "react";
import { NavLink, useNavigate } from "react-router-dom";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import Contact from "../../common/component/Contact";
dayjs.extend(relativeTime);
const NavItem = ({ data, setFiles }) => {
const navigate = useNavigate();
const [{ isActive }, drop] = useDrop(() => ({
@@ -35,7 +37,7 @@ const NavItem = ({ data, setFiles }) => {
<div className="details">
<div className="up">
<span className="name">{user.name}</span>
<time>{dayjs(lastMsg.created_at).format("YYYY-MM-DD")}</time>
<time>{dayjs(lastMsg.created_at).fromNow()}</time>
</div>
<div className="down">
+1 -2
View File
@@ -20,7 +20,6 @@ export default function HomePage() {
const {
ui: { menuExpand, setting, channelSetting },
authData: { usersVersion, afterMid },
contacts,
} = useSelector((store) => {
return {
authData: store.authData,
@@ -74,7 +73,7 @@ export default function HomePage() {
<Outlet />
</div>
</StyledWrapper>
{setting && <SettingModal contacts={contacts} />}
{setting && <SettingModal />}
{channelSetting && <ChannelSettingModal id={channelSetting} />}
</>
);
+9 -11
View File
@@ -1,22 +1,20 @@
/* eslint-disable no-undef */
import { useEffect } from "react";
import { useGetOpenidMutation } from "../../app/services/auth";
export default function SolidLoginButton({ login }) {
export default function SolidLoginButton() {
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
const handleSolidLogin = async () => {
getOpenId({ issuer_url: "https://solidweb.org" });
const handleSolidLogin = () => {
getOpenId({
issuer_url: "https://solidweb.org",
redirect_uri: `${location.origin}/#/login`,
});
};
useEffect(() => {
if (isSuccess) {
console.log("wtf", data);
const { id, url } = data;
window.open(url);
login({
id,
type: "oidc",
});
// location.href = url;
const { url } = data;
location.href = url;
}
}, [data, isSuccess]);
@@ -32,7 +30,7 @@ export default function SolidLoginButton({ login }) {
src="https://solidproject.org/assets/img/solid-emblem.svg"
alt="solid logo icon"
/>
Sign in with Solid
{isLoading ? `Redirecting...` : `Sign in with Solid`}
</button>
);
}
+13 -4
View File
@@ -15,14 +15,24 @@ import { setAuthData } from "../../app/slices/auth.data";
export default function LoginPage() {
const [login, { data, isSuccess, isLoading, error }] = useLoginMutation();
// const { token } = useSelector((store) => store.authData);
const navigateTo = useNavigate();
const dispatch = useDispatch();
const [input, setInput] = useState({
email: "",
password: "",
});
useEffect(() => {
const query = new URLSearchParams(location.search);
const code = query.get("code");
const state = query.get("state");
if (code && state) {
login({
code,
state,
type: "oidc",
});
}
}, []);
useEffect(() => {
if (error) {
@@ -37,7 +47,6 @@ export default function LoginPage() {
case 404:
toast.error("account not exsit");
break;
default:
toast.error("something error");
break;
@@ -110,7 +119,7 @@ export default function LoginPage() {
<hr className="or" />
<GoogleLoginButton login={login} />
<MetamaskLoginButton login={login} />
<SolidLoginButton login={login} />
<SolidLoginButton />
</div>
</StyledWrapper>
);