mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
implement kick/ban logic
This commit is contained in:
parent
c048563b53
commit
9506da25c8
@ -5,14 +5,15 @@ import styled, { css } from "styled-components";
|
||||
export interface Props {
|
||||
readonly compact?: boolean | "icon";
|
||||
palette?: "primary" | "secondary" | "success" | "warning" | "danger" | "accent" | "link";
|
||||
size?: "small" | "medium" | "large";
|
||||
readonly disabled?: boolean;
|
||||
}
|
||||
|
||||
export default styled.button<Props>`
|
||||
color: var(--text);
|
||||
padding: 8px 16px;
|
||||
padding: 2px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
outline: none;
|
||||
border: none;
|
||||
@ -20,6 +21,50 @@ export default styled.button<Props>`
|
||||
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
|
||||
opacity: ${(props) => (props.disabled ? 0.5 : 1)}
|
||||
font-weight: var(--font-weight-bold);
|
||||
height: ${(props) => {
|
||||
switch (props.size) {
|
||||
default:
|
||||
case "small":
|
||||
return "32px;";
|
||||
case "medium":
|
||||
return "40px";
|
||||
case "large":
|
||||
return "45px";
|
||||
}
|
||||
}};
|
||||
min-height: ${(props) => {
|
||||
switch (props.size) {
|
||||
default:
|
||||
case "small":
|
||||
return "32px;";
|
||||
case "medium":
|
||||
return "40px";
|
||||
case "large":
|
||||
return "45px";
|
||||
}
|
||||
}};
|
||||
width: ${(props) => {
|
||||
switch (props.size) {
|
||||
default:
|
||||
case "small":
|
||||
return "96px";
|
||||
case "medium":
|
||||
return "96px";
|
||||
case "large":
|
||||
return "130px";
|
||||
}
|
||||
}};
|
||||
min-width: ${(props) => {
|
||||
switch (props.size) {
|
||||
default:
|
||||
case "small":
|
||||
return "96px";
|
||||
case "medium":
|
||||
return "96px";
|
||||
case "large":
|
||||
return "130px";
|
||||
}
|
||||
}};
|
||||
|
||||
${(props) => {
|
||||
if (!props.palette) props.palette = "primary";
|
||||
|
@ -46,21 +46,25 @@ function UserContextMenu({ user, member }: MenuProps) {
|
||||
<ContextMenuButton disabled>Mention</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Message</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton disabled>Change Nickname</ContextMenuButton>
|
||||
{member && <ContextMenuButton disabled>Change Nickname</ContextMenuButton>}
|
||||
<ContextMenuButton disabled>Add Friend</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Block</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton destructive onClick={kick}>
|
||||
Kick {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuButton destructive onClick={ban}>
|
||||
Ban {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton disabled icon="mdiChevronRight">
|
||||
Roles
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
{member && (
|
||||
<>
|
||||
<ContextMenuButton destructive onClick={kick}>
|
||||
Kick {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuButton destructive onClick={ban}>
|
||||
Ban {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton disabled icon="mdiChevronRight">
|
||||
Roles
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ContextMenuButton
|
||||
icon="mdiIdentifier"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import styled from "styled-components";
|
||||
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { MessageLike } from "../../stores/objects/Message";
|
||||
import Floating from "../floating/Floating";
|
||||
@ -23,12 +24,18 @@ interface Props {
|
||||
|
||||
function MessageAuthor({ message }: Props) {
|
||||
const app = useAppStore();
|
||||
const contextMenu = useContext(ContextMenuContext);
|
||||
const [color, setColor] = React.useState<string | undefined>(undefined);
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const onContextMenu = async (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
const member = await app.guilds.get(message.guild_id!)?.members.fetch(message.author.id);
|
||||
contextMenu.onContextMenu(e, { user: message.author, member });
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if ("guild_id" in message && message.guild_id) {
|
||||
const guild = app.guilds.get(message.guild_id);
|
||||
const guild = app.guilds.get(message.guild_id!);
|
||||
if (!guild) return;
|
||||
const member = guild.members.get(message.author.id);
|
||||
if (!member) return;
|
||||
@ -46,10 +53,11 @@ function MessageAuthor({ message }: Props) {
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<Container
|
||||
ref={ref}
|
||||
style={{
|
||||
color,
|
||||
}}
|
||||
ref={contextMenu.setReferenceElement}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
{message.author.username}
|
||||
</Container>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { useForm } from "react-hook-form";
|
||||
import styled from "styled-components";
|
||||
import * as yup from "yup";
|
||||
import { ModalProps, modalController } from "../../controllers/modals";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
const DescriptionText = styled.p`
|
||||
@ -12,25 +14,57 @@ const DescriptionText = styled.p`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const TextArea = styled.textarea`
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--background-secondary-alt);
|
||||
border: none;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
resize: none;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
const schema = yup
|
||||
.object({
|
||||
reason: yup.string(),
|
||||
reason: yup.string().max(512, "Reason must be less than 512 characters"),
|
||||
})
|
||||
.required();
|
||||
|
||||
export function BanMemberModal({ target, ...props }: ModalProps<"ban_member">) {
|
||||
export function BanMemberModal({ target, type, ...props }: ModalProps<"ban_member">) {
|
||||
const app = useAppStore();
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, disabled, isLoading, isSubmitting },
|
||||
formState: { disabled, isLoading, isSubmitting },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const isDisabled = disabled || isLoading || isSubmitting;
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.rest
|
||||
.put(
|
||||
Routes.guildBan(target.guild.id, target.user!.id),
|
||||
undefined,
|
||||
undefined,
|
||||
data.reason
|
||||
? {
|
||||
"X-Audit-Log-Reason": data.reason,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.then(() => {
|
||||
modalController.pop("close");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
@ -43,17 +77,19 @@ export function BanMemberModal({ target, ...props }: ModalProps<"ban_member">) {
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
onClick: () => console.log("kick"),
|
||||
children: <span>Kick</span>,
|
||||
onClick: onSubmit,
|
||||
children: <span>Ban</span>,
|
||||
palette: "danger",
|
||||
confirmation: true,
|
||||
disabled: isDisabled,
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
onClick: () => modalController.pop("close"),
|
||||
children: <span>Cancel</span>,
|
||||
palette: "link",
|
||||
disabled: isDisabled,
|
||||
size: "small",
|
||||
},
|
||||
]}
|
||||
>
|
||||
@ -63,10 +99,12 @@ export function BanMemberModal({ target, ...props }: ModalProps<"ban_member">) {
|
||||
alt="Thanos Snap GIF"
|
||||
height={300}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
marginBottom: 20,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<span>reason form</span>
|
||||
|
||||
<TextArea {...register("reason")} id="reason" name="reason" placeholder="Reason" maxLength={512} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { useForm } from "react-hook-form";
|
||||
import styled from "styled-components";
|
||||
import * as yup from "yup";
|
||||
import { ModalProps, modalController } from "../../controllers/modals";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
const DescriptionText = styled.p`
|
||||
@ -12,25 +14,57 @@ const DescriptionText = styled.p`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const TextArea = styled.textarea`
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--background-secondary-alt);
|
||||
border: none;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
resize: none;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
const schema = yup
|
||||
.object({
|
||||
reason: yup.string(),
|
||||
reason: yup.string().max(512, "Reason must be less than 512 characters"),
|
||||
})
|
||||
.required();
|
||||
|
||||
export function KickMemberModal({ target, ...props }: ModalProps<"kick_member">) {
|
||||
const app = useAppStore();
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, disabled, isLoading, isSubmitting },
|
||||
formState: { disabled, isLoading, isSubmitting },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const isDisabled = disabled || isLoading || isSubmitting;
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.rest
|
||||
.delete(
|
||||
Routes.guildMember(target.guild.id, target.user!.id),
|
||||
undefined,
|
||||
data.reason
|
||||
? {
|
||||
"X-Audit-Log-Reason": data.reason,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.then(() => {
|
||||
modalController.pop("close");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
@ -43,21 +77,23 @@ export function KickMemberModal({ target, ...props }: ModalProps<"kick_member">)
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
onClick: () => console.log("kick"),
|
||||
onClick: onSubmit,
|
||||
children: <span>Kick</span>,
|
||||
palette: "danger",
|
||||
confirmation: true,
|
||||
disabled: isDisabled,
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
onClick: () => modalController.pop("close"),
|
||||
children: <span>Cancel</span>,
|
||||
palette: "link",
|
||||
disabled: isDisabled,
|
||||
size: "small",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<span>reason form</span>
|
||||
<TextArea {...register("reason")} id="reason" name="reason" placeholder="Reason" maxLength={512} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export const ModalCloseWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export function Modal(props: ModalProps) {
|
||||
export function Modal({ title, description, ...props }: ModalProps) {
|
||||
const [closing, setClosing] = useState(false);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
@ -212,17 +212,13 @@ export function Modal(props: ModalProps) {
|
||||
</ModalCloseWrapper>
|
||||
)}
|
||||
</div>
|
||||
{(props.title || props.description) && (
|
||||
{(title || description) && (
|
||||
<ModalHeader>
|
||||
{props.title && typeof props.title === "string" ? (
|
||||
<ModalHeaderText>{props.title}</ModalHeaderText>
|
||||
{title && typeof title === "string" ? <ModalHeaderText>{title}</ModalHeaderText> : title}
|
||||
{description && typeof description === "string" ? (
|
||||
<ModalSubHeaderText>{description}</ModalSubHeaderText>
|
||||
) : (
|
||||
props.title
|
||||
)}
|
||||
{props.description && typeof props.description === "string" ? (
|
||||
<ModalSubHeaderText>{props.description}</ModalSubHeaderText>
|
||||
) : (
|
||||
props.description
|
||||
description
|
||||
)}
|
||||
</ModalHeader>
|
||||
)}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Snowflake } from "@spacebarchat/spacebar-api-types/globals";
|
||||
import type { APIGuildMember } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { action, computed, makeObservable, observable, ObservableMap } from "mobx";
|
||||
import { type APIGuildMember } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { ObservableMap, action, computed, makeObservable, observable } from "mobx";
|
||||
import { APIUserProfile } from "../utils/interfaces/api";
|
||||
import AppStore from "./AppStore";
|
||||
import Guild from "./objects/Guild";
|
||||
import GuildMember from "./objects/GuildMember";
|
||||
@ -71,4 +72,16 @@ export default class GuildMemberStore {
|
||||
if (!meId) return null;
|
||||
return this.members.get(meId);
|
||||
}
|
||||
|
||||
@action
|
||||
async fetch(id: Snowflake): Promise<GuildMember | undefined> {
|
||||
if (this.has(id)) return this.get(id);
|
||||
const profile = await this.app.rest.get<APIUserProfile>(`/users/${id}/profile`, {
|
||||
guild_id: this.guild.id,
|
||||
});
|
||||
if (!profile.guild_member) return undefined;
|
||||
profile.guild_member.user = profile.user;
|
||||
|
||||
return this.add(profile.guild_member);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { APIGuildMember, PublicUser } from "@spacebarchat/spacebar-api-types/v9";
|
||||
|
||||
export interface IAPILoginResponseMFARequired {
|
||||
token: null;
|
||||
mfa: true;
|
||||
@ -119,3 +121,29 @@ export interface APIError {
|
||||
}
|
||||
|
||||
// export type RESTAPIPostInviteResponse = {} | IAPIError;
|
||||
|
||||
export type UserProfile = Pick<PublicUser, "bio" | "accent_color" | "banner" | "pronouns" | "theme_colors">;
|
||||
|
||||
export type MutualGuild = {
|
||||
id: string;
|
||||
nick?: string;
|
||||
};
|
||||
|
||||
export type PublicMemberProfile = Pick<APIGuildMember, "banner" | "bio" | "pronouns" | "theme_colors"> & {
|
||||
accent_color: unknown; // TODO:
|
||||
emoji: unknown; // TODO:
|
||||
guild_id: string;
|
||||
};
|
||||
|
||||
export interface APIUserProfile {
|
||||
user: PublicUser;
|
||||
connected_accounts: unknown[]; // TODO: type
|
||||
premium_guild_since?: Date;
|
||||
premium_since?: Date;
|
||||
mutual_guilds: unknown[]; // TODO: type
|
||||
premium_type: number;
|
||||
profile_themes_experiment_bucket: number;
|
||||
user_profile: UserProfile;
|
||||
guild_member?: APIGuildMember;
|
||||
guild_member_profile?: PublicMemberProfile;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user