Add public profiles

This commit is contained in:
Spectralitree 2020-12-14 22:25:35 +01:00
parent b5e7c51e5f
commit f501bdc2be
6 changed files with 375 additions and 20 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ import Dash from './Components/Dash.js'
import history from "./History.js";
import Loading from "./Components/Loading.js";
import Footer from './Components/Footer.js'
import Profile from './Components/Profile.js'
import Public from './Components/Public.js'
import API_URL from "./Constants/constants.js";
@ -105,13 +105,13 @@ export default function App() {
pk-webs
</BS.Navbar.Brand>
<BS.NavDropdown id="menu" className="mr-auto" title="Menu">
{ localStorage.getItem('token') ? <BS.NavDropdown.Item onClick={() => logOut()}>Log out</BS.NavDropdown.Item> : "" }
<BS.NavDropdown.Item onClick={() => history.push('/pk-webs/dash')} >Dash</BS.NavDropdown.Item>
<BS.NavDropdown.Item onClick={() => history.push('/pk-webs/settings')} >Settings</BS.NavDropdown.Item>
<BS.NavDropdown.Item onClick={() => history.push('/pk-webs/profile')}>Public profile</BS.NavDropdown.Item>
{ localStorage.getItem('token') ? <><hr className="my-1"/><BS.NavDropdown.Item onClick={() => logOut()}>Log out</BS.NavDropdown.Item></> : "" }
</BS.NavDropdown>
<BS.Nav className="mr-lg-2 d-flex align-items-center row">
<BS.Nav className="mr-2 d-flex align-items-center row">
<Toggle
defaultChecked={true}
icons={false}
@ -121,7 +121,7 @@ export default function App() {
</BS.Navbar>
<BS.Container>
<Switch>
<Route exact path="/pk-webs/dash" >
<Route path="/pk-webs/dash" >
{ !localStorage.getItem('token') || isInvalid ? <Redirect to="/pk-webs"/> : <Dash />
}
</Route>
@ -138,7 +138,7 @@ export default function App() {
<BS.Button type="primary" onClick={() => history.push('/pk-webs/dash')}>Continue to dash</BS.Button></> :
<BS.Form onSubmit={handleSubmit(onSubmit)}>
<BS.Form.Row>
<BS.Col xs={12} lg={10}>
<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>
@ -155,10 +155,10 @@ export default function App() {
</BS.Card>
}
</Route>
<Route exact path="/pk-webs/profile">
<Profile />
<Route path="/pk-webs/profile">
<Public />
</Route>
<Route exact path="/pk-webs/settings">
<Route path="/pk-webs/settings">
<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>
@ -183,6 +183,22 @@ export default function App() {
forceUpdate()}} /> }
Use opendyslexic?
</BS.Col>
<BS.Col xs={12} lg={4} className="mx-lg-2 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.Row>
</BS.Card.Body>
</BS.Card>

View File

@ -1,15 +1,90 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import * as BS from 'react-bootstrap';
import { FaStar } from "react-icons/fa";
import Popup from 'reactjs-popup';
import Twemoji from 'react-twemoji';
import { FaAddressCard } from "react-icons/fa";
import defaultAvatar from '../default_discord_avatar.png'
import Loading from "./Loading.js";
import API_URL from "../Constants/constants.js";
import ProfileList from "./ProfileList.js";
export default function Profile () {
return (
<BS.Card>
<BS.Card.Header>
<BS.Card.Title><FaStar className="mr-3" />Profile</BS.Card.Title>
const { sysID } = useParams();
const [ system, setSystem ] = useState('');
const [ name, setName ] = useState('');
const [ tag, setTag ] = useState("");
const [ timezone, setTimezone ] = useState("");
const [ desc, setDesc ] = useState("");
const [ isLoading, setIsLoading ] = useState(true);
const [ isError, setIsError ] = useState(false);
useEffect (() => {
fetch(`${API_URL}s/${sysID}`,{
method: 'GET'
}).then ( res => res.json()
).then (data => {
setSystem(data);
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsError(true);
setIsLoading(false);
})
}, [sysID])
useEffect(() => {
const { toHTML } = require('../Functions/discord-parser.js');
if (system.name) {
setName(system.name);
} else setName('');
if (system.tag) {
setTag(system.tag);
} else setTag('');
if (system.tz) {
setTimezone(system.tz);
} else setTimezone('');
if (system.description) {
setDesc(toHTML(system.description));
} else setDesc("(no description)");
}, [system.description, system.tag, system.avatar_url, system.tz, system.name]);
return (<>{ isLoading ? <Loading /> : isError ? <BS.Alert variant="danger">Something went wrong, either the system doesn't exist, or there was an error fetching data.</BS.Alert> :
<><BS.Alert variant="primary" >You are currently <b>viewing</b> a system.</BS.Alert>
<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} ({system.id})</BS.Card.Title>
{ system.avatar_url ? <Popup trigger={<BS.Image src={`${system.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()}>
<BS.Image src={`${system.avatar_url}`} style={{width: 500, height: 'auto'}} thumbnail />
</div>
)}
</Popup> :
<BS.Image src={defaultAvatar} style={{width: 50, height: 50}} className="float-right" roundedCircle />}
</BS.Card.Header>
<BS.Card.Body>
WIP. Public profiles coming soon here!
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>ID:</b> {system.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>
</BS.Row>
<p><b>Description:</b></p>
{ localStorage.getItem('twemoji') ? <Twemoji options={{ className: 'twemoji' }}><p dangerouslySetInnerHTML={{__html: desc}}></p></Twemoji> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
</BS.Card.Body>
</BS.Card>
<ProfileList sysID={sysID} /> </> }
</>
)
}

View File

@ -0,0 +1,100 @@
import React, { useEffect, useState } from 'react';
import * as BS from 'react-bootstrap'
import moment from 'moment';
import Popup from 'reactjs-popup';
import 'reactjs-popup/dist/index.css';
import autosize from 'autosize';
import LazyLoad from 'react-lazyload';
import Twemoji from 'react-twemoji';
import defaultAvatar from '../default_discord_avatar.png'
import { FaUser } from "react-icons/fa";
export default function MemberCard(props) {
const member = props.member;
const [ displayName, setDisplayName ] = useState("");
const [ birthday, setBirthday ] = useState("");
const [ pronouns, setPronouns ] = useState("");
const [ color, setColor ] = useState("");
const [ desc, setDesc ] = useState("");
const proxyTags = member.proxy_tags;
const [ proxyView, setProxyView ] = useState(false);
useEffect(() => {
autosize(document.querySelector('textarea'));
})
useEffect(() => {
const { toHTML } = require('../Functions/discord-parser.js');
if (member.display_name) {
setDisplayName(member.display_name)
} else setDisplayName('')
if (member.birthday) {
if (member.birthday.startsWith('0004-')) {
var bday = member.birthday.replace('0004-','');
var bdaymoment = moment(bday, 'MM-DD').format('MMM D');
setBirthday(bdaymoment);
} else {
var birthdaymoment = moment(member.birthday, 'YYYY-MM-DD').format('MMM D, YYYY');
setBirthday(birthdaymoment);
}
} else { setBirthday('');
}
if (member.pronouns) {
setPronouns(member.pronouns)
} else setPronouns('')
if (member.color) {
setColor(member.color);
} else setColor('');
if (member.description) {
setDesc(toHTML(member.description));
} else setDesc("(no description)");
}, [member.description, member.color, member.birthday, member.display_name, member.pronouns, member.avatar_url, member.proxy_tags]);
return (
<LazyLoad offset={100}>
<BS.Card.Header className="d-flex align-items-center justify-content-between">
<BS.Accordion.Toggle as={BS.Button} variant="link" eventKey={member.id} className="float-left"><FaUser className="mr-4 float-left" /> <b>{member.name}</b> ({member.id})</BS.Accordion.Toggle>
{ 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()}>
<BS.Image src={`${member.avatar_url}`} style={{width: 500, height: 'auto'}} thumbnail />
</div>
)}
</Popup> :
<BS.Image src={defaultAvatar} style={{width: 50, height: 50}} tabIndex="0" className="float-right" roundedCircle />}
</BS.Card.Header>
<BS.Accordion.Collapse eventKey={member.id}>
<BS.Card.Body style={{borderLeft: `5px solid #${color}` }}>
<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>{displayName}</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> {pronouns}</BS.Col> : "" }
{ member.color ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>Color:</b> {color}</BS.Col> : "" }
{ 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> }
</BS.Row>
{ 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) => <BS.Col key={proxytag.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>
<hr/></> : "" }
<p><b>Description:</b></p>
{ localStorage.getItem('twemoji') ? <Twemoji options={{ className: 'twemoji' }}><p dangerouslySetInnerHTML={{__html: desc}}></p></Twemoji> : <p dangerouslySetInnerHTML={{__html: desc}}></p>}
</BS.Card.Body>
</BS.Accordion.Collapse>
</LazyLoad>
)
}

View File

@ -0,0 +1,117 @@
import React, { useEffect, useState, useCallback } from 'react';
import * as BS from 'react-bootstrap'
import 'reactjs-popup/dist/index.css';
import ProfileCard from './ProfileCard.js'
import Loading from "./Loading.js";
import API_URL from "../Constants/constants.js";
export default function Memberlist(props) {
const sysID = props.sysID;
const [isLoading, setIsLoading ] = useState(false);
const [isError, setIsError ] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [membersPerPage, setMembersPerPage] = useState(25);
const [members, setMembers ] = useState([]);
const [value, setValue] = useState('');
const fetchMembers = useCallback( () => {
setIsLoading(true);
setIsError(false);
setMembersPerPage(25);
fetch(`${API_URL}s/${sysID}/members`,{
method: 'GET',
}).then ( res => res.json()
).then (data => {
setMembers(data)
setIsLoading(false);
})
.catch (error => {
console.log(error);
setIsError(true);
setIsLoading(false);
})
}, [sysID])
useEffect(() => {
fetchMembers();
}, [fetchMembers])
const indexOfLastMember = currentPage * membersPerPage;
const indexOfFirstMember = indexOfLastMember - membersPerPage;
const currentMembers = members.filter(member => {
if (!value) return true;
if (member.name.toLowerCase().includes(value.toLowerCase())) {
return true;
}
return false;
}).sort((a, b) => a.name.localeCompare(b.name)).slice(indexOfFirstMember, indexOfLastMember);
const active = currentPage;
const pageAmount = Math.ceil(members.length / membersPerPage);
const memberList = currentMembers.map((member) => <BS.Card key={member.id}>
<ProfileCard
member={member}
/>
</BS.Card>
);
return (
<>
{ isLoading ? <Loading /> : isError ?
<BS.Alert variant="danger">Error fetching members. Perhaps the member list has been set to private.</BS.Alert> :
<>
<BS.Row noGutters="true" className="justify-content-md-center">
<BS.Col className="lg-2 mb-3" xs={12} lg={3}>
<BS.Form>
<BS.InputGroup className="mb-3">
<BS.Form.Control disabled placeholder='Page length:'/>
<BS.Form.Control as="select" onChange={e => {
setMembersPerPage(e.target.value);
setCurrentPage(1);
}}>
<option>10</option>
<option selected>25</option>
<option>50</option>
<option>100</option>
</BS.Form.Control>
</BS.InputGroup>
</BS.Form>
</BS.Col>
<BS.Col className="ml-lg-2 mb-3" xs={12} lg={4}>
<BS.Form>
<BS.Form.Control value={value} onChange={e => {setValue(e.target.value); setCurrentPage(1)}} placeholder="Search"/>
</BS.Form>
</BS.Col>
<BS.Col className="ml-lg-2 mb-3" xs={12} lg={1}>
<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 ? "" :<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 ? "" : <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>
<BS.Accordion className="mb-3 mt-3 w-100" defaultActiveKey="0">
{memberList}
</BS.Accordion>
</>
}
</>
)
}

47
src/Components/Public.js Normal file
View File

@ -0,0 +1,47 @@
import React from 'react';
import * as BS from 'react-bootstrap';
import { FaStar } from "react-icons/fa";
import { useForm } from "react-hook-form";
import history from "../History.js";
import { Switch, Route, useRouteMatch } from 'react-router-dom';
import Profile from './Profile.js'
export default function Public () {
const { path, url } = useRouteMatch();
const { register, handleSubmit } = useForm();
const submitID = (data) => {
history.push(`${url}/${data.sysID}`);
}
return (
<Switch>
<Route exact path={path}>
<BS.Card>
<BS.Card.Header>
<BS.Card.Title><FaStar className="mr-3" />Profile</BS.Card.Title>
</BS.Card.Header>
<BS.Card.Body>
<BS.Form onSubmit={handleSubmit(submitID)}>
<BS.Form.Label>
Submit a system ID to view to that system's profile.
</BS.Form.Label>
<BS.Form.Row>
<BS.Col className="mb-1" xs={12} lg={10}>
<BS.Form.Control name="sysID" ref={register} defaultValue="" />
</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>
</Route>
<Route path={`${path}/:sysID`}>
<Profile />
</Route>
</Switch>
)
}