mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 03:49:11 +00:00
feat(Editor): make server utilities their own pages
Signed-off-by: Katalina Okano <git@kat.cafe>
This commit is contained in:
parent
d52508a046
commit
4cc202b62a
8 changed files with 125 additions and 153 deletions
|
@ -5,10 +5,10 @@ export const Space = styled.div`
|
||||||
height: 15px;
|
height: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LinedSpace = styled.div<{ width?: number }>`
|
export const LinedSpace = styled.div<{ width?: number; color?: string }>`
|
||||||
height: 7.5px;
|
height: 7.5px;
|
||||||
margin-top: 7.5px;
|
margin-top: 7.5px;
|
||||||
border-top: 1px solid ${palette.taupe300};
|
border-top: 1px solid ${(props) => (props.color ? props.color : palette.taupe300)};
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.width &&
|
props.width &&
|
||||||
css`
|
css`
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { guildData } from '@roleypoly/design-system/fixtures/storyData';
|
||||||
|
import { ServerUtilities } from './ServerUtilities';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Molecules/Server Utilities',
|
||||||
|
component: ServerUtilities,
|
||||||
|
args: {
|
||||||
|
guildData: guildData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serverUtilities = (args) => <ServerUtilities {...args} />;
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { palette } from '@roleypoly/design-system/atoms/colors';
|
||||||
|
import { transitions } from '@roleypoly/design-system/atoms/timings';
|
||||||
|
import { text200, text400 } from '@roleypoly/design-system/atoms/typography';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const ClickaleBlock = styled.a`
|
||||||
|
display: flex;
|
||||||
|
color: unset;
|
||||||
|
text-decoration: none;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5em;
|
||||||
|
transition: background-color ${transitions.actionable}s ease-in-out;
|
||||||
|
box-sizing: border-box;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 93vw;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
background-color: ${palette.taupe300};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Title = styled.div`
|
||||||
|
${text400};
|
||||||
|
/* padding: 0.15em 0; */
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: ${palette.taupe500};
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Description = styled.div`
|
||||||
|
${text200};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MainSide = styled.div``;
|
|
@ -1,72 +1,74 @@
|
||||||
import { palette } from '@roleypoly/design-system/atoms/colors';
|
import {
|
||||||
import { FaderOpacity } from '@roleypoly/design-system/atoms/fader';
|
ClickaleBlock,
|
||||||
import { TextInput } from '@roleypoly/design-system/atoms/text-input';
|
Description,
|
||||||
import { AmbientLarge, Text } from '@roleypoly/design-system/atoms/typography';
|
MainSide,
|
||||||
import { MessageBox } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled';
|
Title,
|
||||||
import { GuildData, WebhookValidationStatus } from '@roleypoly/types';
|
} from '@roleypoly/design-system/molecules/server-utilities/ServerUtilities.styled';
|
||||||
import { GoAlert, GoInfo } from 'react-icons/go';
|
import { GuildData } from '@roleypoly/types';
|
||||||
import ReactTooltip from 'react-tooltip';
|
import { GoArchive, GoChevronRight, GoReport, GoShield, GoSync } from 'react-icons/go';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onChange: (guildData: GuildData) => void;
|
|
||||||
guildData: GuildData;
|
guildData: GuildData;
|
||||||
validationStatus: WebhookValidationStatus | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServerUtilities = (props: Props) => {
|
const Utility = (props: {
|
||||||
return (
|
link: string;
|
||||||
<MessageBox>
|
title: React.ReactNode;
|
||||||
<Text>
|
description: string;
|
||||||
(optional) Webhook URL for Audit Logging{' '}
|
}) => (
|
||||||
<GoInfo
|
<ClickaleBlock href={props.link}>
|
||||||
data-for="server-utilities"
|
<MainSide>
|
||||||
data-tip="Reports changes made in the editor to a Webhook integration within your Discord server."
|
<Title>{props.title}</Title>
|
||||||
/>
|
<Description>{props.description}</Description>
|
||||||
</Text>
|
</MainSide>
|
||||||
<TextInput
|
<div>
|
||||||
placeholder="https://discord.com/api/webhooks/000000000000000000/..."
|
<GoChevronRight />
|
||||||
value={props.guildData.auditLogWebhook || ''}
|
</div>
|
||||||
onChange={(event) =>
|
</ClickaleBlock>
|
||||||
props.onChange({ ...props.guildData, auditLogWebhook: event.target.value })
|
);
|
||||||
}
|
|
||||||
/>
|
|
||||||
<FaderOpacity isVisible={props.validationStatus !== WebhookValidationStatus.Ok}>
|
|
||||||
<ValidationStatus validationStatus={props.validationStatus} />
|
|
||||||
</FaderOpacity>
|
|
||||||
<ReactTooltip id="server-utilities" />
|
|
||||||
</MessageBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ValidationStatus = (props: Pick<Props, 'validationStatus'>) => {
|
export const ServerUtilities = (props: Props) => (
|
||||||
switch (props.validationStatus) {
|
<div>
|
||||||
case WebhookValidationStatus.NotDiscordURL:
|
{/* <LargeText>Server Utilities</LargeText> */}
|
||||||
return (
|
<Utility
|
||||||
<AmbientLarge>
|
title={
|
||||||
<Alert /> URL must be to a Discord webhook, starting with
|
<>
|
||||||
"https://discord.com/api/webhooks/".
|
<GoShield />
|
||||||
</AmbientLarge>
|
Access Control
|
||||||
);
|
</>
|
||||||
case WebhookValidationStatus.NotSameGuild:
|
}
|
||||||
return (
|
description="Set up who can use Roleypoly in your server"
|
||||||
<AmbientLarge>
|
link={`/s/${props.guildData.id}/edit/access-control`}
|
||||||
<Alert /> Webhook must be in the same guild you are currently editing.
|
/>
|
||||||
</AmbientLarge>
|
<Utility
|
||||||
);
|
title={
|
||||||
case WebhookValidationStatus.DoesNotExist:
|
<>
|
||||||
return (
|
<GoReport />
|
||||||
<AmbientLarge>
|
Audit Logging
|
||||||
<Alert /> This webhook doesn't exist.
|
</>
|
||||||
</AmbientLarge>
|
}
|
||||||
);
|
description="Setup audit logging via a Discord webhook"
|
||||||
default:
|
link={`/s/${props.guildData.id}/edit/audit-logging`}
|
||||||
return <AmbientLarge> </AmbientLarge>;
|
/>
|
||||||
}
|
<Utility
|
||||||
};
|
title={
|
||||||
|
<>
|
||||||
const Alert = styled(GoAlert)`
|
<GoSync />
|
||||||
color: ${palette.red400};
|
Import from Roleypoly Legacy
|
||||||
position: relative;
|
</>
|
||||||
top: 2px;
|
}
|
||||||
`;
|
description="Used Roleypoly before and don't see your categories?"
|
||||||
|
link={`/s/${props.guildData.id}/edit/import-from-legacy`}
|
||||||
|
/>
|
||||||
|
<Utility
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<GoArchive />
|
||||||
|
Manage your Data
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
description="Export or delete all of your Roleypoly data."
|
||||||
|
link={`/s/${props.guildData.id}/edit/data`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './ServerUtilities';
|
|
@ -5,12 +5,7 @@ import { ServerUtilities } from '@roleypoly/design-system/molecules/server-utili
|
||||||
import { SecondaryEditing } from '@roleypoly/design-system/organisms/masthead';
|
import { SecondaryEditing } from '@roleypoly/design-system/organisms/masthead';
|
||||||
import { Container } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled';
|
import { Container } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled';
|
||||||
import { ServerCategoryEditor } from '@roleypoly/design-system/organisms/server-category-editor';
|
import { ServerCategoryEditor } from '@roleypoly/design-system/organisms/server-category-editor';
|
||||||
import {
|
import { Category, PresentableGuild } from '@roleypoly/types';
|
||||||
Category,
|
|
||||||
GuildData,
|
|
||||||
PresentableGuild,
|
|
||||||
WebhookValidationStatus,
|
|
||||||
} from '@roleypoly/types';
|
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -19,26 +14,17 @@ export type EditorShellProps = {
|
||||||
onGuildChange?: (guild: PresentableGuild) => void;
|
onGuildChange?: (guild: PresentableGuild) => void;
|
||||||
onCategoryChange?: (category: Category) => void;
|
onCategoryChange?: (category: Category) => void;
|
||||||
onMessageChange?: (message: PresentableGuild['data']['message']) => void;
|
onMessageChange?: (message: PresentableGuild['data']['message']) => void;
|
||||||
errors: {
|
|
||||||
webhookValidation: WebhookValidationStatus;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditorShell = (props: EditorShellProps) => {
|
export const EditorShell = (props: EditorShellProps) => {
|
||||||
const [guild, setGuild] = React.useState<PresentableGuild>(props.guild);
|
const [guild, setGuild] = React.useState<PresentableGuild>(props.guild);
|
||||||
const [errors, setErrors] = React.useState<EditorShellProps['errors']>(props.errors);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setGuild(props.guild);
|
setGuild(props.guild);
|
||||||
}, [props.guild]);
|
}, [props.guild]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setErrors(props.errors);
|
|
||||||
}, [props.errors]);
|
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
setGuild(props.guild);
|
setGuild(props.guild);
|
||||||
setErrors({ webhookValidation: WebhookValidationStatus.Ok });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const replaceCategories = (categories: Category[]) => {
|
const replaceCategories = (categories: Category[]) => {
|
||||||
|
@ -53,12 +39,6 @@ export const EditorShell = (props: EditorShellProps) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateGuildData = (data: PresentableGuild['data']) => {
|
|
||||||
setGuild((currentGuild) => {
|
|
||||||
return { ...currentGuild, data };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const doSubmit = () => {
|
const doSubmit = () => {
|
||||||
props.onGuildChange?.(guild);
|
props.onGuildChange?.(guild);
|
||||||
};
|
};
|
||||||
|
@ -87,47 +67,8 @@ export const EditorShell = (props: EditorShellProps) => {
|
||||||
<Space />
|
<Space />
|
||||||
<ServerCategoryEditor guild={guild} onChange={replaceCategories} />
|
<ServerCategoryEditor guild={guild} onChange={replaceCategories} />
|
||||||
<LinedSpace />
|
<LinedSpace />
|
||||||
<ServerUtilities
|
<ServerUtilities guildData={guild.data} />
|
||||||
guildData={guild.data}
|
|
||||||
onChange={updateGuildData}
|
|
||||||
validationStatus={validateWebhook(
|
|
||||||
guild.data.auditLogWebhook,
|
|
||||||
errors.webhookValidation
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateWebhook = (
|
|
||||||
webhook: GuildData['auditLogWebhook'],
|
|
||||||
validationStatus: WebhookValidationStatus
|
|
||||||
) => {
|
|
||||||
if (!webhook) {
|
|
||||||
return WebhookValidationStatus.NoneSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(webhook);
|
|
||||||
|
|
||||||
if (
|
|
||||||
url.hostname !== 'discord.com' ||
|
|
||||||
url.protocol !== 'https:' ||
|
|
||||||
url.pathname.startsWith('api/webhooks/')
|
|
||||||
) {
|
|
||||||
return WebhookValidationStatus.NotDiscordURL;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return WebhookValidationStatus.Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
validationStatus !== WebhookValidationStatus.Ok &&
|
|
||||||
validationStatus !== WebhookValidationStatus.NoneSet
|
|
||||||
) {
|
|
||||||
return validationStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebhookValidationStatus.Ok;
|
|
||||||
};
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ export const EditorTemplate = (
|
||||||
props;
|
props;
|
||||||
return (
|
return (
|
||||||
<AppShell {...appShellProps} activeGuildId={guild.id} small double>
|
<AppShell {...appShellProps} activeGuildId={guild.id} small double>
|
||||||
<EditorShell guild={guild} onGuildChange={onGuildChange} errors={props.errors} />
|
<EditorShell guild={guild} onGuildChange={onGuildChange} />
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EditorErrors = EditorShellProps['errors'];
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { navigate, Redirect } from '@reach/router';
|
import { navigate, Redirect } from '@reach/router';
|
||||||
import { EditorErrors, EditorTemplate } from '@roleypoly/design-system/templates/editor';
|
import { EditorTemplate } from '@roleypoly/design-system/templates/editor';
|
||||||
import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading';
|
import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading';
|
||||||
import {
|
import {
|
||||||
GuildDataUpdate,
|
GuildDataUpdate,
|
||||||
PresentableGuild,
|
PresentableGuild,
|
||||||
UserGuildPermissions,
|
UserGuildPermissions,
|
||||||
WebhookValidationStatus,
|
|
||||||
} from '@roleypoly/types';
|
} from '@roleypoly/types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
|
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
|
||||||
|
@ -26,9 +25,6 @@ const Editor = (props: EditorProps) => {
|
||||||
|
|
||||||
const [guild, setGuild] = React.useState<PresentableGuild | null | false>(null);
|
const [guild, setGuild] = React.useState<PresentableGuild | null | false>(null);
|
||||||
const [pending, setPending] = React.useState(false);
|
const [pending, setPending] = React.useState(false);
|
||||||
const [errors, setErrors] = React.useState<EditorErrors>({
|
|
||||||
webhookValidation: WebhookValidationStatus.Ok,
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const shouldPullUncached = (): boolean => {
|
const shouldPullUncached = (): boolean => {
|
||||||
|
@ -104,28 +100,13 @@ const Editor = (props: EditorProps) => {
|
||||||
navigate(`/s/${props.serverID}`);
|
navigate(`/s/${props.serverID}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 400) {
|
|
||||||
const error = await response.json();
|
|
||||||
if (error.data.what === 'webhookValidationStatus') {
|
|
||||||
setErrors((errors) => ({
|
|
||||||
...errors,
|
|
||||||
webhookValidation: error.data.webhookValidationStatus,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPending(false);
|
setPending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title title={`Editing ${guild.guild.name} - Roleypoly`} />
|
<Title title={`Editing ${guild.guild.name} - Roleypoly`} />
|
||||||
<EditorTemplate
|
<EditorTemplate {...appShellProps} guild={guild} onGuildChange={onGuildChange} />
|
||||||
{...appShellProps}
|
|
||||||
guild={guild}
|
|
||||||
onGuildChange={onGuildChange}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue