mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-25 07:02:10 -05:00
Add open route / invite route.
Modularise server header.
This commit is contained in:
parent
602cca1047
commit
c597fc81f8
6 changed files with 279 additions and 23 deletions
39
src/components/common/ServerHeader.tsx
Normal file
39
src/components/common/ServerHeader.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import Header from "../ui/Header";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import IconButton from "../ui/IconButton";
|
||||||
|
import { Settings } from "@styled-icons/feather";
|
||||||
|
import { Server } from "revolt.js/dist/api/objects";
|
||||||
|
import { ServerPermission } from "revolt.js/dist/api/permissions";
|
||||||
|
import { HookContext, useServerPermission } from "../../context/revoltjs/hooks";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
server: Server,
|
||||||
|
ctx: HookContext
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServerName = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function ServerHeader({ server, ctx }: Props) {
|
||||||
|
const permissions = useServerPermission(server._id, ctx);
|
||||||
|
const bannerURL = ctx.client.servers.getBannerURL(server._id, { width: 480 }, true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header placement="secondary"
|
||||||
|
background={typeof bannerURL !== 'undefined'}
|
||||||
|
style={{ background: bannerURL ? `linear-gradient(to bottom, transparent 50%, #000e), url('${bannerURL}')` : undefined }}>
|
||||||
|
<ServerName>
|
||||||
|
{ server.name }
|
||||||
|
</ServerName>
|
||||||
|
{ (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
|
||||||
|
<Link to={`/server/${server._id}/settings`}>
|
||||||
|
<IconButton>
|
||||||
|
<Settings size={24} />
|
||||||
|
</IconButton>
|
||||||
|
</Link>
|
||||||
|
</div> }
|
||||||
|
</Header>
|
||||||
|
)
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import { connectState } from "../../../redux/connector";
|
||||||
import PaintCounter from "../../../lib/PaintCounter";
|
import PaintCounter from "../../../lib/PaintCounter";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { attachContextMenu } from 'preact-context-menu';
|
import { attachContextMenu } from 'preact-context-menu';
|
||||||
|
import ServerHeader from "../../common/ServerHeader";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
unreads: Unreads;
|
unreads: Unreads;
|
||||||
|
@ -45,7 +46,6 @@ function ServerSidebar(props: Props & WithDispatcher) {
|
||||||
const server = useServer(server_id, ctx);
|
const server = useServer(server_id, ctx);
|
||||||
if (!server) return <Redirect to="/" />;
|
if (!server) return <Redirect to="/" />;
|
||||||
|
|
||||||
const permissions = useServerPermission(server._id, ctx);
|
|
||||||
const channels = (useChannels(server.channels, ctx)
|
const channels = (useChannels(server.channels, ctx)
|
||||||
.filter(entry => typeof entry !== 'undefined') as Readonly<Channels.TextChannel>[])
|
.filter(entry => typeof entry !== 'undefined') as Readonly<Channels.TextChannel>[])
|
||||||
.map(x => mapChannelWithUnread(x, props.unreads));
|
.map(x => mapChannelWithUnread(x, props.unreads));
|
||||||
|
@ -55,16 +55,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerBase>
|
<ServerBase>
|
||||||
<Header placement="secondary" background style={{ background: `url('${ctx.client.servers.getBannerURL(server._id, { width: 480 }, true)}')` }}>
|
<ServerHeader server={server} ctx={ctx} />
|
||||||
<div>
|
|
||||||
{ server.name }
|
|
||||||
</div>
|
|
||||||
{ (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
|
|
||||||
{/*<IconButton to={`/server/${server._id}/settings`}>*/}
|
|
||||||
<Settings size={24} />
|
|
||||||
{/*</IconButton>*/}
|
|
||||||
</div> }
|
|
||||||
</Header>
|
|
||||||
<ConnectionStatus />
|
<ConnectionStatus />
|
||||||
<ServerList onContextMenu={attachContextMenu('Menu', { server_list: server._id })}>
|
<ServerList onContextMenu={attachContextMenu('Menu', { server_list: server._id })}>
|
||||||
{channels.map(entry => {
|
{channels.map(entry => {
|
||||||
|
|
72
src/pages/Open.tsx
Normal file
72
src/pages/Open.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
import Header from "../components/ui/Header";
|
||||||
|
import { useContext, useEffect } from "preact/hooks";
|
||||||
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
|
import { useIntermediate } from "../context/intermediate/Intermediate";
|
||||||
|
import { useChannels, useForceUpdate, useUser } from "../context/revoltjs/hooks";
|
||||||
|
import { AppContext, ClientStatus, StatusContext } from "../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
|
export default function Open() {
|
||||||
|
const history = useHistory();
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const status = useContext(StatusContext);
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const { openScreen } = useIntermediate();
|
||||||
|
|
||||||
|
if (status !== ClientStatus.ONLINE) {
|
||||||
|
return (
|
||||||
|
<Header placement="primary">
|
||||||
|
<Text id="general.loading" />
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = useForceUpdate();
|
||||||
|
const channels = useChannels(undefined, ctx);
|
||||||
|
const user = useUser(id, ctx);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id === "saved") {
|
||||||
|
for (const channel of channels) {
|
||||||
|
if (channel?.channel_type === "SavedMessages") {
|
||||||
|
history.push(`/channel/${channel._id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.users
|
||||||
|
.openDM(client.user?._id as string)
|
||||||
|
.then(channel => history.push(`/channel/${channel?._id}`))
|
||||||
|
.catch(error => openScreen({ id: "error", error }));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const channel: string | undefined = channels.find(
|
||||||
|
channel =>
|
||||||
|
channel?.channel_type === "DirectMessage" &&
|
||||||
|
channel.recipients.includes(id)
|
||||||
|
)?._id;
|
||||||
|
|
||||||
|
if (channel) {
|
||||||
|
history.push(`/channel/${channel}`);
|
||||||
|
} else {
|
||||||
|
client.users
|
||||||
|
.openDM(id)
|
||||||
|
.then(channel => history.push(`/channel/${channel?._id}`))
|
||||||
|
.catch(error => openScreen({ id: "error", error }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push("/");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header placement="primary">
|
||||||
|
<Text id="general.loading" />
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
}
|
|
@ -13,7 +13,9 @@ import LeftSidebar from "../components/navigation/LeftSidebar";
|
||||||
import RightSidebar from "../components/navigation/RightSidebar";
|
import RightSidebar from "../components/navigation/RightSidebar";
|
||||||
import BottomNavigation from "../components/navigation/BottomNavigation";
|
import BottomNavigation from "../components/navigation/BottomNavigation";
|
||||||
|
|
||||||
|
import Open from "./Open";
|
||||||
import Home from './home/Home';
|
import Home from './home/Home';
|
||||||
|
import Invite from "./invite/Invite";
|
||||||
import Friends from "./friends/Friends";
|
import Friends from "./friends/Friends";
|
||||||
import Channel from "./channels/Channel";
|
import Channel from "./channels/Channel";
|
||||||
import Settings from './settings/Settings';
|
import Settings from './settings/Settings';
|
||||||
|
@ -64,6 +66,8 @@ export default function App() {
|
||||||
|
|
||||||
<Route path="/dev" component={Developer} />
|
<Route path="/dev" component={Developer} />
|
||||||
<Route path="/friends" component={Friends} />
|
<Route path="/friends" component={Friends} />
|
||||||
|
<Route path="/open/:id" component={Open} />
|
||||||
|
<Route path="/invite/:code" component={Invite} />
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -75,15 +79,3 @@ export default function App() {
|
||||||
</OverlappingPanels>
|
</OverlappingPanels>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
|
|
||||||
<Route path="/open/:id">
|
|
||||||
<Open />
|
|
||||||
</Route>
|
|
||||||
{/*<Route path="/invite/:code">
|
|
||||||
<OpenInvite />
|
|
||||||
</Route>
|
|
||||||
*/
|
|
||||||
|
|
75
src/pages/invite/Invite.module.scss
Normal file
75
src/pages/invite/Invite.module.scss
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
.invite {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
user-select: none;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background-size: cover;
|
||||||
|
justify-content: center;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
* {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 64px;
|
||||||
|
z-index: 100;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
top: -32px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.leave {
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
align-self: center;
|
||||||
|
padding: 32px 16px 16px 16px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
gap: 8px;
|
||||||
|
display: flex;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 400;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
87
src/pages/invite/Invite.tsx
Normal file
87
src/pages/invite/Invite.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import styles from './Invite.module.scss';
|
||||||
|
import Button from '../../components/ui/Button';
|
||||||
|
import { ArrowLeft } from "@styled-icons/feather";
|
||||||
|
import Overline from '../../components/ui/Overline';
|
||||||
|
import { Invites } from "revolt.js/dist/api/objects";
|
||||||
|
import Preloader from '../../components/ui/Preloader';
|
||||||
|
import { takeError } from "../../context/revoltjs/util";
|
||||||
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
|
import ServerIcon from '../../components/common/ServerIcon';
|
||||||
|
import UserIcon from '../../components/common/user/UserIcon';
|
||||||
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
import RequiresOnline from '../../context/revoltjs/RequiresOnline';
|
||||||
|
import { AppContext, ClientStatus, StatusContext } from "../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
|
export default function Invite() {
|
||||||
|
const history = useHistory();
|
||||||
|
const client = useContext(AppContext);
|
||||||
|
const status = useContext(StatusContext);
|
||||||
|
const { code } = useParams<{ code: string }>();
|
||||||
|
const [ processing, setProcessing ] = useState(false);
|
||||||
|
const [ error, setError ] = useState<string | undefined>(undefined);
|
||||||
|
const [ invite, setInvite ] = useState<Invites.RetrievedInvite | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof invite === 'undefined' && (status === ClientStatus.ONLINE || status === ClientStatus.READY)) {
|
||||||
|
client.fetchInvite(code)
|
||||||
|
.then(data => setInvite(data))
|
||||||
|
.catch(err => setError(takeError(err)))
|
||||||
|
}
|
||||||
|
}, [ status ]);
|
||||||
|
|
||||||
|
if (typeof invite === 'undefined') {
|
||||||
|
return (
|
||||||
|
<div className={styles.preloader}>
|
||||||
|
<RequiresOnline>
|
||||||
|
{ error ? <Overline type="error" error={error} />
|
||||||
|
: <Preloader /> }
|
||||||
|
</RequiresOnline>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.invite} style={{ backgroundImage: invite.server_banner ? `url('${client.generateFileURL(invite.server_banner)}')` : undefined }}>
|
||||||
|
<div className={styles.leave}>
|
||||||
|
<ArrowLeft size={32} onClick={() => history.push('/')} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ !processing &&
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<ServerIcon attachment={invite.server_icon} server_name={invite.server_name} size={64} />
|
||||||
|
</div> }
|
||||||
|
|
||||||
|
<div className={styles.details}>
|
||||||
|
{ processing ? <Preloader /> :
|
||||||
|
<>
|
||||||
|
<h1>{ invite.server_name }</h1>
|
||||||
|
<h2>#{invite.channel_name}</h2>
|
||||||
|
<h3>Invited by <UserIcon size={24} attachment={invite.user_avatar} /> { invite.user_name }</h3>
|
||||||
|
<Overline type="error" error={error} />
|
||||||
|
<Button contrast
|
||||||
|
onClick={
|
||||||
|
async () => {
|
||||||
|
if (status === ClientStatus.READY) {
|
||||||
|
return history.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setProcessing(true);
|
||||||
|
|
||||||
|
let result = await client.joinInvite(code);
|
||||||
|
if (result.type === 'Server') {
|
||||||
|
history.push(`/server/${result.server._id}/channel/${result.channel._id}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(takeError(err));
|
||||||
|
setProcessing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>{ status === ClientStatus.READY ? 'Login to REVOLT' : 'Accept Invite' }</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue