Add public profiles
This commit is contained in:
parent
b5e7c51e5f
commit
f501bdc2be
File diff suppressed because one or more lines are too long
32
src/App.js
32
src/App.js
@ -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>
|
||||
|
@ -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>
|
||||
</BS.Card.Header>
|
||||
<BS.Card.Body>
|
||||
WIP. Public profiles coming soon here!
|
||||
</BS.Card.Body>
|
||||
</BS.Card>
|
||||
)
|
||||
|
||||
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>
|
||||
<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} /> </> }
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
100
src/Components/ProfileCard.js
Normal file
100
src/Components/ProfileCard.js
Normal 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>
|
||||
|
||||
)
|
||||
}
|
117
src/Components/ProfileList.js
Normal file
117
src/Components/ProfileList.js
Normal 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
47
src/Components/Public.js
Normal 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>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user