fix formatting in a few files (spaces vs tabs)

This commit is contained in:
spiral 2021-11-03 04:18:18 -04:00
parent d2550b4c3c
commit 1d51b2333b
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
10 changed files with 1837 additions and 1837 deletions

View File

@ -17,51 +17,51 @@ import Navbar from './Components/Navbar.js'
export default function App() {
const [isLoading, setIsLoading] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
useEffect(() => {
if (localStorage.getItem("pk-darkmode")) {
document.body.classList.add('dark-mode')
}
else {
document.body.classList.remove('dark-mode')
}
forceUpdate();
useEffect(() => {
if (localStorage.getItem("pk-darkmode")) {
document.body.classList.add('dark-mode')
}
else {
document.body.classList.remove('dark-mode')
}
forceUpdate();
}, []);
return (
<div className={ `contents ${localStorage.getItem('opendyslexic') ? "opendyslexic" : ""}`}>
<Router history={history}>
<Navbar forceUpdate={forceUpdate} setIsSubmit={setIsSubmit} />
<div className="content">
<BS.Container>
<Switch>
<Route path="/dash">
{ !localStorage.getItem('token') || isInvalid ? <Redirect to="/"/> : <Dash />
}
</Route>
<Route exact path="/">
<Home forceUpdate={forceUpdate} isLoading={isLoading} setIsLoading={setIsLoading} isSubmit={isSubmit} setIsSubmit={setIsSubmit} isInvalid={isInvalid} setIsInvalid={setIsInvalid}/>
</Route>
<Route path="/profile">
<Public />
</Route>
<Route path="/settings">
<Settings forceUpdate={forceUpdate}/>
</Route>
<Route path="/template">
<Template/>
</Route>
</Switch>
</BS.Container>
</div>
<Footer />
</Router>
</div>
);
return (
<div className={ `contents ${localStorage.getItem('opendyslexic') ? "opendyslexic" : ""}`}>
<Router history={history}>
<Navbar forceUpdate={forceUpdate} setIsSubmit={setIsSubmit} />
<div className="content">
<BS.Container>
<Switch>
<Route path="/dash">
{ !localStorage.getItem('token') || isInvalid ? <Redirect to="/"/> : <Dash />
}
</Route>
<Route exact path="/">
<Home forceUpdate={forceUpdate} isLoading={isLoading} setIsLoading={setIsLoading} isSubmit={isSubmit} setIsSubmit={setIsSubmit} isInvalid={isInvalid} setIsInvalid={setIsInvalid}/>
</Route>
<Route path="/profile">
<Public />
</Route>
<Route path="/settings">
<Settings forceUpdate={forceUpdate}/>
</Route>
<Route path="/template">
<Template/>
</Route>
</Switch>
</BS.Container>
</div>
<Footer />
</Router>
</div>
);
}

View File

@ -8,148 +8,148 @@ import "moment-timezone";
import API_URL from "../../../Constants/constants.js";
const EditSystem = ({
name,
tag,
timezone,
avatar,
banner,
editDesc,
setEditMode,
setErrorAlert,
user,
setUser,
setErrorMessage
name,
tag,
timezone,
avatar,
banner,
editDesc,
setEditMode,
setErrorAlert,
user,
setUser,
setErrorMessage
}) => {
const [invalidTimezone, setInvalidTimezone] = useState(false);
const [invalidTimezone, setInvalidTimezone] = useState(false);
const { register: registerEdit, handleSubmit: handleSubmitEdit } = useForm();
const { register: registerEdit, handleSubmit: handleSubmitEdit } = useForm();
const submitEdit = (data) => {
if (data.tz) {
if (!moment.tz.zone(data.tz)) {
setInvalidTimezone(true);
return;
}
}
fetch(`${API_URL}s`, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status)
return res.json();
})
.then(() => {
setUser((prevState) => {
return { ...prevState, ...data };
});
localStorage.setItem("user", JSON.stringify(user));
setEditMode(false);
})
.catch((error) => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
};
const submitEdit = (data) => {
if (data.tz) {
if (!moment.tz.zone(data.tz)) {
setInvalidTimezone(true);
return;
}
}
fetch(`${API_URL}s`, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status)
return res.json();
})
.then(() => {
setUser((prevState) => {
return { ...prevState, ...data };
});
localStorage.setItem("user", JSON.stringify(user));
setEditMode(false);
})
.catch((error) => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
};
return (
<BS.Form onSubmit={handleSubmitEdit(submitEdit)}>
<BS.Form.Text className="mb-4">
<b>Note:</b> if you refresh the page, the old data might show up again,
this is due to the bot caching data.
<br />
Try editing a member to clear the cache, or wait a few minutes before
refreshing.
</BS.Form.Text>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control
name="name"
{...registerEdit("name")}
defaultValue={name}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Tag:</BS.Form.Label>
<BS.Form.Control
name="tag"
{...registerEdit("tag")}
defaultValue={tag}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Timezone:</BS.Form.Label>
<BS.Form.Control
name="tz"
{...registerEdit("tz")}
defaultValue={timezone}
required
/>
{invalidTimezone ? (
<BS.Form.Text>
Please enter a valid
<a
href="https://xske.github.io/tz/"
rel="noreferrer"
target="_blank"
>
timezone
</a>
</BS.Form.Text>
) : (
""
)}
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control
type="url"
name="avatar_url"
{...registerEdit("avatar_url")}
defaultValue={avatar}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control
type="url"
name="banner"
{...registerEdit("banner")}
defaultValue={banner}
/>
</BS.Col>
</BS.Form.Row>
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control
maxLength="1000"
as="textarea"
name="description"
{...registerEdit("description")}
defaultValue={editDesc}
/>
</BS.Form.Group>
<BS.Button variant="light" onClick={() => setEditMode(false)}>
Cancel
</BS.Button>{" "}
<BS.Button variant="primary" type="submit">
Submit
</BS.Button>
</BS.Form>
);
return (
<BS.Form onSubmit={handleSubmitEdit(submitEdit)}>
<BS.Form.Text className="mb-4">
<b>Note:</b> if you refresh the page, the old data might show up again,
this is due to the bot caching data.
<br />
Try editing a member to clear the cache, or wait a few minutes before
refreshing.
</BS.Form.Text>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control
name="name"
{...registerEdit("name")}
defaultValue={name}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Tag:</BS.Form.Label>
<BS.Form.Control
name="tag"
{...registerEdit("tag")}
defaultValue={tag}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Timezone:</BS.Form.Label>
<BS.Form.Control
name="tz"
{...registerEdit("tz")}
defaultValue={timezone}
required
/>
{invalidTimezone ? (
<BS.Form.Text>
Please enter a valid
<a
href="https://xske.github.io/tz/"
rel="noreferrer"
target="_blank"
>
timezone
</a>
</BS.Form.Text>
) : (
""
)}
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control
type="url"
name="avatar_url"
{...registerEdit("avatar_url")}
defaultValue={avatar}
/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control
type="url"
name="banner"
{...registerEdit("banner")}
defaultValue={banner}
/>
</BS.Col>
</BS.Form.Row>
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control
maxLength="1000"
as="textarea"
name="description"
{...registerEdit("description")}
defaultValue={editDesc}
/>
</BS.Form.Group>
<BS.Button variant="light" onClick={() => setEditMode(false)}>
Cancel
</BS.Button>{" "}
<BS.Button variant="primary" type="submit">
Submit
</BS.Button>
</BS.Form>
);
};
export default EditSystem;

View File

@ -6,113 +6,113 @@ import * as BS from "react-bootstrap";
import API_URL from "../../../Constants/constants.js";
const EditSystemPrivacy = ({
setErrorAlert,
setUser,
user,
setPrivacyEdit,
setErrorMessage
setErrorAlert,
setUser,
user,
setPrivacyEdit,
setErrorMessage
}) => {
const { register: registerPrivacy, handleSubmit: handleSubmitPrivacy } =
useForm();
const { register: registerPrivacy, handleSubmit: handleSubmitPrivacy } =
useForm();
// submit privacy stuffs
const submitPrivacy = (data) => {
fetch(`${API_URL}s`, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status)
return res.json();
})
.then(() => {
setUser((prevState) => {
return { ...prevState, ...data };
});
localStorage.setItem("user", JSON.stringify(user));
setPrivacyEdit(false);
})
.catch((error) => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
};
// submit privacy stuffs
const submitPrivacy = (data) => {
fetch(`${API_URL}s`, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status)
return res.json();
})
.then(() => {
setUser((prevState) => {
return { ...prevState, ...data };
});
localStorage.setItem("user", JSON.stringify(user));
setPrivacyEdit(false);
})
.catch((error) => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
};
return (
<BS.Form onSubmit={handleSubmitPrivacy(submitPrivacy)}>
<hr />
<h5>Editing privacy settings</h5>
<BS.Form.Row className="mb-3 mb-lg-0">
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control
name="description_privacy"
defaultValue={user.description_privacy}
as="select"
{...registerPrivacy("description_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Member list:</BS.Form.Label>
<BS.Form.Control
name="member_list_privacy"
defaultValue={user.member_list_privacy}
as="select"
{...registerPrivacy("member_list_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Front:</BS.Form.Label>
<BS.Form.Control
name="front_privacy"
as="select"
defaultValue={user.front_privacy}
{...registerPrivacy("front_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Front history:</BS.Form.Label>
<BS.Form.Control
name="front_history_privacy"
defaultValue={user.front_history_privacy}
as="select"
{...registerPrivacy("front_history_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => setPrivacyEdit(false)}>
Cancel
</BS.Button>{" "}
<BS.Button variant="primary" type="submit">
Submit
</BS.Button>
<hr />
</BS.Form>
);
return (
<BS.Form onSubmit={handleSubmitPrivacy(submitPrivacy)}>
<hr />
<h5>Editing privacy settings</h5>
<BS.Form.Row className="mb-3 mb-lg-0">
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control
name="description_privacy"
defaultValue={user.description_privacy}
as="select"
{...registerPrivacy("description_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Member list:</BS.Form.Label>
<BS.Form.Control
name="member_list_privacy"
defaultValue={user.member_list_privacy}
as="select"
{...registerPrivacy("member_list_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Front:</BS.Form.Label>
<BS.Form.Control
name="front_privacy"
as="select"
defaultValue={user.front_privacy}
{...registerPrivacy("front_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Front history:</BS.Form.Label>
<BS.Form.Control
name="front_history_privacy"
defaultValue={user.front_history_privacy}
as="select"
{...registerPrivacy("front_history_privacy")}
>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => setPrivacyEdit(false)}>
Cancel
</BS.Button>{" "}
<BS.Button variant="primary" type="submit">
Submit
</BS.Button>
<hr />
</BS.Form>
);
};
export default EditSystemPrivacy;

View File

@ -16,454 +16,454 @@ import { FaLink, FaLock, FaTrashAlt } from "react-icons/fa";
export default function MemberCard(props) {
const system = JSON.parse(localStorage.getItem('user'));
const sysID = system.id;
const [member, setMember] = useState(props.member);
const system = JSON.parse(localStorage.getItem('user'));
const sysID = system.id;
const [member, setMember] = useState(props.member);
const [ displayName, setDisplayName ] = useState("");
const [ birthday, setBirthday ] = useState("");
const [ birthdate, setBirthdate ] = useState("");
const [ created, setCreated ] = useState("");
const [ pronouns, setPronouns ] = useState("");
const [ editPronouns, setEditPronouns ] = useState("");
const [ avatar, setAvatar ] = useState("");
const [ banner, setBanner ] = useState("");
const [ color, setColor ] = useState("");
const [ desc, setDesc ] = useState("");
const [ editDesc, setEditDesc ] = useState("");
const [ proxyTags, setProxyTags ] = useState(member.proxy_tags);
const [ displayName, setDisplayName ] = useState("");
const [ birthday, setBirthday ] = useState("");
const [ birthdate, setBirthdate ] = useState("");
const [ created, setCreated ] = useState("");
const [ pronouns, setPronouns ] = useState("");
const [ editPronouns, setEditPronouns ] = useState("");
const [ avatar, setAvatar ] = useState("");
const [ banner, setBanner ] = useState("");
const [ color, setColor ] = useState("");
const [ desc, setDesc ] = useState("");
const [ editDesc, setEditDesc ] = useState("");
const [ proxyTags, setProxyTags ] = useState(member.proxy_tags);
const [ editMode, setEditMode ] = useState(false);
const [ privacyEdit, setprivacyEdit ] = useState(false);
const [ privacyView, setPrivacyView ] = useState(false);
const [ proxyView, setProxyView ] = useState(false);
const [ proxyEdit, setproxyEdit ] = useState(false);
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
const [ editMode, setEditMode ] = useState(false);
const [ privacyEdit, setprivacyEdit ] = useState(false);
const [ privacyView, setPrivacyView ] = useState(false);
const [ proxyView, setProxyView ] = useState(false);
const [ proxyEdit, setproxyEdit ] = useState(false);
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
const [ errorAlert, setErrorAlert ] = useState(false);
const [ wrongID, setWrongID ] = useState(false);
const [ memberDeleted, setMemberDeleted ] = useState(false);
const [ errorMessage, setErrorMessage ] = useState("");
const [ errorAlert, setErrorAlert ] = useState(false);
const [ wrongID, setWrongID ] = useState(false);
const [ memberDeleted, setMemberDeleted ] = useState(false);
const [ errorMessage, setErrorMessage ] = useState("");
const {
register: registerEdit,
handleSubmit: handleSubmitEdit,
setValue
} = useForm();
const {
register: registerEdit,
handleSubmit: handleSubmitEdit,
setValue
} = useForm();
const {
register: registerPrivacy,
handleSubmit: handleSubmitPrivacy
} = useForm();
const {
register: registerPrivacy,
handleSubmit: handleSubmitPrivacy
} = useForm();
const {
register: registerDelete,
handleSubmit: handleSubmitDelete
} = useForm();
const {
register: registerProxy,
handleSubmit: handleSubmitProxy,
} = useForm();
const {
register: registerDelete,
handleSubmit: handleSubmitDelete
} = useForm();
const {
register: registerProxy,
handleSubmit: handleSubmitProxy,
} = useForm();
useEffect(() => {
autosize(document.querySelectorAll('textarea'));
})
useEffect(() => {
autosize(document.querySelectorAll('textarea'));
})
useEffect(() => {
const { toHTML } = require('../../Functions/discord-parser.js');
useEffect(() => {
const { toHTML } = require('../../Functions/discord-parser.js');
if (member.display_name) {
setDisplayName(member.display_name)
} else setDisplayName('')
if (member.display_name) {
setDisplayName(member.display_name)
} else setDisplayName('')
if (member.birthday) {
setBirthdate(member.birthday)
if (member.birthday.startsWith('0004-')) {
var bdaymoment = moment(member.birthday, 'YYYY-MM-DD').format('MMM D');
setBirthday(bdaymoment);
} else {
var birthdaymoment = moment(member.birthday, 'YYYY-MM-DD').format('MMM D, YYYY');
setBirthday(birthdaymoment);
}
} else { setBirthday('');
setBirthdate('');
}
if (member.birthday) {
setBirthdate(member.birthday)
if (member.birthday.startsWith('0004-')) {
var bdaymoment = moment(member.birthday, 'YYYY-MM-DD').format('MMM D');
setBirthday(bdaymoment);
} else {
var birthdaymoment = moment(member.birthday, 'YYYY-MM-DD').format('MMM D, YYYY');
setBirthday(birthdaymoment);
}
} else { setBirthday('');
setBirthdate('');
}
var createdmoment = moment(member.created).format('MMM D, YYYY');
setCreated(createdmoment);
var createdmoment = moment(member.created).format('MMM D, YYYY');
setCreated(createdmoment);
if (member.pronouns) {
setPronouns(toHTML(member.pronouns));
setEditPronouns(member.pronouns);
} else { setPronouns('');
setEditPronouns('');
}
if (member.pronouns) {
setPronouns(toHTML(member.pronouns));
setEditPronouns(member.pronouns);
} else { setPronouns('');
setEditPronouns('');
}
if (member.avatar_url) {
var avatarsmall = member.avatar_url.replace('&format=jpeg', '');
setAvatar(avatarsmall.replace('?width=256&height=256', ''))
} else setAvatar('')
if (member.color) {
setColor(member.color);
} else setColor('');
if (member.avatar_url) {
var avatarsmall = member.avatar_url.replace('&format=jpeg', '');
setAvatar(avatarsmall.replace('?width=256&height=256', ''))
} else setAvatar('')
if (member.color) {
setColor(member.color);
} else setColor('');
if (member.banner) {
setBanner(member.banner);
} else setBanner("");
if (member.banner) {
setBanner(member.banner);
} else setBanner("");
if (member.description) {
setDesc(toHTML(member.description));
setEditDesc(member.description);
} else { setDesc("(no description)");
setEditDesc("");
}
}, [member.description, member.color, member.birthday, member.display_name, member.pronouns, member.avatar_url, member.proxy_tags, member.created, member.banner]);
if (member.description) {
setDesc(toHTML(member.description));
setEditDesc(member.description);
} else { setDesc("(no description)");
setEditDesc("");
}
}, [member.description, member.color, member.birthday, member.display_name, member.pronouns, member.avatar_url, member.proxy_tags, member.created, member.banner]);
function submitEditPatch(data) {
fetch(`${API_URL}m/${member.id}`,{
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => {
if (!res.ok) {
throw new Error('HTTP Status ' + res.status);
}
return res.json();
}
).then (data => {
setMember(prevState => {return {...prevState, ...data}});
setErrorAlert(false);
setEditMode(false);
}
).catch (error => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("401: Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
}
function submitEditPatch(data) {
fetch(`${API_URL}m/${member.id}`,{
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => {
if (!res.ok) {
throw new Error('HTTP Status ' + res.status);
}
return res.json();
}
).then (data => {
setMember(prevState => {return {...prevState, ...data}});
setErrorAlert(false);
setEditMode(false);
}
).catch (error => {
console.log(error);
setErrorMessage(error.message);
if (error.message === 'HTTP Status 401') {
setErrorMessage("401: Your token is invalid, please log out and enter a new token.")
};
if (error.message === 'HTTP Status 500') {
setErrorMessage("500: Internal server error.")
}
setErrorAlert(true);
});
}
const submitEdit = data => {
props.edit(Object.assign(member, data));
submitEditPatch(data);
}
const submitEdit = data => {
props.edit(Object.assign(member, data));
submitEditPatch(data);
}
const submitPrivacy = data => {
props.edit(Object.assign(member, data));
submitEditPatch(data);
}
const submitPrivacy = data => {
props.edit(Object.assign(member, data));
submitEditPatch(data);
}
const deleteMember = data => {
if (data.memberID !== member.id) {
setWrongID(true);
} else {
fetch(`${API_URL}m/${member.id}`,{
method: 'DELETE',
headers: {
'Authorization': localStorage.getItem("token")
}}).then (() => {
setErrorAlert(false);
setMemberDeleted(true);
})
.catch (error => {
console.error(error);
setErrorAlert(true);
})
}
}
const deleteMember = data => {
if (data.memberID !== member.id) {
setWrongID(true);
} else {
fetch(`${API_URL}m/${member.id}`,{
method: 'DELETE',
headers: {
'Authorization': localStorage.getItem("token")
}}).then (() => {
setErrorAlert(false);
setMemberDeleted(true);
})
.catch (error => {
console.error(error);
setErrorAlert(true);
})
}
}
function addProxyField() {
setProxyTags(oldTags => [...oldTags, {prefix: '', suffix: ''}] )
}
function addProxyField() {
setProxyTags(oldTags => [...oldTags, {prefix: '', suffix: ''}] )
}
function resetProxyFields() {
setproxyEdit(false);
setProxyTags(member.proxy_tags);
}
function resetProxyFields() {
setproxyEdit(false);
setProxyTags(member.proxy_tags);
}
const submitProxy = data => {
const submitProxy = data => {
const newdata = {proxy_tags: data.proxy_tags.filter(tag => !(tag.prefix === "" && tag.suffix === ""))}
props.edit(Object.assign(member, newdata));
const newdata = {proxy_tags: data.proxy_tags.filter(tag => !(tag.prefix === "" && tag.suffix === ""))}
props.edit(Object.assign(member, newdata));
fetch(`${API_URL}m/${member.id}`,{
method: 'PATCH',
body: JSON.stringify(newdata),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => res.json()
).then (data => {
setMember(prevState => {return {...prevState, ...data}});
setProxyTags(data.proxy_tags);
setErrorAlert(false);
setproxyEdit(false);
}
).catch (error => {
console.error(error);
setErrorAlert(true);
});
}
fetch(`${API_URL}m/${member.id}`,{
method: 'PATCH',
body: JSON.stringify(newdata),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => res.json()
).then (data => {
setMember(prevState => {return {...prevState, ...data}});
setProxyTags(data.proxy_tags);
setErrorAlert(false);
setproxyEdit(false);
}
).catch (error => {
console.error(error);
setErrorAlert(true);
});
}
function copyLink() {
var link = `https://pk-webs.spectralitree.com/profile/${sysID}/${member.id}`
var textField = document.createElement('textarea')
textField.innerText = link
document.body.appendChild(textField);
function copyLink() {
var link = `https://pk-webs.spectralitree.com/profile/${sysID}/${member.id}`
var textField = document.createElement('textarea')
textField.innerText = link
document.body.appendChild(textField);
textField.select();
textField.setSelectionRange(0, 99999);
document.execCommand('copy');
textField.select();
textField.setSelectionRange(0, 99999);
document.execCommand('copy');
document.body.removeChild(textField);
}
document.body.removeChild(textField);
}
function renderCard() {
return (
<BS.Card.Body style={{borderLeft: `5px solid #${color}` }}>
{ errorAlert ? <BS.Alert variant="danger">{errorMessage}</BS.Alert> : "" }
{ editMode ?
<>
<BS.Form id='Edit' onSubmit={handleSubmitEdit(submitEdit)}>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name" {...registerEdit("name")} defaultValue={member.name} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Display name: </BS.Form.Label>
<BS.Form.Control name="display_name" {...registerEdit("display_name")} defaultValue={displayName} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$" name="birthday" {...registerEdit("birthday")} defaultValue={birthdate}/>
<BS.Form.Text>(YYYY-MM-DD)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control as="textarea" rows="1" maxLength="100" name="pronouns" {...registerEdit("pronouns")} defaultValue={editPronouns} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control type="url" name="avatar_url" {...registerEdit("avatar_url")} defaultValue={avatar} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control type="url" name="banner" {...registerEdit("banner")} defaultValue={banner} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Color:</BS.Form.Label>
<BS.Form.Control pattern="[A-Fa-f0-9]{6}" name="color" {...registerEdit("color")} defaultValue={color} />
<BS.Form.Text>(hexcode)</BS.Form.Text>
</BS.Col>
</BS.Form.Row>
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label><br/>
{ localStorage.getItem('template1') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template1'))}>Template 1</BS.Button> : ""} { localStorage.getItem('template2') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template2'))}>Template 2</BS.Button> : ""} { localStorage.getItem('template3') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template3'))}>Template 3</BS.Button> : ""}
<BS.Form.Control maxLength="1000" as="textarea" name="description" {...registerEdit("description")} defaultValue={editDesc}/>
</BS.Form.Group>
<BS.Button variant="light" onClick={() => setEditMode(false)}>Cancel</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button> <BS.Button variant="danger" className="float-right" onClick={() => setOpen(o => !o)}>Delete</BS.Button>
</BS.Form>
<Popup open={open} position="top-center" modal>
<BS.Container>
<BS.Card>
<BS.Card.Header>
<h5><FaTrashAlt className="mr-3"/> Are you sure you want to delete {member.name}?</h5>
</BS.Card.Header>
<BS.Card.Body>
{ wrongID ? <BS.Alert variant="danger">Incorrect ID, please check the spelling.</BS.Alert> : "" }
<p>If you're sure you want to delete this member, please enter the member ID ({member.id}) below.</p>
<BS.Form id='Delete' onSubmit={handleSubmitDelete(deleteMember)}>
<BS.Form.Label>Member ID:</BS.Form.Label>
<BS.Form.Control className="mb-4" name="memberID" {...registerDelete("memberID", {required: true})} placeholder={member.id} />
<BS.Button variant="danger" type="submit">Delete</BS.Button> <BS.Button variant="light" className="float-right" onClick={closeModal}>Cancel</BS.Button>
</BS.Form>
</BS.Card.Body>
</BS.Card>
</BS.Container>
</Popup></>
:
<>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>ID:</b> {member.id}</BS.Col>
{ member.display_name ?
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Display name:</b> { localStorage.getItem("twemoji") ? <span dangerouslySetInnerHTML={{__html: twemoji.parse(displayName)}}></span> : <span dangerouslySetInnerHTML={{__html: displayName}}></span>}</BS.Col> : "" }
{ member.birthday ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Birthday:</b> {birthday}</BS.Col> : "" }
{ member.pronouns ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Pronouns:</b> { localStorage.getItem("twemoji") ? <span dangerouslySetInnerHTML={{__html: twemoji.parse(pronouns)}}></span> : <span dangerouslySetInnerHTML={{__html: pronouns}}></span>}</BS.Col> : "" }
{ member.color ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Color:</b> {color}</BS.Col> : "" }
{ privacyView ? "" : proxyView ? "" : <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Privacy:</b> <BS.Button variant="light" size="sm" onClick={() => setPrivacyView(true)}>View</BS.Button></BS.Col> }
{ privacyView ? "" : proxyView ? "" : <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Proxy tags:</b> <BS.Button variant="light" size="sm" onClick={() => setProxyView(true)}>View</BS.Button></BS.Col> }
{ privacyView || proxyView || !member.banner ? "" :
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Banner:</b>{" "}
<Popup
trigger={
<BS.Button
variant="light"
size="sm"
>
View
</BS.Button>
}
className="banner"
modal
>
{(close) => (
<div className="text-center w-100" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '100%'}}>
<BS.Image src={`${banner}`} style={{maxWidth: 'auto', maxHeight: '640px'}} thumbnail />
</div>
</div>
)}
</Popup>
</BS.Col>
}
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Created:</b> {created}</BS.Col>
</BS.Row>
{ privacyEdit ? <BS.Form id='Privacy' onSubmit={handleSubmitPrivacy(submitPrivacy)}>
<hr/>
<h5>Editing privacy settings</h5>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Visibility:</BS.Form.Label>
<BS.Form.Control name="visibility" defaultValue={member.visibility} as="select" {...registerPrivacy("visibility")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name_privacy" defaultValue={member.name_privacy} as="select" {...registerPrivacy("name_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control name="description_privacy" defaultValue={member.description_privacy} as="select" {...registerPrivacy("description_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar:</BS.Form.Label>
<BS.Form.Control name="avatar_privacy" defaultValue={member.avatar_privacy} as="select" {...registerPrivacy("avatar_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control name="birthday_privacy" defaultValue={member.birthday_privacy} as="select" {...registerPrivacy("birthday_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control name="pronoun_privacy" defaultValue={member.pronoun_privacy} as="select" {...registerPrivacy("pronoun_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}>
<BS.Form.Label>Meta:</BS.Form.Label>
<BS.Form.Control name="metadata_privacy" defaultValue={member.metadata_privacy} as="select" {...registerPrivacy("metadata_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => setprivacyEdit(false)}>Cancel</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button>
<hr/>
</BS.Form> : privacyView ? <><hr/>
<h5>Viewing privacy settings</h5>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Visibility:</b> {member.visibility}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Name: </b>{member.name_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Description:</b> {member.description_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Avatar:</b> {member.avatar_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Birthday:</b> {member.birthday_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Pronouns:</b> {member.pronoun_privacy}</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}><b>Meta:</b> {member.metadata_privacy}</BS.Col>
</BS.Row>
<BS.Button variant="light" onClick={() => setPrivacyView(false)}>Exit</BS.Button> <BS.Button variant="primary" onClick={() => setprivacyEdit(true)}>Edit</BS.Button>
<hr/></> : "" }
{ proxyEdit ?
<><hr/>
<h5>Editing proxy tags</h5>
<BS.Form onSubmit={handleSubmitProxy(submitProxy)}>
<BS.Form.Row>
{ proxyTags.map((item, index) => (
<BS.Col key={index} className="mb-lg-2" xs={12} lg={6}>
<BS.Form.Row>
<BS.InputGroup className="ml-1 mr-1 mb-1">
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].prefix`} defaultValue={item.prefix} {...registerProxy(`proxy_tags[${index}].prefix`)}/>
<BS.Form.Control style={{flex: '0 0 3.5em'}} as="textarea" rows="1" disabled placeholder='text'/>
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].suffix`} defaultValue={item.suffix} {...registerProxy(`proxy_tags[${index}].suffix`)}/>
</BS.InputGroup>
</BS.Form.Row>
</BS.Col>
))} <BS.Col className="mb-2" xs={12} lg={3}><BS.Button block variant="light" onClick={() => addProxyField()}>Add new</BS.Button></BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => resetProxyFields()}>Exit</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button>
</BS.Form><hr/></> : proxyView ?
<><hr/>
<h5>Viewing proxy tags</h5>
<BS.Row className="mb-2">
{ proxyTags.length === 0 ? <BS.Col className="mb-lg-2"><b>No proxy tags set.</b></BS.Col> : proxyTags.map((proxytag, index) => <BS.Col key={index} className="mb-lg-2" xs={12} lg={2}> <code>{proxytag.prefix}text{proxytag.suffix}</code></BS.Col> )}
</BS.Row>
<BS.Button variant="light" onClick={() => setProxyView(false)}>Exit</BS.Button> <BS.Button variant="primary" onClick={() => setproxyEdit(true)}>Edit</BS.Button>
<hr/></> : "" }
<p><b>Description:</b></p>
{ localStorage.getItem("twemoji") ? <p dangerouslySetInnerHTML={{__html: twemoji.parse(desc)}}></p> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
{ !member.banner || !localStorage.getItem("bottombanners") ? "" :
<BS.Image rounded className="mb-2" style={{width: '100%', maxHeight: '15rem', objectFit: 'cover'}} src={banner}/>
}
{ proxyView ? "" : privacyEdit ? "" : privacyView ? "" : <><BS.Button variant="light" onClick={() => setEditMode(true)}>Edit</BS.Button> <Link to={`dash/${member.id}`}><BS.Button variant="primary" className="float-right">View page</BS.Button></Link></> }
</> } </BS.Card.Body>
)
}
function renderCard() {
return (
<BS.Card.Body style={{borderLeft: `5px solid #${color}` }}>
{ errorAlert ? <BS.Alert variant="danger">{errorMessage}</BS.Alert> : "" }
{ editMode ?
<>
<BS.Form id='Edit' onSubmit={handleSubmitEdit(submitEdit)}>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name" {...registerEdit("name")} defaultValue={member.name} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Display name: </BS.Form.Label>
<BS.Form.Control name="display_name" {...registerEdit("display_name")} defaultValue={displayName} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$" name="birthday" {...registerEdit("birthday")} defaultValue={birthdate}/>
<BS.Form.Text>(YYYY-MM-DD)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control as="textarea" rows="1" maxLength="100" name="pronouns" {...registerEdit("pronouns")} defaultValue={editPronouns} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control type="url" name="avatar_url" {...registerEdit("avatar_url")} defaultValue={avatar} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control type="url" name="banner" {...registerEdit("banner")} defaultValue={banner} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Color:</BS.Form.Label>
<BS.Form.Control pattern="[A-Fa-f0-9]{6}" name="color" {...registerEdit("color")} defaultValue={color} />
<BS.Form.Text>(hexcode)</BS.Form.Text>
</BS.Col>
</BS.Form.Row>
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label><br/>
{ localStorage.getItem('template1') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template1'))}>Template 1</BS.Button> : ""} { localStorage.getItem('template2') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template2'))}>Template 2</BS.Button> : ""} { localStorage.getItem('template3') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setValue('description', localStorage.getItem('template3'))}>Template 3</BS.Button> : ""}
<BS.Form.Control maxLength="1000" as="textarea" name="description" {...registerEdit("description")} defaultValue={editDesc}/>
</BS.Form.Group>
<BS.Button variant="light" onClick={() => setEditMode(false)}>Cancel</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button> <BS.Button variant="danger" className="float-right" onClick={() => setOpen(o => !o)}>Delete</BS.Button>
</BS.Form>
<Popup open={open} position="top-center" modal>
<BS.Container>
<BS.Card>
<BS.Card.Header>
<h5><FaTrashAlt className="mr-3"/> Are you sure you want to delete {member.name}?</h5>
</BS.Card.Header>
<BS.Card.Body>
{ wrongID ? <BS.Alert variant="danger">Incorrect ID, please check the spelling.</BS.Alert> : "" }
<p>If you're sure you want to delete this member, please enter the member ID ({member.id}) below.</p>
<BS.Form id='Delete' onSubmit={handleSubmitDelete(deleteMember)}>
<BS.Form.Label>Member ID:</BS.Form.Label>
<BS.Form.Control className="mb-4" name="memberID" {...registerDelete("memberID", {required: true})} placeholder={member.id} />
<BS.Button variant="danger" type="submit">Delete</BS.Button> <BS.Button variant="light" className="float-right" onClick={closeModal}>Cancel</BS.Button>
</BS.Form>
</BS.Card.Body>
</BS.Card>
</BS.Container>
</Popup></>
:
<>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>ID:</b> {member.id}</BS.Col>
{ member.display_name ?
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Display name:</b> { localStorage.getItem("twemoji") ? <span dangerouslySetInnerHTML={{__html: twemoji.parse(displayName)}}></span> : <span dangerouslySetInnerHTML={{__html: displayName}}></span>}</BS.Col> : "" }
{ member.birthday ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Birthday:</b> {birthday}</BS.Col> : "" }
{ member.pronouns ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Pronouns:</b> { localStorage.getItem("twemoji") ? <span dangerouslySetInnerHTML={{__html: twemoji.parse(pronouns)}}></span> : <span dangerouslySetInnerHTML={{__html: pronouns}}></span>}</BS.Col> : "" }
{ member.color ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Color:</b> {color}</BS.Col> : "" }
{ privacyView ? "" : proxyView ? "" : <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Privacy:</b> <BS.Button variant="light" size="sm" onClick={() => setPrivacyView(true)}>View</BS.Button></BS.Col> }
{ privacyView ? "" : proxyView ? "" : <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Proxy tags:</b> <BS.Button variant="light" size="sm" onClick={() => setProxyView(true)}>View</BS.Button></BS.Col> }
{ privacyView || proxyView || !member.banner ? "" :
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Banner:</b>{" "}
<Popup
trigger={
<BS.Button
variant="light"
size="sm"
>
View
</BS.Button>
}
className="banner"
modal
>
{(close) => (
<div className="text-center w-100" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '100%'}}>
<BS.Image src={`${banner}`} style={{maxWidth: 'auto', maxHeight: '640px'}} thumbnail />
</div>
</div>
)}
</Popup>
</BS.Col>
}
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Created:</b> {created}</BS.Col>
</BS.Row>
{ privacyEdit ? <BS.Form id='Privacy' onSubmit={handleSubmitPrivacy(submitPrivacy)}>
<hr/>
<h5>Editing privacy settings</h5>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Visibility:</BS.Form.Label>
<BS.Form.Control name="visibility" defaultValue={member.visibility} as="select" {...registerPrivacy("visibility")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name_privacy" defaultValue={member.name_privacy} as="select" {...registerPrivacy("name_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control name="description_privacy" defaultValue={member.description_privacy} as="select" {...registerPrivacy("description_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar:</BS.Form.Label>
<BS.Form.Control name="avatar_privacy" defaultValue={member.avatar_privacy} as="select" {...registerPrivacy("avatar_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control name="birthday_privacy" defaultValue={member.birthday_privacy} as="select" {...registerPrivacy("birthday_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control name="pronoun_privacy" defaultValue={member.pronoun_privacy} as="select" {...registerPrivacy("pronoun_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}>
<BS.Form.Label>Meta:</BS.Form.Label>
<BS.Form.Control name="metadata_privacy" defaultValue={member.metadata_privacy} as="select" {...registerPrivacy("metadata_privacy")}>
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => setprivacyEdit(false)}>Cancel</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button>
<hr/>
</BS.Form> : privacyView ? <><hr/>
<h5>Viewing privacy settings</h5>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Visibility:</b> {member.visibility}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Name: </b>{member.name_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Description:</b> {member.description_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Avatar:</b> {member.avatar_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Birthday:</b> {member.birthday_privacy}</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>Pronouns:</b> {member.pronoun_privacy}</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}><b>Meta:</b> {member.metadata_privacy}</BS.Col>
</BS.Row>
<BS.Button variant="light" onClick={() => setPrivacyView(false)}>Exit</BS.Button> <BS.Button variant="primary" onClick={() => setprivacyEdit(true)}>Edit</BS.Button>
<hr/></> : "" }
{ proxyEdit ?
<><hr/>
<h5>Editing proxy tags</h5>
<BS.Form onSubmit={handleSubmitProxy(submitProxy)}>
<BS.Form.Row>
{ proxyTags.map((item, index) => (
<BS.Col key={index} className="mb-lg-2" xs={12} lg={6}>
<BS.Form.Row>
<BS.InputGroup className="ml-1 mr-1 mb-1">
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].prefix`} defaultValue={item.prefix} {...registerProxy(`proxy_tags[${index}].prefix`)}/>
<BS.Form.Control style={{flex: '0 0 3.5em'}} as="textarea" rows="1" disabled placeholder='text'/>
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].suffix`} defaultValue={item.suffix} {...registerProxy(`proxy_tags[${index}].suffix`)}/>
</BS.InputGroup>
</BS.Form.Row>
</BS.Col>
))} <BS.Col className="mb-2" xs={12} lg={3}><BS.Button block variant="light" onClick={() => addProxyField()}>Add new</BS.Button></BS.Col>
</BS.Form.Row>
<BS.Button variant="light" onClick={() => resetProxyFields()}>Exit</BS.Button> <BS.Button variant="primary" type="submit">Submit</BS.Button>
</BS.Form><hr/></> : proxyView ?
<><hr/>
<h5>Viewing proxy tags</h5>
<BS.Row className="mb-2">
{ proxyTags.length === 0 ? <BS.Col className="mb-lg-2"><b>No proxy tags set.</b></BS.Col> : proxyTags.map((proxytag, index) => <BS.Col key={index} className="mb-lg-2" xs={12} lg={2}> <code>{proxytag.prefix}text{proxytag.suffix}</code></BS.Col> )}
</BS.Row>
<BS.Button variant="light" onClick={() => setProxyView(false)}>Exit</BS.Button> <BS.Button variant="primary" onClick={() => setproxyEdit(true)}>Edit</BS.Button>
<hr/></> : "" }
<p><b>Description:</b></p>
{ localStorage.getItem("twemoji") ? <p dangerouslySetInnerHTML={{__html: twemoji.parse(desc)}}></p> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
{ !member.banner || !localStorage.getItem("bottombanners") ? "" :
<BS.Image rounded className="mb-2" style={{width: '100%', maxHeight: '15rem', objectFit: 'cover'}} src={banner}/>
}
{ proxyView ? "" : privacyEdit ? "" : privacyView ? "" : <><BS.Button variant="light" onClick={() => setEditMode(true)}>Edit</BS.Button> <Link to={`dash/${member.id}`}><BS.Button variant="primary" className="float-right">View page</BS.Button></Link></> }
</> } </BS.Card.Body>
)
}
return (
memberDeleted ? <BS.Card.Header className="d-flex align-items-center justify-content-between"><BS.Button variant="link" className="float-left"><FaTrashAlt className="mr-4"/>Member Deleted</BS.Button></BS.Card.Header> :
<LazyLoad offset={100}>
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<div> { member.visibility === 'public' ? <BS.OverlayTrigger placement="left" overlay={
<BS.Tooltip>
Copy public link
</BS.Tooltip>
}><BS.Button variant="link" onClick={() => copyLink()}><FaLink style={{fontSize: '1.25rem'}}/></BS.Button></BS.OverlayTrigger> :
<BS.Button variant="link"><FaLock style={{fontSize: '1.25rem'}} /></BS.Button> }
{ localStorage.getItem('pagesonly') ?
<Link to={`dash/${member.id}`}><BS.Button variant="link" className="float-left"><b>{member.name}</b> ({member.id})</BS.Button></Link>
: <BS.Accordion.Toggle as={BS.Button} variant="link" eventKey={member.id}> <b>{member.name}</b> ({member.id})</BS.Accordion.Toggle>}</div>
{ member.avatar_url ? <Popup trigger={<BS.Image src={`${member.avatar_url}`} style={{width: 50, height: 50}} tabIndex="0" className="float-right" roundedCircle />} className="avatar" modal>
{close => (
<div className="text-center w-100 m-0" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '640px'}}>
<BS.Image src={`${avatar}`} style={{'maxWidth': '100%', height: 'auto'}} thumbnail />
</div>
</div>
)}
</Popup> :
<BS.Image src={defaultAvatar} style={{width: 50, height: 50}} tabIndex="0" className="float-right" roundedCircle />}
</BS.Card.Header>
{localStorage.getItem("expandcards") ? renderCard() : <BS.Accordion.Collapse eventKey={member.id}>
{renderCard()}
</BS.Accordion.Collapse>}
</LazyLoad>
)
return (
memberDeleted ? <BS.Card.Header className="d-flex align-items-center justify-content-between"><BS.Button variant="link" className="float-left"><FaTrashAlt className="mr-4"/>Member Deleted</BS.Button></BS.Card.Header> :
<LazyLoad offset={100}>
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<div> { member.visibility === 'public' ? <BS.OverlayTrigger placement="left" overlay={
<BS.Tooltip>
Copy public link
</BS.Tooltip>
}><BS.Button variant="link" onClick={() => copyLink()}><FaLink style={{fontSize: '1.25rem'}}/></BS.Button></BS.OverlayTrigger> :
<BS.Button variant="link"><FaLock style={{fontSize: '1.25rem'}} /></BS.Button> }
{ localStorage.getItem('pagesonly') ?
<Link to={`dash/${member.id}`}><BS.Button variant="link" className="float-left"><b>{member.name}</b> ({member.id})</BS.Button></Link>
: <BS.Accordion.Toggle as={BS.Button} variant="link" eventKey={member.id}> <b>{member.name}</b> ({member.id})</BS.Accordion.Toggle>}</div>
{ member.avatar_url ? <Popup trigger={<BS.Image src={`${member.avatar_url}`} style={{width: 50, height: 50}} tabIndex="0" className="float-right" roundedCircle />} className="avatar" modal>
{close => (
<div className="text-center w-100 m-0" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '640px'}}>
<BS.Image src={`${avatar}`} style={{'maxWidth': '100%', height: 'auto'}} thumbnail />
</div>
</div>
)}
</Popup> :
<BS.Image src={defaultAvatar} style={{width: 50, height: 50}} tabIndex="0" className="float-right" roundedCircle />}
</BS.Card.Header>
{localStorage.getItem("expandcards") ? renderCard() : <BS.Accordion.Collapse eventKey={member.id}>
{renderCard()}
</BS.Accordion.Collapse>}
</LazyLoad>
)
}

View File

@ -14,435 +14,435 @@ import { FaPlus } from "react-icons/fa";
export default function Memberlist() {
const { path } = useRouteMatch();
const user = JSON.parse(localStorage.getItem('user'));
const userId = user.id;
const { path } = useRouteMatch();
const user = JSON.parse(localStorage.getItem('user'));
const userId = user.id;
const [isLoading, setIsLoading ] = useState(false);
const [isError, setIsError ] = useState(false);
const [errorAlert, setErrorAlert ] = useState(false);
const [isLoading, setIsLoading ] = useState(false);
const [isError, setIsError ] = useState(false);
const [errorAlert, setErrorAlert ] = useState(false);
const [proxyView, setProxyView] = useState(false);
const [privacyView, setPrivacyView] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [membersPerPage, setMembersPerPage] = useState(25);
const [proxyView, setProxyView] = useState(false);
const [privacyView, setPrivacyView] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [membersPerPage, setMembersPerPage] = useState(25);
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
const [members, setMembers ] = useState([]);
const [members, setMembers ] = useState([]);
const [searchBy, setSearchBy] = useState('name')
const [privacyFilter, setPrivacyFilter] = useState('all')
const [sortBy, setSortBy] = useState('name')
const [sortOrder, setSortOrder] = useState('ascending')
const [searchBy, setSearchBy] = useState('name')
const [privacyFilter, setPrivacyFilter] = useState('all')
const [sortBy, setSortBy] = useState('name')
const [sortOrder, setSortOrder] = useState('ascending')
const [value, setValue] = useState('');
const [proxyTags, setProxyTags] = useState([{
prefix: "", suffix: ""
}]);
const [value, setValue] = useState('');
const [proxyTags, setProxyTags] = useState([{
prefix: "", suffix: ""
}]);
const {register, handleSubmit, setValue: setTemplate} = useForm();
const {register, handleSubmit, setValue: setTemplate} = useForm();
const fetchMembers = useCallback( () => {
setIsLoading(true);
setIsError(false);
setMembersPerPage(localStorage.getItem("expandcards") ? 10 : 25);
const fetchMembers = useCallback( () => {
setIsLoading(true);
setIsError(false);
setMembersPerPage(localStorage.getItem("expandcards") ? 10 : 25);
fetch(`${API_URL}s/${userId}/members`,{
method: 'GET',
headers: {
'Authorization': localStorage.getItem("token")
}}).then ( res => res.json()
).then (data => {
setMembers(data)
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsError(true);
setIsLoading(false);
})
}, [userId])
fetch(`${API_URL}s/${userId}/members`,{
method: 'GET',
headers: {
'Authorization': localStorage.getItem("token")
}}).then ( res => res.json()
).then (data => {
setMembers(data)
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsError(true);
setIsLoading(false);
})
}, [userId])
useEffect(() => {
fetchMembers();
}, [fetchMembers])
useEffect(() => {
fetchMembers();
}, [fetchMembers])
const indexOfLastMember = currentPage * membersPerPage;
const indexOfFirstMember = indexOfLastMember - membersPerPage;
const indexOfLastMember = currentPage * membersPerPage;
const indexOfFirstMember = indexOfLastMember - membersPerPage;
let Members = members.map(member => {
if (member.display_name) {
return {...member, displayName: member.display_name}
} return {...member, displayName: member.name}
})
let Members1 = Members.map(member => {
if (member.description) {
return {...member, desc: member.description}
} return {...member, desc: "(no description)"}
})
let Members = members.map(member => {
if (member.display_name) {
return {...member, displayName: member.display_name}
} return {...member, displayName: member.name}
})
let Members1 = Members.map(member => {
if (member.description) {
return {...member, desc: member.description}
} return {...member, desc: "(no description)"}
})
const currentMembers = Members1.filter(member => {
if (!value & privacyFilter === 'all') return true;
if (privacyFilter === 'private') {
if (member.visibility !== 'private') {
return false;
}
} else if (privacyFilter === 'public') {
if (member.visibility !== 'public') {
return false;
}
}
const currentMembers = Members1.filter(member => {
if (!value & privacyFilter === 'all') return true;
if (privacyFilter === 'private') {
if (member.visibility !== 'private') {
return false;
}
} else if (privacyFilter === 'public') {
if (member.visibility !== 'public') {
return false;
}
}
if (searchBy === 'name') {
if (member.name.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
} else if (searchBy === 'display name') {
if (member.displayName.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false
} else if (searchBy === 'description') {
if (member.desc.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
} else if (searchBy === 'ID') {
if (member.id.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
}
if (searchBy === 'name') {
if (member.name.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
} else if (searchBy === 'display name') {
if (member.displayName.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false
} else if (searchBy === 'description') {
if (member.desc.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
} else if (searchBy === 'ID') {
if (member.id.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
}
return false;
})
return false;
})
const active = currentPage;
const pageAmount = Math.ceil(currentMembers.length / membersPerPage);
const active = currentPage;
const pageAmount = Math.ceil(currentMembers.length / membersPerPage);
var sortMembers = currentMembers;
if (sortBy === 'name') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.name.localeCompare(b.name)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.name.localeCompare(b.name)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'display name') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.displayName.localeCompare(b.displayName)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.displayName.localeCompare(b.displayName)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'ID') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.id.localeCompare(b.id)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.id.localeCompare(b.id)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'date created') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.created.localeCompare(b.created)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.created.localeCompare(b.created)).slice(indexOfFirstMember, indexOfLastMember);
}
var sortMembers = currentMembers;
if (sortBy === 'name') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.name.localeCompare(b.name)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.name.localeCompare(b.name)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'display name') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.displayName.localeCompare(b.displayName)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.displayName.localeCompare(b.displayName)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'ID') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.id.localeCompare(b.id)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.id.localeCompare(b.id)).slice(indexOfFirstMember, indexOfLastMember);
}
else if (sortBy === 'date created') {
if (sortOrder === 'descending') {
sortMembers = currentMembers.sort((a, b) => a.created.localeCompare(b.created)).reverse().slice(indexOfFirstMember, indexOfLastMember);
} else sortMembers = currentMembers.sort((a, b) => a.created.localeCompare(b.created)).slice(indexOfFirstMember, indexOfLastMember);
}
const memberList = sortMembers.map((member) => <BS.Card key={member.id} className={localStorage.getItem("expandcards") ? "mb-3" : ""}>
<MemberCard
member={member}
edit={memberEdit => setMembers(members.map(member => member.id === memberEdit.id ? Object.assign(member, memberEdit) : member))}
/>
</BS.Card>
);
const memberList = sortMembers.map((member) => <BS.Card key={member.id} className={localStorage.getItem("expandcards") ? "mb-3" : ""}>
<MemberCard
member={member}
edit={memberEdit => setMembers(members.map(member => member.id === memberEdit.id ? Object.assign(member, memberEdit) : member))}
/>
</BS.Card>
);
function addProxyField() {
setProxyTags(oldTags => [...oldTags, {prefix: '', suffix: ''}] )
}
function addProxyField() {
setProxyTags(oldTags => [...oldTags, {prefix: '', suffix: ''}] )
}
const submitMember = data => {
setIsLoading(true);
const submitMember = data => {
setIsLoading(true);
const newdata = data.proxy_tags ? {...data, proxy_tags: data.proxy_tags.filter(tag => !(tag.prefix === "" && tag.suffix === ""))} : data
const newdata = data.proxy_tags ? {...data, proxy_tags: data.proxy_tags.filter(tag => !(tag.prefix === "" && tag.suffix === ""))} : data
fetch(`${API_URL}m/`,{
method: 'POST',
body: JSON.stringify(newdata),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => res.json()
).then (data => {
setErrorAlert(false);
closeModal();
fetchMembers();
}
).catch (error => {
console.error(error);
setErrorAlert(true);
});
}
fetch(`${API_URL}m/`,{
method: 'POST',
body: JSON.stringify(newdata),
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem("token")
}}).then (res => res.json()
).then (data => {
setErrorAlert(false);
closeModal();
fetchMembers();
}
).catch (error => {
console.error(error);
setErrorAlert(true);
});
}
return (
<Switch>
<Route exact path={path}>
<>
<BS.Row className="mb-lg-3 justfiy-content-md-center">
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Page length:'/>
<BS.Form.Control as="select" defaultValue={localStorage.getItem("expandcards") ? 10 : 25} onChange={e => {
setMembersPerPage(e.target.value);
setCurrentPage(1);
}}>
<option>10</option>
<option>25</option>
<option>50</option>
<option>100</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Search by:'/>
<BS.Form.Control as="select" defaultValue={searchBy} onChange={e => {
setSearchBy(e.target.value)
}}>
<option>name</option>
<option>display name</option>
<option>description</option>
<option>ID</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Sort by:'/>
<BS.Form.Control as="select" defaultValue={sortBy} onChange={e => {
setSortBy(e.target.value)
}}>
<option>name</option>
<option>display name</option>
<option>ID</option>
<option>date created</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Sort order:'/>
<BS.Form.Control as="select" defaultValue={sortOrder} onChange={e => {
setSortOrder(e.target.value)
}}>
<option>ascending</option>
<option>descending</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
</BS.Row>
<BS.Row className="justify-content-md-center">
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Only show:'/>
<BS.Form.Control as="select" defaultValue={privacyFilter} onChange={e => {
setPrivacyFilter(e.target.value)
}}>
<option>all</option>
<option>private</option>
<option>public</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={7}>
<BS.Form>
<BS.Form.Control value={value} onChange={e => {setValue(e.target.value); setCurrentPage(1);}} placeholder={`Search by ${searchBy}`}/>
</BS.Form>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={2}>
<BS.Button type="primary" className="m-0" block onClick={() => fetchMembers()}>Refresh</BS.Button>
</BS.Col>
</BS.Row>
<BS.Row className="justify-content-md-center">
<BS.Pagination className="ml-auto mr-auto">
{ currentPage === 1 ? <BS.Pagination.Prev disabled/> : <BS.Pagination.Prev onClick={() => setCurrentPage(currentPage - 1)} />}
{ currentPage < 3 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(1)} active={1 === active}>{1}</BS.Pagination.Item>}
{ currentPage < 4 ? "" : currentPage < 5 ? <BS.Pagination.Item onClick={() => setCurrentPage(2)} active={2 === active}>{2}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > 1 ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage - 1)}>{currentPage - 1}</BS.Pagination.Item> : "" }
<BS.Pagination.Item onClick={() => setCurrentPage(currentPage)} active={currentPage === active}>{currentPage}</BS.Pagination.Item>
{ currentPage < pageAmount ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage + 1)}>{currentPage + 1}</BS.Pagination.Item> : "" }
{ currentPage > pageAmount - 3 ? "" : currentPage === pageAmount - 3 ? <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount - 1)} active={pageAmount - 1 === active}>{pageAmount - 1}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > pageAmount - 2 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount)} active={pageAmount === active}>{pageAmount}</BS.Pagination.Item>}
{ currentPage === pageAmount ? <BS.Pagination.Next disabled /> :<BS.Pagination.Next onClick={() => setCurrentPage(currentPage + 1)} />}
</BS.Pagination>
</BS.Row>
{ isLoading ? <Loading /> : isError ?
<BS.Alert variant="danger">Error fetching members.</BS.Alert> :
<>
<BS.Card className="w-100">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Button variant="link" className="float-left" onClick={() => setOpen(o => !o)}><FaPlus className="mr-4"/>Add Member</BS.Button>
<Popup open={open} position="top-center" modal>
<BS.Container>
<BS.Card>
<BS.Card.Header>
<h5><FaPlus className="mr-3"/> Add member </h5>
</BS.Card.Header>
<BS.Card.Body>
{ errorAlert ? <BS.Alert variant="danger">Something went wrong, please try logging in and out again.</BS.Alert> : "" }
<BS.Form onSubmit={handleSubmit(submitMember)}>
<BS.Form.Text>
</BS.Form.Text>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name"{...register("name")} defaultValue={''} required/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Display name: </BS.Form.Label>
<BS.Form.Control name="display_name" {...register("display_name")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$" name="birthday" {...register("birthday")} defaultValue={''}/>
<BS.Form.Text>(YYYY-MM-DD)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control as="textarea" rows="1" name="pronouns" {...register("pronouns")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control type="url" name="avatar_url" {...register("avatar_url")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control type="url" name="banner" {...register("banner")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Color:</BS.Form.Label>
<BS.Form.Control pattern="[A-Fa-f0-9]{6}" name="color" {...register("color")} defaultValue={''} />
<BS.Form.Text>(hexcode)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={2}>
<BS.Form.Label>Proxy tags:</BS.Form.Label>
<BS.Button variant="primary" block onClick={() => setProxyView(view => !view)}> { proxyView ? "Hide" : "Show" }</BS.Button>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={2}>
<BS.Form.Label>Privacy settings:</BS.Form.Label>
<BS.Button variant="primary" block onClick={() => setPrivacyView(view => !view)}> { privacyView ? "Hide" : "Show" }</BS.Button>
</BS.Col>
</BS.Form.Row>
<hr/>
{ proxyView ? <>
<h5>Proxy Tags</h5>
<BS.Form.Row>
{ proxyTags.map((item, index) => (
<BS.Col key={index} className="mb-lg-2" xs={12} lg={6}>
<BS.Form.Row>
<BS.InputGroup className="ml-1 mr-1 mb-1">
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].prefix`} defaultValue={item.prefix} {...register(`proxy_tags[${index}].prefix`)}/>
<BS.Form.Control style={{flex: '0 0 3.5em'}} as="textarea" rows="1" disabled placeholder='text'/>
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].suffix`} defaultValue={item.suffix} {...register(`proxy_tags[${index}].suffix`)}/>
</BS.InputGroup>
</BS.Form.Row>
</BS.Col>
))} <BS.Col className="mb-lg-2" xs={12} lg={2}><BS.Button block variant="light" onClick={() => addProxyField()}>Add new</BS.Button></BS.Col>
</BS.Form.Row>
<hr/></> : "" }
{ privacyView ? <><h5>privacy settings</h5>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Visibility:</BS.Form.Label>
<BS.Form.Control name="visibility" as="select" {...register("visibility")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name_privacy" as="select" {...register("name_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control name="description_privacy" as="select" {...register("description_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control name="birthday_privacy" as="select" {...register("birthday_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control name="pronoun_privacy" as="select" {...register("pronoun_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}>
<BS.Form.Label>Meta:</BS.Form.Label>
<BS.Form.Control name="metadata_privacy" as="select" {...register("metadata_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<hr/></> : "" }
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label><br/>
{ localStorage.getItem('template1') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template1'))}>Template 1</BS.Button> : ""} { localStorage.getItem('template2') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template2'))}>Template 2</BS.Button> : ""} { localStorage.getItem('template3') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template3'))}>Template 3</BS.Button> : ""}
<BS.Form.Control maxLength="1000" as="textarea" rows="7" name="description" {...register("description")} defaultValue={''}/>
</BS.Form.Group>
<BS.Button variant="primary" type="submit">Submit</BS.Button> <BS.Button variant="light" className="float-right" onClick={closeModal}>Cancel</BS.Button>
</BS.Form>
</BS.Card.Body>
</BS.Card>
</BS.Container>
</Popup>
</BS.Card.Header>
</BS.Card>
<BS.Accordion className="mb-3 mt-3 w-100" defaultActiveKey="0">
{memberList}
</BS.Accordion>
<BS.Row className="justify-content-md-center">
<BS.Pagination className="ml-auto mr-auto">
{ currentPage === 1 ? <BS.Pagination.Prev disabled/> : <BS.Pagination.Prev onClick={() => setCurrentPage(currentPage - 1)} />}
{ currentPage < 3 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(1)} active={1 === active}>{1}</BS.Pagination.Item>}
{ currentPage < 4 ? "" : currentPage < 5 ? <BS.Pagination.Item onClick={() => setCurrentPage(2)} active={2 === active}>{2}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > 1 ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage - 1)}>{currentPage - 1}</BS.Pagination.Item> : "" }
<BS.Pagination.Item onClick={() => setCurrentPage(currentPage)} active={currentPage === active}>{currentPage}</BS.Pagination.Item>
{ currentPage < pageAmount ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage + 1)}>{currentPage + 1}</BS.Pagination.Item> : "" }
{ currentPage > pageAmount - 3 ? "" : currentPage === pageAmount - 3 ? <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount - 1)} active={pageAmount - 1 === active}>{pageAmount - 1}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > pageAmount - 2 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount)} active={pageAmount === active}>{pageAmount}</BS.Pagination.Item>}
{ currentPage === pageAmount ? <BS.Pagination.Next disabled /> :<BS.Pagination.Next onClick={() => setCurrentPage(currentPage + 1)} />}
</BS.Pagination>
</BS.Row>
</>
}
</>
</Route>
<Route path={`${path}/:memberID`}>
{ isLoading ? <Loading/> :
<MemberPages members={members}
edit={memberEdit => setMembers(members.map(member => member.id === memberEdit.id ? Object.assign(member, memberEdit) : member))}/>}
</Route>
</Switch>
)
return (
<Switch>
<Route exact path={path}>
<>
<BS.Row className="mb-lg-3 justfiy-content-md-center">
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Page length:'/>
<BS.Form.Control as="select" defaultValue={localStorage.getItem("expandcards") ? 10 : 25} onChange={e => {
setMembersPerPage(e.target.value);
setCurrentPage(1);
}}>
<option>10</option>
<option>25</option>
<option>50</option>
<option>100</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Search by:'/>
<BS.Form.Control as="select" defaultValue={searchBy} onChange={e => {
setSearchBy(e.target.value)
}}>
<option>name</option>
<option>display name</option>
<option>description</option>
<option>ID</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Sort by:'/>
<BS.Form.Control as="select" defaultValue={sortBy} onChange={e => {
setSortBy(e.target.value)
}}>
<option>name</option>
<option>display name</option>
<option>ID</option>
<option>date created</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Sort order:'/>
<BS.Form.Control as="select" defaultValue={sortOrder} onChange={e => {
setSortOrder(e.target.value)
}}>
<option>ascending</option>
<option>descending</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
</BS.Row>
<BS.Row className="justify-content-md-center">
<BS.Col xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Only show:'/>
<BS.Form.Control as="select" defaultValue={privacyFilter} onChange={e => {
setPrivacyFilter(e.target.value)
}}>
<option>all</option>
<option>private</option>
<option>public</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={7}>
<BS.Form>
<BS.Form.Control value={value} onChange={e => {setValue(e.target.value); setCurrentPage(1);}} placeholder={`Search by ${searchBy}`}/>
</BS.Form>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={2}>
<BS.Button type="primary" className="m-0" block onClick={() => fetchMembers()}>Refresh</BS.Button>
</BS.Col>
</BS.Row>
<BS.Row className="justify-content-md-center">
<BS.Pagination className="ml-auto mr-auto">
{ currentPage === 1 ? <BS.Pagination.Prev disabled/> : <BS.Pagination.Prev onClick={() => setCurrentPage(currentPage - 1)} />}
{ currentPage < 3 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(1)} active={1 === active}>{1}</BS.Pagination.Item>}
{ currentPage < 4 ? "" : currentPage < 5 ? <BS.Pagination.Item onClick={() => setCurrentPage(2)} active={2 === active}>{2}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > 1 ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage - 1)}>{currentPage - 1}</BS.Pagination.Item> : "" }
<BS.Pagination.Item onClick={() => setCurrentPage(currentPage)} active={currentPage === active}>{currentPage}</BS.Pagination.Item>
{ currentPage < pageAmount ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage + 1)}>{currentPage + 1}</BS.Pagination.Item> : "" }
{ currentPage > pageAmount - 3 ? "" : currentPage === pageAmount - 3 ? <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount - 1)} active={pageAmount - 1 === active}>{pageAmount - 1}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > pageAmount - 2 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount)} active={pageAmount === active}>{pageAmount}</BS.Pagination.Item>}
{ currentPage === pageAmount ? <BS.Pagination.Next disabled /> :<BS.Pagination.Next onClick={() => setCurrentPage(currentPage + 1)} />}
</BS.Pagination>
</BS.Row>
{ isLoading ? <Loading /> : isError ?
<BS.Alert variant="danger">Error fetching members.</BS.Alert> :
<>
<BS.Card className="w-100">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Button variant="link" className="float-left" onClick={() => setOpen(o => !o)}><FaPlus className="mr-4"/>Add Member</BS.Button>
<Popup open={open} position="top-center" modal>
<BS.Container>
<BS.Card>
<BS.Card.Header>
<h5><FaPlus className="mr-3"/> Add member </h5>
</BS.Card.Header>
<BS.Card.Body>
{ errorAlert ? <BS.Alert variant="danger">Something went wrong, please try logging in and out again.</BS.Alert> : "" }
<BS.Form onSubmit={handleSubmit(submitMember)}>
<BS.Form.Text>
</BS.Form.Text>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name"{...register("name")} defaultValue={''} required/>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Display name: </BS.Form.Label>
<BS.Form.Control name="display_name" {...register("display_name")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$" name="birthday" {...register("birthday")} defaultValue={''}/>
<BS.Form.Text>(YYYY-MM-DD)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control as="textarea" rows="1" name="pronouns" {...register("pronouns")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Avatar url:</BS.Form.Label>
<BS.Form.Control type="url" name="avatar_url" {...register("avatar_url")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Banner url:</BS.Form.Label>
<BS.Form.Control type="url" name="banner" {...register("banner")} defaultValue={''} />
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Color:</BS.Form.Label>
<BS.Form.Control pattern="[A-Fa-f0-9]{6}" name="color" {...register("color")} defaultValue={''} />
<BS.Form.Text>(hexcode)</BS.Form.Text>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={2}>
<BS.Form.Label>Proxy tags:</BS.Form.Label>
<BS.Button variant="primary" block onClick={() => setProxyView(view => !view)}> { proxyView ? "Hide" : "Show" }</BS.Button>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={2}>
<BS.Form.Label>Privacy settings:</BS.Form.Label>
<BS.Button variant="primary" block onClick={() => setPrivacyView(view => !view)}> { privacyView ? "Hide" : "Show" }</BS.Button>
</BS.Col>
</BS.Form.Row>
<hr/>
{ proxyView ? <>
<h5>Proxy Tags</h5>
<BS.Form.Row>
{ proxyTags.map((item, index) => (
<BS.Col key={index} className="mb-lg-2" xs={12} lg={6}>
<BS.Form.Row>
<BS.InputGroup className="ml-1 mr-1 mb-1">
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].prefix`} defaultValue={item.prefix} {...register(`proxy_tags[${index}].prefix`)}/>
<BS.Form.Control style={{flex: '0 0 3.5em'}} as="textarea" rows="1" disabled placeholder='text'/>
<BS.Form.Control as="textarea" rows="1" name={`proxy_tags[${index}].suffix`} defaultValue={item.suffix} {...register(`proxy_tags[${index}].suffix`)}/>
</BS.InputGroup>
</BS.Form.Row>
</BS.Col>
))} <BS.Col className="mb-lg-2" xs={12} lg={2}><BS.Button block variant="light" onClick={() => addProxyField()}>Add new</BS.Button></BS.Col>
</BS.Form.Row>
<hr/></> : "" }
{ privacyView ? <><h5>privacy settings</h5>
<BS.Form.Row>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Visibility:</BS.Form.Label>
<BS.Form.Control name="visibility" as="select" {...register("visibility")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Name:</BS.Form.Label>
<BS.Form.Control name="name_privacy" as="select" {...register("name_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Description:</BS.Form.Label>
<BS.Form.Control name="description_privacy" as="select" {...register("description_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Birthday:</BS.Form.Label>
<BS.Form.Control name="birthday_privacy" as="select" {...register("birthday_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-lg-2" xs={12} lg={3}>
<BS.Form.Label>Pronouns:</BS.Form.Label>
<BS.Form.Control name="pronoun_privacy" as="select" {...register("pronoun_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
<BS.Col className="mb-3" xs={12} lg={3}>
<BS.Form.Label>Meta:</BS.Form.Label>
<BS.Form.Control name="metadata_privacy" as="select" {...register("metadata_privacy")} >
<option>public</option>
<option>private</option>
</BS.Form.Control>
</BS.Col>
</BS.Form.Row>
<hr/></> : "" }
<BS.Form.Group className="mt-3">
<BS.Form.Label>Description:</BS.Form.Label><br/>
{ localStorage.getItem('template1') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template1'))}>Template 1</BS.Button> : ""} { localStorage.getItem('template2') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template2'))}>Template 2</BS.Button> : ""} { localStorage.getItem('template3') ? <BS.Button className="mb-2" size="sm" variant="primary" onClick={() => setTemplate('description', localStorage.getItem('template3'))}>Template 3</BS.Button> : ""}
<BS.Form.Control maxLength="1000" as="textarea" rows="7" name="description" {...register("description")} defaultValue={''}/>
</BS.Form.Group>
<BS.Button variant="primary" type="submit">Submit</BS.Button> <BS.Button variant="light" className="float-right" onClick={closeModal}>Cancel</BS.Button>
</BS.Form>
</BS.Card.Body>
</BS.Card>
</BS.Container>
</Popup>
</BS.Card.Header>
</BS.Card>
<BS.Accordion className="mb-3 mt-3 w-100" defaultActiveKey="0">
{memberList}
</BS.Accordion>
<BS.Row className="justify-content-md-center">
<BS.Pagination className="ml-auto mr-auto">
{ currentPage === 1 ? <BS.Pagination.Prev disabled/> : <BS.Pagination.Prev onClick={() => setCurrentPage(currentPage - 1)} />}
{ currentPage < 3 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(1)} active={1 === active}>{1}</BS.Pagination.Item>}
{ currentPage < 4 ? "" : currentPage < 5 ? <BS.Pagination.Item onClick={() => setCurrentPage(2)} active={2 === active}>{2}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > 1 ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage - 1)}>{currentPage - 1}</BS.Pagination.Item> : "" }
<BS.Pagination.Item onClick={() => setCurrentPage(currentPage)} active={currentPage === active}>{currentPage}</BS.Pagination.Item>
{ currentPage < pageAmount ? <BS.Pagination.Item onClick={() => setCurrentPage(currentPage + 1)}>{currentPage + 1}</BS.Pagination.Item> : "" }
{ currentPage > pageAmount - 3 ? "" : currentPage === pageAmount - 3 ? <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount - 1)} active={pageAmount - 1 === active}>{pageAmount - 1}</BS.Pagination.Item> : <BS.Pagination.Ellipsis disabled />}
{ currentPage > pageAmount - 2 ? "" : <BS.Pagination.Item onClick={() => setCurrentPage(pageAmount)} active={pageAmount === active}>{pageAmount}</BS.Pagination.Item>}
{ currentPage === pageAmount ? <BS.Pagination.Next disabled /> :<BS.Pagination.Next onClick={() => setCurrentPage(currentPage + 1)} />}
</BS.Pagination>
</BS.Row>
</>
}
</>
</Route>
<Route path={`${path}/:memberID`}>
{ isLoading ? <Loading/> :
<MemberPages members={members}
edit={memberEdit => setMembers(members.map(member => member.id === memberEdit.id ? Object.assign(member, memberEdit) : member))}/>}
</Route>
</Switch>
)
}

View File

@ -13,266 +13,266 @@ import EditSystem from "./Edit/EditSystem.js";
import EditSystemPrivacy from "./Edit/EditSystemPrivacy.js";
export default function System() {
// match the url, if there's a member ID there, don't render this component at all
const match = useRouteMatch("/dash/:memberID");
// match the url, if there's a member ID there, don't render this component at all
const match = useRouteMatch("/dash/:memberID");
// get the user from the localstorage
const [user, setUser] = useState(JSON.parse(localStorage.getItem("user")));
// get the user from the localstorage
const [user, setUser] = useState(JSON.parse(localStorage.getItem("user")));
// bunch of useState stuff, used in the useEffect() hook below
const [name, setName] = useState("");
const [tag, setTag] = useState("");
const [timezone, setTimezone] = useState("");
const [avatar, setAvatar] = useState("");
const [banner, setBanner] = useState("");
const [desc, setDesc] = useState("");
const [editDesc, setEditDesc] = useState("");
// bunch of useState stuff, used in the useEffect() hook below
const [name, setName] = useState("");
const [tag, setTag] = useState("");
const [timezone, setTimezone] = useState("");
const [avatar, setAvatar] = useState("");
const [banner, setBanner] = useState("");
const [desc, setDesc] = useState("");
const [editDesc, setEditDesc] = useState("");
// more useState, this time to actually handle state
// TODO: name them something more intuitive maybe?
const [editMode, setEditMode] = useState(false);
const [privacyEdit, setPrivacyEdit] = useState(false);
const [privacyView, setPrivacyView] = useState(false);
// more useState, this time to actually handle state
// TODO: name them something more intuitive maybe?
const [editMode, setEditMode] = useState(false);
const [privacyEdit, setPrivacyEdit] = useState(false);
const [privacyView, setPrivacyView] = useState(false);
const [errorAlert, setErrorAlert] = useState(false);
const [ errorMessage, setErrorMessage ] = useState("");
const [errorAlert, setErrorAlert] = useState(false);
const [ errorMessage, setErrorMessage ] = useState("");
// this useEffect does a couple of things after the user is gotten from localstorage
useEffect(() => {
// first require the discord markdown parser
const { toHTML } = require("../../Functions/discord-parser.js");
// this useEffect does a couple of things after the user is gotten from localstorage
useEffect(() => {
// first require the discord markdown parser
const { toHTML } = require("../../Functions/discord-parser.js");
// check if there's a name object in user, if it's null, set name to a blank string, otherwise set name to user.name
if (user.name) {
setName(user.name);
} else setName("");
// check if there's a name object in user, if it's null, set name to a blank string, otherwise set name to user.name
if (user.name) {
setName(user.name);
} else setName("");
// same as above, but with the user tag instead
if (user.tag) {
setTag(user.tag);
} else setTag("");
// same as above, but with the user tag instead
if (user.tag) {
setTag(user.tag);
} else setTag("");
// same as above but with timezone
if (user.tz) {
setTimezone(user.tz);
} else setTimezone("");
// same as above but with timezone
if (user.tz) {
setTimezone(user.tz);
} else setTimezone("");
// also trims the avatar url so that 1. pngs won't be converted to jpegs and 2. won't be resized to 256x256
if (user.avatar_url) {
var avatarsmall = user.avatar_url.replace("&format=jpeg", "");
setAvatar(avatarsmall.replace("?width=256&height=256", ""));
} else setAvatar("");
// also trims the avatar url so that 1. pngs won't be converted to jpegs and 2. won't be resized to 256x256
if (user.avatar_url) {
var avatarsmall = user.avatar_url.replace("&format=jpeg", "");
setAvatar(avatarsmall.replace("?width=256&height=256", ""));
} else setAvatar("");
if (user.banner) {
setBanner(user.banner);
} else setBanner("");
if (user.banner) {
setBanner(user.banner);
} else setBanner("");
// same as above, but with descriptions
// two description variables! one is just the plain description, the other is parsed and converted to html
if (user.description) {
setDesc(toHTML(user.description));
setEditDesc(user.description);
} else {
setDesc("(no description)");
setEditDesc("");
}
}, [user.description, user.tag, user.avatar_url, user.tz, user.name, user.banner]);
// same as above, but with descriptions
// two description variables! one is just the plain description, the other is parsed and converted to html
if (user.description) {
setDesc(toHTML(user.description));
setEditDesc(user.description);
} else {
setDesc("(no description)");
setEditDesc("");
}
}, [user.description, user.tag, user.avatar_url, user.tz, user.name, user.banner]);
// this just resizes the textarea when filled with larger amounts of text
useEffect(() => {
autosize(document.querySelector("textarea"));
});
// this just resizes the textarea when filled with larger amounts of text
useEffect(() => {
autosize(document.querySelector("textarea"));
});
if (match) return null;
if (match) return null;
return (
<>
{ user.banner && !localStorage.getItem("hidebanners") ? <div className="banner" style={{backgroundImage: `url(${user.banner})`}} alt=""/> : ""}
<BS.Card className="mb-3 mt-3 w-100">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title className="float-left">
<FaAddressCard className="mr-4 float-left" /> {name} ({user.id})
</BS.Card.Title>
{user.avatar_url ? (
<Popup
trigger={
<BS.Image
src={`${user.avatar_url}`}
style={{ width: 50, height: 50 }}
tabIndex="0"
className="float-right"
roundedCircle
/>
}
className="avatar"
modal
>
{(close) => (
<div className="text-center w-100 m-0" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '640px'}}>
<BS.Image src={`${avatar}`} style={{'maxWidth': '100%', height: 'auto'}} thumbnail />
</div>
</div>
)}
</Popup>
) : (
<BS.Image
src={defaultAvatar}
style={{ width: 50, height: 50 }}
className="float-right"
roundedCircle
/>
)}
</BS.Card.Header>
<BS.Card.Body>
{errorAlert ? (
<BS.Alert variant="danger">
{errorMessage}
</BS.Alert>
) : (
""
)}
{editMode ? (
<EditSystem
editDesc={editDesc}
name={name}
tag={tag}
timezone={timezone}
avatar={avatar}
banner={banner}
setErrorAlert={setErrorAlert}
user={user}
setUser={setUser}
setEditMode={setEditMode}
setErrorMessage={setErrorMessage}
/>
) : (
<>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>ID:</b> {user.id}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Tag:</b> {tag}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Timezone:</b> {timezone}
</BS.Col>
{privacyView ? (
""
) : (
<>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Privacy:</b>{" "}
<BS.Button
variant="light"
size="sm"
onClick={() => setPrivacyView(true)}
>
View
</BS.Button>
</BS.Col>
{user.banner ?
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Banner:</b>{" "}
<Popup
trigger={
<BS.Button
variant="light"
size="sm"
>
View
</BS.Button>
}
className="banner"
modal
>
{(close) => (
<div className="text-center w-100" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '100%'}}>
<BS.Image src={`${banner}`} style={{maxWidth: 'auto', maxHeight: '640px'}} thumbnail />
</div>
</div>
)}
</Popup>
</BS.Col>
: "" }
</>
)}
</BS.Row>
{privacyEdit ? (
<EditSystemPrivacy
setErrorAlert={setErrorAlert}
setUser={setUser}
user={user}
setPrivacyEdit={setPrivacyEdit}
setErrorMessage={setErrorMessage}
/>
) : privacyView ? (
<>
<hr />
<h5>Viewing privacy settings</h5>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Description:</b> {user.description_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Member list: </b>
{user.member_list_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Front:</b> {user.front_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Front history:</b> {user.front_history_privacy}
</BS.Col>
</BS.Row>
<BS.Button
variant="light"
onClick={() => setPrivacyView(false)}
>
Exit
</BS.Button>{" "}
<BS.Button
variant="primary"
onClick={() => setPrivacyEdit(true)}
>
Edit
</BS.Button>
<hr />
</>
) : (
""
)}
<p>
<b>Description:</b>
</p>
{ localStorage.getItem("twemoji") ? <p dangerouslySetInnerHTML={{__html: twemoji.parse(desc)}}></p> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
{ !user.banner || !localStorage.getItem("bottombanners") ? "" :
<BS.Image rounded className="mb-2" style={{width: '100%', maxHeight: '15rem', objectFit: 'cover'}} src={banner}/>
}
{privacyEdit ? (
""
) : privacyView ? (
""
) : (
<>
<BS.Button variant="light" onClick={() => setEditMode(true)}>
Edit
</BS.Button>
<BS.Button
variant="primary"
className="float-right"
onClick={() => history.push(`/profile/${user.id}`)}
>
Profile
</BS.Button>
</>
)}
</>
)}
</BS.Card.Body>
</BS.Card>
</>
);
return (
<>
{ user.banner && !localStorage.getItem("hidebanners") ? <div className="banner" style={{backgroundImage: `url(${user.banner})`}} alt=""/> : ""}
<BS.Card className="mb-3 mt-3 w-100">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title className="float-left">
<FaAddressCard className="mr-4 float-left" /> {name} ({user.id})
</BS.Card.Title>
{user.avatar_url ? (
<Popup
trigger={
<BS.Image
src={`${user.avatar_url}`}
style={{ width: 50, height: 50 }}
tabIndex="0"
className="float-right"
roundedCircle
/>
}
className="avatar"
modal
>
{(close) => (
<div className="text-center w-100 m-0" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '640px'}}>
<BS.Image src={`${avatar}`} style={{'maxWidth': '100%', height: 'auto'}} thumbnail />
</div>
</div>
)}
</Popup>
) : (
<BS.Image
src={defaultAvatar}
style={{ width: 50, height: 50 }}
className="float-right"
roundedCircle
/>
)}
</BS.Card.Header>
<BS.Card.Body>
{errorAlert ? (
<BS.Alert variant="danger">
{errorMessage}
</BS.Alert>
) : (
""
)}
{editMode ? (
<EditSystem
editDesc={editDesc}
name={name}
tag={tag}
timezone={timezone}
avatar={avatar}
banner={banner}
setErrorAlert={setErrorAlert}
user={user}
setUser={setUser}
setEditMode={setEditMode}
setErrorMessage={setErrorMessage}
/>
) : (
<>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>ID:</b> {user.id}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Tag:</b> {tag}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Timezone:</b> {timezone}
</BS.Col>
{privacyView ? (
""
) : (
<>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Privacy:</b>{" "}
<BS.Button
variant="light"
size="sm"
onClick={() => setPrivacyView(true)}
>
View
</BS.Button>
</BS.Col>
{user.banner ?
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Banner:</b>{" "}
<Popup
trigger={
<BS.Button
variant="light"
size="sm"
>
View
</BS.Button>
}
className="banner"
modal
>
{(close) => (
<div className="text-center w-100" onClick={() => close()}>
<div className="m-auto" style={{maxWidth: '100%'}}>
<BS.Image src={`${banner}`} style={{maxWidth: 'auto', maxHeight: '640px'}} thumbnail />
</div>
</div>
)}
</Popup>
</BS.Col>
: "" }
</>
)}
</BS.Row>
{privacyEdit ? (
<EditSystemPrivacy
setErrorAlert={setErrorAlert}
setUser={setUser}
user={user}
setPrivacyEdit={setPrivacyEdit}
setErrorMessage={setErrorMessage}
/>
) : privacyView ? (
<>
<hr />
<h5>Viewing privacy settings</h5>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Description:</b> {user.description_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Member list: </b>
{user.member_list_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Front:</b> {user.front_privacy}
</BS.Col>
<BS.Col className="mb-lg-3" xs={12} lg={3}>
<b>Front history:</b> {user.front_history_privacy}
</BS.Col>
</BS.Row>
<BS.Button
variant="light"
onClick={() => setPrivacyView(false)}
>
Exit
</BS.Button>{" "}
<BS.Button
variant="primary"
onClick={() => setPrivacyEdit(true)}
>
Edit
</BS.Button>
<hr />
</>
) : (
""
)}
<p>
<b>Description:</b>
</p>
{ localStorage.getItem("twemoji") ? <p dangerouslySetInnerHTML={{__html: twemoji.parse(desc)}}></p> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
{ !user.banner || !localStorage.getItem("bottombanners") ? "" :
<BS.Image rounded className="mb-2" style={{width: '100%', maxHeight: '15rem', objectFit: 'cover'}} src={banner}/>
}
{privacyEdit ? (
""
) : privacyView ? (
""
) : (
<>
<BS.Button variant="light" onClick={() => setEditMode(true)}>
Edit
</BS.Button>
<BS.Button
variant="primary"
className="float-right"
onClick={() => history.push(`/profile/${user.id}`)}
>
Profile
</BS.Button>
</>
)}
</>
)}
</BS.Card.Body>
</BS.Card>
</>
);
}

View File

@ -10,202 +10,202 @@ import { FaLockOpen, FaHome } from "react-icons/fa";
import API_URL from "../Constants/constants.js";
const Home = ({isInvalid, setIsInvalid, isLoading, setIsLoading, isSubmit, setIsSubmit, forceUpdate }) => {
const { register, handleSubmit } = useForm();
const { register, handleSubmit } = useForm();
const [ errorMessage, setErrorMessage ] = useState("");
const [ errorMessage, setErrorMessage ] = useState("");
// submit login form, add the token to the localstorage
const onSubmit = (data) => {
localStorage.setItem("token", data.pkToken);
logIn();
};
// submit login form, add the token to the localstorage
const onSubmit = (data) => {
localStorage.setItem("token", data.pkToken);
logIn();
};
function logIn() {
function logIn() {
// make sure the token is not set to invalid and add a funny little spinner to indicate loading
setIsInvalid(false);
setIsLoading(true);
// then fetch the system data with the token stored in localstorage
fetch(`${API_URL}s/`, {
method: "GET",
headers: {
Authorization: localStorage.getItem("token"),
},
})
// put all the system data in localstorage
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status);
return res.json();
})
.then((data) => {
localStorage.setItem("user", JSON.stringify(data));
setIsSubmit(true);
setIsLoading(false);
history.push("/dash");
})
// remove the token and user data from localstorage if there's an error, also set the token as invalid
.catch((error) => {
console.log(error);
setIsInvalid(true);
setErrorMessage(error.message);
if (error.message === "HTTP Status 401")
setErrorMessage("Your token is invalid.")
localStorage.removeItem("token");
localStorage.removeItem("user");
setIsLoading(false);
});
}
// make sure the token is not set to invalid and add a funny little spinner to indicate loading
setIsInvalid(false);
setIsLoading(true);
// then fetch the system data with the token stored in localstorage
fetch(`${API_URL}s/`, {
method: "GET",
headers: {
Authorization: localStorage.getItem("token"),
},
})
// put all the system data in localstorage
.then((res) => {
if (!res.ok)
throw new Error('HTTP Status ' + res.status);
return res.json();
})
.then((data) => {
localStorage.setItem("user", JSON.stringify(data));
setIsSubmit(true);
setIsLoading(false);
history.push("/dash");
})
// remove the token and user data from localstorage if there's an error, also set the token as invalid
.catch((error) => {
console.log(error);
setIsInvalid(true);
setErrorMessage(error.message);
if (error.message === "HTTP Status 401")
setErrorMessage("Your token is invalid.")
localStorage.removeItem("token");
localStorage.removeItem("user");
setIsLoading(false);
});
}
// Logout function
function logOut() {
setIsSubmit(false);
localStorage.removeItem("token");
localStorage.removeItem("user");
history.push("/");
forceUpdate();
};
// Logout function
function logOut() {
setIsSubmit(false);
localStorage.removeItem("token");
localStorage.removeItem("user");
history.push("/");
forceUpdate();
};
// when the homepage loads, check if there's a token, if there is, check if it's still valid
// removing the dependency array causes a rerender loop, so just ignore ESlint here
useEffect(() => {
if (localStorage.getItem('token')) {
checkLogIn();
}
}, []);
// when the homepage loads, check if there's a token, if there is, check if it's still valid
// removing the dependency array causes a rerender loop, so just ignore ESlint here
useEffect(() => {
if (localStorage.getItem('token')) {
checkLogIn();
}
}, []);
// very similar to LogIn(), only difference is that it doesn't push the user afterwards
// TODO: useless double code that probably could be refactored somehow
function checkLogIn() {
setIsInvalid(false);
setIsLoading(true);
fetch(`${API_URL}s/`,{
method: 'GET',
headers: {
'Authorization': localStorage.getItem("token")
}}).then ( res => res.json()
).then (data => {
localStorage.setItem('user', JSON.stringify(data));
setIsSubmit(true);
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsInvalid(true);
localStorage.removeItem('token');
localStorage.removeItem('user');
setIsLoading(false);
})
};
// very similar to LogIn(), only difference is that it doesn't push the user afterwards
// TODO: useless double code that probably could be refactored somehow
function checkLogIn() {
setIsInvalid(false);
setIsLoading(true);
fetch(`${API_URL}s/`,{
method: 'GET',
headers: {
'Authorization': localStorage.getItem("token")
}}).then ( res => res.json()
).then (data => {
localStorage.setItem('user', JSON.stringify(data));
setIsSubmit(true);
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsInvalid(true);
localStorage.removeItem('token');
localStorage.removeItem('user');
setIsLoading(false);
})
};
return (
<>
{/* if the page is loading, just show the loading component */}
{isLoading ? (
<Loading />
) : (
<BS.Card className="mb-3 mt-3">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title>
<FaLockOpen className="mr-3" />
Login
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
{/* if the login form has been submitted, and there's no user object, show a generic error */}
{isSubmit && !localStorage.getItem("user") ? (
<BS.Alert variant="danger">
Something went wrong, please try again.
</BS.Alert>
) : (
""
)}
{/* if the inserted token is invalid, show invalid error!
this also shows if the token used in checkLogIn() is invalid */}
{isInvalid ? (
<BS.Alert variant="danger">{errorMessage}</BS.Alert>
) : (
""
)}
{ // if there's a user object in localstorage, and there's a token in localstorage, the user is logged in already
localStorage.getItem("user") && localStorage.getItem("token") ? (
<>
<p>
You are logged in already, click here to continue to the dash.
</p>
<BS.Button
variant="primary"
onClick={() => history.push("/dash")}
>
Continue to dash
</BS.Button>
<BS.Button style={{float: 'right'}} variant="danger"
onClick={() => logOut()}
>Log out
</BS.Button>
</>
) : (
// otherwise, show login form
<BS.Form onSubmit={handleSubmit(onSubmit)}>
<BS.Form.Row>
<BS.Col className="mb-1" xs={12} lg={10}>
<BS.Form.Label>
Enter your token here. You can get your token by using{" "}
<b>"pk;token"</b>.
</BS.Form.Label>
</BS.Col>
</BS.Form.Row>
<BS.Form.Row>
<BS.Col xs={12} lg={10}>
<BS.Form.Control
required
name="pkToken"
type="text"
{...register("pkToken")}
placeholder="token"
/>
</BS.Col>
<BS.Col>
<BS.Button variant="primary" type="submit" block>
Submit
</BS.Button>
</BS.Col>
</BS.Form.Row>
</BS.Form>
)}
</BS.Card.Body>
</BS.Card>
)}
<BS.Card>
<BS.Card.Header>
<BS.Card.Title>
<FaHome className="mr-3" />
Welcome!
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
<p>Pk-webs is a web interface for PluralKit, it lets you edit your system and members using PluralKit's API, without having to use commands on discord.</p>
<blockquote>
<p>This website is an ongoing project and will be sporadically updated. If you have any issues or questions, join <a href='https://discord.gg/PczBt78'>PluralKit's support server</a> and ping us (Fulmine#1917). Since this project is unofficial, the actual pluralkit devs will not be able to help you with everything.</p>
</blockquote>
<hr/>
<h5>FAQ:</h5>
Will groups be added to this website?
<blockquote>In the future, yes! Groups are not in the API as of now, which is what this site depends on. When APIv2 comes out, they will eventually be added here too.</blockquote>
Will switch history/editing switches be added to this website?
<blockquote>Probably when APIv2 comes out, right now the API is very limited in how it can handle switches. And we'd rather add everything in one go.</blockquote>
What about bulk editing?
<blockquote>Probably not. Bulk commands are planned for the main bot, and we're not sure how to fit them into the main site at the moment.</blockquote>
Can you add [other feature]?
<blockquote>Depends on the feature. Ping us in the support server and we'll let you know if it's feasible or not. If it's accessibility related, chances are good that we'll add it.</blockquote>
Can i contribute to this?
<blockquote>Yeah sure! The code is open source on <a href='https://github.com/Spectralitree/pk-webs/'>github</a>. Be warned though that we're currently in the middle of cleaning it all up and adding comments to everything, so some sections are quite messy still.</blockquote>
</BS.Card.Body>
</BS.Card>
</>
);
return (
<>
{/* if the page is loading, just show the loading component */}
{isLoading ? (
<Loading />
) : (
<BS.Card className="mb-3 mt-3">
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title>
<FaLockOpen className="mr-3" />
Login
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
{/* if the login form has been submitted, and there's no user object, show a generic error */}
{isSubmit && !localStorage.getItem("user") ? (
<BS.Alert variant="danger">
Something went wrong, please try again.
</BS.Alert>
) : (
""
)}
{/* if the inserted token is invalid, show invalid error!
this also shows if the token used in checkLogIn() is invalid */}
{isInvalid ? (
<BS.Alert variant="danger">{errorMessage}</BS.Alert>
) : (
""
)}
{ // if there's a user object in localstorage, and there's a token in localstorage, the user is logged in already
localStorage.getItem("user") && localStorage.getItem("token") ? (
<>
<p>
You are logged in already, click here to continue to the dash.
</p>
<BS.Button
variant="primary"
onClick={() => history.push("/dash")}
>
Continue to dash
</BS.Button>
<BS.Button style={{float: 'right'}} variant="danger"
onClick={() => logOut()}
>Log out
</BS.Button>
</>
) : (
// otherwise, show login form
<BS.Form onSubmit={handleSubmit(onSubmit)}>
<BS.Form.Row>
<BS.Col className="mb-1" xs={12} lg={10}>
<BS.Form.Label>
Enter your token here. You can get your token by using{" "}
<b>"pk;token"</b>.
</BS.Form.Label>
</BS.Col>
</BS.Form.Row>
<BS.Form.Row>
<BS.Col xs={12} lg={10}>
<BS.Form.Control
required
name="pkToken"
type="text"
{...register("pkToken")}
placeholder="token"
/>
</BS.Col>
<BS.Col>
<BS.Button variant="primary" type="submit" block>
Submit
</BS.Button>
</BS.Col>
</BS.Form.Row>
</BS.Form>
)}
</BS.Card.Body>
</BS.Card>
)}
<BS.Card>
<BS.Card.Header>
<BS.Card.Title>
<FaHome className="mr-3" />
Welcome!
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
<p>Pk-webs is a web interface for PluralKit, it lets you edit your system and members using PluralKit's API, without having to use commands on discord.</p>
<blockquote>
<p>This website is an ongoing project and will be sporadically updated. If you have any issues or questions, join <a href='https://discord.gg/PczBt78'>PluralKit's support server</a> and ping us (Fulmine#1917). Since this project is unofficial, the actual pluralkit devs will not be able to help you with everything.</p>
</blockquote>
<hr/>
<h5>FAQ:</h5>
Will groups be added to this website?
<blockquote>In the future, yes! Groups are not in the API as of now, which is what this site depends on. When APIv2 comes out, they will eventually be added here too.</blockquote>
Will switch history/editing switches be added to this website?
<blockquote>Probably when APIv2 comes out, right now the API is very limited in how it can handle switches. And we'd rather add everything in one go.</blockquote>
What about bulk editing?
<blockquote>Probably not. Bulk commands are planned for the main bot, and we're not sure how to fit them into the main site at the moment.</blockquote>
Can you add [other feature]?
<blockquote>Depends on the feature. Ping us in the support server and we'll let you know if it's feasible or not. If it's accessibility related, chances are good that we'll add it.</blockquote>
Can i contribute to this?
<blockquote>Yeah sure! The code is open source on <a href='https://github.com/Spectralitree/pk-webs/'>github</a>. Be warned though that we're currently in the middle of cleaning it all up and adding comments to everything, so some sections are quite messy still.</blockquote>
</BS.Card.Body>
</BS.Card>
</>
);
};
export default Home;

View File

@ -7,48 +7,48 @@ import API_URL from '../Constants/constants';
import ProfilePage from '../Components/Public/ProfilePage';
const MemberProfile = () => {
const { memberID } = useParams();
const { memberID } = useParams();
const [isLoading, setIsLoading ] = useState(false);
const [isError, setIsError ] = useState(false);
const [isForbidden, setIsForbidden ] = useState(false);
const [ member, setMember ] = useState({});
const [isLoading, setIsLoading ] = useState(false);
const [isError, setIsError ] = useState(false);
const [isForbidden, setIsForbidden ] = useState(false);
const [ member, setMember ] = useState({});
const fetchMember = useCallback( () => {
setIsLoading(true);
setIsError(false);
fetch(`${API_URL}m/${memberID}`,{
method: 'GET',
}).then ( res => {
if (res.status === 403) {
throw new Error('Access denied!');
}
return res.json()
}
).then (data => {
setMember(data)
setIsLoading(false);
})
.catch (error => {
if (error.message === 'Access denied!') {
setIsForbidden(true);
} else {
console.log(error);
setIsError(true);
}
setIsLoading(false);
})
}, [memberID])
useEffect(() => {
fetchMember();
}, [fetchMember])
const fetchMember = useCallback( () => {
setIsLoading(true);
setIsError(false);
fetch(`${API_URL}m/${memberID}`,{
method: 'GET',
}).then ( res => {
if (res.status === 403) {
throw new Error('Access denied!');
}
return res.json()
}
).then (data => {
setMember(data)
setIsLoading(false);
})
.catch (error => {
if (error.message === 'Access denied!') {
setIsForbidden(true);
} else {
console.log(error);
setIsError(true);
}
setIsLoading(false);
})
}, [memberID])
useEffect(() => {
fetchMember();
}, [fetchMember])
return (
isLoading ? <Loading /> : isError ?
<BS.Alert variant="danger">Error fetching member.</BS.Alert> : isForbidden ? <BS.Alert variant="danger">This member is private.</BS.Alert> : <ProfilePage member={member} list={false}/>
);
return (
isLoading ? <Loading /> : isError ?
<BS.Alert variant="danger">Error fetching member.</BS.Alert> : isForbidden ? <BS.Alert variant="danger">This member is private.</BS.Alert> : <ProfilePage member={member} list={false}/>
);
}
export default MemberProfile;

View File

@ -7,250 +7,250 @@ import Toggle from "react-toggle";
const Settings = ({ forceUpdate }) => {
// this all should be pretty self-explanatory, might add comments later tho
return (
<BS.Card>
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title>
<FaCog className="mr-3" />
Settings
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
<p>
Change how you view and use pk-webs here, changes will be saved after
refreshing. You will have to apply them again in different browsers
and on different devices.
</p>
<hr />
<BS.Row>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("opendyslexic") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("opendyslexic");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("opendyslexic", "true");
forceUpdate();
}}
/>
)}
Use opendyslexic?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("twemoji") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("twemoji");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("twemoji", "true");
forceUpdate();
}}
/>
)}
Use twemoji?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("pagesonly") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("pagesonly");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("pagesonly", "true");
forceUpdate();
}}
/>
)}
Use only member pages?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("fullbg") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("fullbg");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("fullbg", "true");
forceUpdate();
}}
/>
)}
Remove gradient from background color?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("colorbg") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("colorbg");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("colorbg", "false");
forceUpdate();
}}
/>
)}
Hide colored backgrounds altogether?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("expandcards") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("expandcards");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("expandcards", "true");
forceUpdate();
}}
/>
)}
Expand member cards on default?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("hidebanners") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("hidebanners");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("hidebanners", "true");
forceUpdate();
}}
/>
)}
Hide banners at the top?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("bottombanners") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("bottombanners");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("bottombanners", "true");
forceUpdate();
}}
/>
)}
Show banners at the bottom?
</BS.Col>
</BS.Row>
</BS.Card.Body>
</BS.Card>
);
return (
<BS.Card>
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Card.Title>
<FaCog className="mr-3" />
Settings
</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
<p>
Change how you view and use pk-webs here, changes will be saved after
refreshing. You will have to apply them again in different browsers
and on different devices.
</p>
<hr />
<BS.Row>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("opendyslexic") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("opendyslexic");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("opendyslexic", "true");
forceUpdate();
}}
/>
)}
Use opendyslexic?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("twemoji") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("twemoji");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("twemoji", "true");
forceUpdate();
}}
/>
)}
Use twemoji?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("pagesonly") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("pagesonly");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("pagesonly", "true");
forceUpdate();
}}
/>
)}
Use only member pages?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("fullbg") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("fullbg");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("fullbg", "true");
forceUpdate();
}}
/>
)}
Remove gradient from background color?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("colorbg") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("colorbg");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("colorbg", "false");
forceUpdate();
}}
/>
)}
Hide colored backgrounds altogether?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("expandcards") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("expandcards");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("expandcards", "true");
forceUpdate();
}}
/>
)}
Expand member cards on default?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("hidebanners") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("hidebanners");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("hidebanners", "true");
forceUpdate();
}}
/>
)}
Hide banners at the top?
</BS.Col>
<BS.Col
xs={12}
lg={4}
className="mx-1 mb-4 d-flex align-items-center row"
>
{localStorage.getItem("bottombanners") ? (
<Toggle
className="mr-2"
defaultChecked={true}
icons={false}
onChange={() => {
localStorage.removeItem("bottombanners");
forceUpdate();
}}
/>
) : (
<Toggle
className="mr-2"
defaultChecked={false}
icons={false}
onChange={() => {
localStorage.setItem("bottombanners", "true");
forceUpdate();
}}
/>
)}
Show banners at the bottom?
</BS.Col>
</BS.Row>
</BS.Card.Body>
</BS.Card>
);
};
export default Settings;

View File

@ -6,8 +6,8 @@ import './index.css';
import App from './App';
ReactDOM.render(
<Router history={history}>
<App />
</Router>,
document.getElementById('root')
<Router history={history}>
<App />
</Router>,
document.getElementById('root')
);