Frontend: Add basic guild invite page

This commit is contained in:
ADAMJR 2021-12-30 00:21:30 +00:00
parent 675fe5b776
commit 090f7e73ba
11 changed files with 106 additions and 162 deletions

View File

@ -53,8 +53,11 @@ router.get('/check-email', async (req, res) => {
router.get('/self', updateUser, validateUser, async (req, res) => res.json(res.locals.user));
router.get('/entities', updateUser, validateUser, async (req, res) => {
const guildIds: string[] = req.params.guildIds;
const user: UserTypes.Self = res.locals.user;
const $in = user.guildIds;
const $in = (guildIds)
? user.guildIds.concat(guildIds)
: user.guildIds;
const [channels, guilds, members, roles, themes, unsecureUsers] = await Promise.all([
Channel.find({ guildId: { $in } }),

View File

@ -83,20 +83,16 @@ const Message: React.FunctionComponent<MessageProps> = ({ message }: MessageProp
<MessageToolbar message={message} />
</div>
{(message.system)
? (
<div className="my-1">
<MessageContent message={message} />
<MessageHeader message={message} />
</div>
) : (
<>
<MessageHeader
author={author}
message={message}
isExtra={isActuallyExtra} />
<MessageContent message={message} />
</>
)}
? (<div className="my-1">
<MessageContent message={message} />
<MessageHeader message={message} />
</div>) : (<>
<MessageHeader
author={author}
message={message}
isExtra={isActuallyExtra} />
<MessageContent message={message} />
</>)}
</div>
<div className="right-side w-12" />
</div>

View File

@ -1,39 +1,73 @@
import { Entity } from '@accord/types';
import { useEffect } from 'react';
import Particles from 'react-particles-js';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { fetchGuild, getGuild } from '../../store/guilds';
import { Link, useHistory, useParams } from 'react-router-dom';
import fetchEntities from '../../store/actions/fetch-entities';
import { getGuild, getGuildMembers } from '../../store/guilds';
import { fetchInvite, getInvite } from '../../store/invites';
import Navbar from '../navigation/navbar';
import { joinGuild } from '../../store/members';
import { getTag, getUser } from '../../store/users';
import SidebarIcon from '../navigation/sidebar/sidebar-icon';
import NormalButton from '../utils/buttons/normal-button';
import PageWrapper from './page-wrapper';
interface InvitePageProps {}
const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
const history = useHistory();
const dispatch = useDispatch();
const { inviteId }: any = useParams();
const invite = useSelector(getInvite(inviteId));
const guild = useSelector(getGuild(invite?.guildId));
const invite: Entity.Invite = useSelector(getInvite(inviteId));
const guild: Entity.Guild | undefined = useSelector(getGuild(invite?.guildId));
const members: Entity.GuildMember[] = useSelector(getGuildMembers(invite?.guildId));
const ownerUser: Entity.User = useSelector(getUser(guild!?.ownerId));
useEffect(() => {
dispatch(fetchInvite(inviteId));
if (invite) dispatch(fetchGuild(invite.guildId));
if (invite) dispatch(fetchEntities([invite.guildId]));
}, []);
if (!invite) return <div>Invalid Invite: Invite not found</div>;
if (!guild) return <div>Invalid Invite: Guild not found</div>;
return (guild) ? (
<PageWrapper
className="z-10 bg-bg-tertiary h-screen relative"
pageTitle="accord.app | Like Discord but Cooler">
<div className="flex items-center absolute justify-center h-screen">
<section className="flex justify-center items-center h-1/2">
<h1>You have been invited to {guild.name}</h1>
return (
<PageWrapper pageTitle={`accord.app | Invite to '${guild.name}'`}>
<div className="flex items-center absolute justify-center h-screen left-[35%]">
<section className="rounded-md shadow bg-bg-primary p-8 w-[478px]">
<h1 className="text-3xl font-bold">You have been invited to {guild.name}!</h1>
<div className="flex mt-5">
<SidebarIcon
name={guild.name}
imageURL={guild.iconURL}
childClasses="bg-bg-tertiary w-24 h-24 pt-6 text-xl"
disableHoverEffect />
<div className="flex justify-around w-full mx-5">
<span>
<strong className="heading">Members</strong>: <code className="muted">{members.length}</code>
</span>
<span>
<strong className="heading">Owner</strong>: <code className="muted">{getTag(ownerUser)}</code>
</span>
</div>
</div>
<div className="flex justify-center gap-5 mx-5 mt-5">
<NormalButton
onClick={() => {
dispatch(joinGuild(inviteId));
history.push(`/channels/${invite.guildId}`);
}}
className="bg-success light">Join :D</NormalButton>
<Link to="/">
<NormalButton className="bg-danger light">Nope :(</NormalButton>
</Link>
</div>
</section>
</div>
<Particles width="100%" height="100%" />
</PageWrapper>
) : null;
);
}
export default InvitePage;

View File

@ -15,7 +15,6 @@ const MemberList: React.FunctionComponent = () => {
const hoistedRoles = useSelector(filterHoistedRoles(guild.id));
const members = useSelector(getGuildMembers(guild.id));
// get users that can view the channel
const users = useSelector(getGuildUsers(guild.id))
.filter(u => {
const member = members.find(m => m.userId === u.id)!;

View File

@ -1,6 +1,6 @@
import { Entity } from '@accord/types';
import { getChannel, getChannelByName } from '../store/channels';
import { getUser, getUserByTag } from '../store/users';
import { getTag, getUser, getUserByTag } from '../store/users';
export class MentionService {
public readonly tags = ['@245538070684827648', '#\d{18}'];
@ -48,7 +48,7 @@ export class MentionService {
},
user: {
onClick: `events.emit('openUserProfile', '${id}')`,
text: `@${this.tag(getUser(id)(this.state))}`,
text: `@${getTag(getUser(id)(this.state))}`,
},
};
@ -58,11 +58,4 @@ export class MentionService {
class="font-extrabold cursor-pointer hover:underline ${mentioned}"
onclick="${tag[type].onClick}">${tag[type].text}</a>`;
}
private tag(user: Entity.User) {
const tag = (user.discriminator || 0)
.toString()
.padStart(4, '0');
return `${user.username ?? 'Unknown'}#${tag}`;
}
}

View File

@ -9,11 +9,11 @@ import { actions as themes } from '../themes';
import { actions as userActions } from '../users';
import { getHeaders } from '../utils/rest-headers';
export default () => (dispatch) => {
export default (guildIds?: string[]) => (dispatch) => {
dispatch(api.restCallBegan({
onSuccess: [],
headers: getHeaders(),
url: `/users/entities`,
url: `/users/entities?guild_ids=${guildIds}`,
callback: (data: REST.From.Get['/users/entities']) => {
dispatch(channelActions.fetched(data.channels));
dispatch(guildActions.fetched(data.guilds));

View File

@ -29,17 +29,17 @@ const slice = createSlice({
export const actions = slice.actions;
export default slice.reducer;
export const fetchGuild = (id: string) => (dispatch, getStore: () => Store.AppState) => {
const guilds = getStore().entities.guilds;
const isCached = guilds.some(g => g.id === id);
if (isCached) return;
// export const fetchGuild = (id: string) => (dispatch, getStore: () => Store.AppState) => {
// const guilds = getStore().entities.guilds;
// const isCached = guilds.some(g => g.id === id);
// if (isCached) return;
dispatch(api.restCallBegan({
url: `/guilds/${id}`,
headers: getHeaders(),
callback: (guild: Entity.Guild) => dispatch(actions.fetched([guild])),
}));
}
// dispatch(api.restCallBegan({
// url: `/guilds/${id}`,
// headers: getHeaders(),
// callback: (guild: Entity.Guild) => dispatch(actions.fetched([guild])),
// }));
// }
export const createGuild = (name: string) => (dispatch) => {
dispatch(api.wsCallBegan({

View File

@ -87,4 +87,11 @@ export const getUserByTag = (tag: string) => createSelector(
const [username, discrim] = tag.split('#');
return users.find(u => u.username === username && u.discriminator === +discrim);
}
);
);
export const getTag = ({ discriminator, username }: Entity.User) => {
const tag = (discriminator || 0)
.toString()
.padStart(4, '0');
return `${username ?? 'Unknown'}#${tag}`;
}

View File

@ -64,113 +64,3 @@ nav a.active {
background-color: var(--bg-modifier-selected);
color: var(--heading);
}
/* Util */
.dark {
color: var(--dark);
}
.font {
color: var(--font);
}
.success {
color: var(--success);
}
.normal {
color: var(--normal);
}
.muted {
color: var(--muted);
}
.danger {
color: var(--danger);
}
.warning {
color: var(--warning);
}
.heading {
color: var(--heading);
}
.primary {
color: var(--primary);
}
.secondary {
color: var(--secondary);
}
.tertiary {
color: var(--tertiary);
}
.bg-primary {
background-color: var(--primary);
}
.bg-secondary {
background-color: var(--secondary);
}
.bg-tertiary {
background-color: var(--tertiary);
}
.bg-warning {
background-color: var(--warning);
}
.bg-danger {
background-color: var(--danger);
}
.bg-success {
background-color: var(--success);
}
.bg-bg-primary {
background-color: var(--bg-primary);
}
.bg-bg-secondary {
background-color: var(--bg-secondary);
}
.bg-bg-secondary-alt {
background-color: var(--bg-secondary-alt);
}
.bg-bg-tertiary {
background-color: var(--bg-tertiary);
}
/* \/ should be replaced with tag name selector */
.bg-bg-modifier-accent {
background-color: var(--bg-modifier-accent);
color: var(--heading);
}
.bg-bg-floating {
background-color: var(--bg-floating);
}
.shadow-elevation {
box-shadow: var(--elevation);
}
.font-primary {
font-family: var(--font-primary);
}
/* borders */
.border-primary {
border-color: var(--primary);
}
.border-secondary {
border-color: var(--secondary);
}
.border-tertiary {
border-color: var(--tertiary);
}
.border-danger {
border-color: var(--danger);
}
.border-success {
border-color: var(--success);
}
.border-bg-primary {
border-color: var(--bg-primary);
}
.border-bg-secondary {
border-color: var(--bg-secondary);
}
.border-bg-secondary-alt {
border-color: var(--bg-secondary-alt);
}
.border-bg-tertiary {
border-color: var(--bg-tertiary);
}

View File

@ -693,6 +693,9 @@ video {
.m-1 {
margin: 0.25rem;
}
.m-5 {
margin: 1.25rem;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@ -713,6 +716,10 @@ video {
margin-left: 0.625rem;
margin-right: 0.625rem;
}
.mx-5 {
margin-left: 1.25rem;
margin-right: 1.25rem;
}
.mb-5 {
margin-bottom: 1.25rem;
}
@ -881,6 +888,9 @@ video {
.h-1\/2 {
height: 50%;
}
.h-24 {
height: 6rem;
}
.max-h-96 {
max-height: 24rem;
}
@ -965,6 +975,9 @@ video {
.w-\[478px\] {
width: 478px;
}
.w-24 {
width: 6rem;
}
.max-w-full {
max-width: 100%;
}
@ -1039,12 +1052,18 @@ video {
.justify-between {
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
}
.gap-4 {
gap: 1rem;
}
.gap-20 {
gap: 5rem;
}
.gap-5 {
gap: 1.25rem;
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));

View File

@ -33,6 +33,9 @@ nav a.active {
}
/* Util */
.light {
color: var(--light);
}
.dark {
color: var(--dark);
}