Frontend: Add basic guild invite page
This commit is contained in:
parent
675fe5b776
commit
090f7e73ba
@ -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 } }),
|
||||
|
@ -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>
|
||||
|
@ -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;
|
@ -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)!;
|
||||
|
@ -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}`;
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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({
|
||||
|
@ -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}`;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -33,6 +33,9 @@ nav a.active {
|
||||
}
|
||||
|
||||
/* Util */
|
||||
.light {
|
||||
color: var(--light);
|
||||
}
|
||||
.dark {
|
||||
color: var(--dark);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user