create invite token dialog
This commit is contained in:
parent
d875a8f8fb
commit
84d0a0e542
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import Display from '../components/display';
|
||||
@ -49,8 +49,19 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
}
|
||||
}, [ expiresFromNowText ]);
|
||||
|
||||
const [ tokenResult, tokenError, tokenButtonText, tokenButtonShaking, tokenButtonCallback ] = ReactHelper.useAsyncButtonSubscription(
|
||||
async () => await guild.requestDoCreateToken(expiresFromNowText === 'never' ? null : expiresFromNowText),
|
||||
{ start: 'Create Token', pending: 'Creating...', error: 'Try Again', done: 'Create Token' },
|
||||
[ guild, expiresFromNowText ]
|
||||
);
|
||||
|
||||
const createToken = useCallback(async () => {
|
||||
await guild.requestDoCreateToken(expiresFromNowText); // note: the text, NOT the duration. The server uses PostgreSQL interval conversion
|
||||
}, [ expiresFromNowText ]);
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
if (guildMetaError) return 'Unable to load guild metadata';
|
||||
if (tokenError) return 'Unable to create token';
|
||||
return null;
|
||||
}, [ guildMetaError ]);
|
||||
|
||||
@ -97,7 +108,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
{ value: '1 month', display: 'a Month' },
|
||||
{ value: 'never', display: 'Never' },
|
||||
]} />
|
||||
<div><Button>Create Invite</Button></div>
|
||||
<div><Button shaking={tokenButtonShaking} onClick={tokenButtonCallback}>{tokenButtonText}</Button></div>
|
||||
</div>
|
||||
<InvitePreview
|
||||
name={guildMeta?.name ?? ''} iconSrc={iconSrc}
|
||||
|
@ -205,6 +205,7 @@ export default class GuildSubscriptions {
|
||||
const events = useMemo(() => new EventEmitter<MultipleSubscriptionEvents<T>>(), []);
|
||||
|
||||
const onFetch = useCallback((fetchValue: T[] | null) => {
|
||||
if (fetchValue) fetchValue.sort(sortFunc);
|
||||
setValue(fetchValue);
|
||||
setFetchError(null);
|
||||
events.emit('fetch');
|
||||
|
@ -3,9 +3,10 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import { DependencyList, useEffect, useRef, useState } from "react";
|
||||
import { DependencyList, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { ShouldNeverHappenError } from "../../data-types";
|
||||
import Util from '../../util';
|
||||
|
||||
// Helper function so we can use JSX before fully committing to React
|
||||
|
||||
@ -51,4 +52,54 @@ export default class ReactHelper {
|
||||
|
||||
return [ value, error ];
|
||||
}
|
||||
|
||||
static useAsyncButtonSubscription<T>(
|
||||
actionFunc: () => Promise<T>,
|
||||
stateText: { start: string, pending: string, error: string, done: string },
|
||||
deps: DependencyList
|
||||
): [ result: T | null, error: unknown | null, text: string, shaking: boolean, callback: () => void ] {
|
||||
const isMounted = useRef(false);
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
return () => { isMounted.current = false; }
|
||||
});
|
||||
|
||||
const [ result, setResult ] = useState<T | null>(null);
|
||||
const [ error, setError ] = useState<unknown | null>(null);
|
||||
|
||||
const [ pending, setPending ] = useState<boolean>(false);
|
||||
const [ complete, setComplete ] = useState<boolean>(false);
|
||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
||||
|
||||
const text = useMemo(() => {
|
||||
if (error) return stateText.error;
|
||||
if (pending) return stateText.pending;
|
||||
if (complete) return stateText.done;
|
||||
return stateText.start;
|
||||
}, [ error, pending, complete ]);
|
||||
|
||||
const callback = useCallback(async () => {
|
||||
if (pending) return;
|
||||
setPending(true);
|
||||
try {
|
||||
const value = await actionFunc();
|
||||
if (!isMounted.current) return;
|
||||
setResult(value);
|
||||
setComplete(true);
|
||||
setError(null);
|
||||
setPending(false);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('unable to perform async button subscription');
|
||||
if (!isMounted.current) return;
|
||||
setError(e);
|
||||
setShaking(true);
|
||||
await Util.sleep(400);
|
||||
if (!isMounted.current) return;
|
||||
setShaking(false);
|
||||
setPending(false);
|
||||
}
|
||||
}, [ ...deps, pending ]);
|
||||
|
||||
return [ result, error, text, shaking, callback ];
|
||||
}
|
||||
}
|
||||
|
@ -384,13 +384,16 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
|
||||
async requestSetGuildIcon(guildIcon: Buffer): Promise<void> {
|
||||
await this.socketGuild.requestSetGuildIcon(guildIcon);
|
||||
}
|
||||
async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<void> {
|
||||
await this.socketGuild.requestDoUpdateChannel(channelId, name, flavorText);
|
||||
async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<Channel> {
|
||||
return await this.socketGuild.requestDoUpdateChannel(channelId, name, flavorText);
|
||||
}
|
||||
async requestDoCreateChannel(name: string, flavorText: string | null): Promise<void> {
|
||||
await this.socketGuild.requestDoCreateChannel(name, flavorText);
|
||||
async requestDoCreateChannel(name: string, flavorText: string | null): Promise<Channel> {
|
||||
return await this.socketGuild.requestDoCreateChannel(name, flavorText);
|
||||
}
|
||||
async requestDoRevokeToken(token: string): Promise<void> {
|
||||
await this.socketGuild.requestDoRevokeToken(token);
|
||||
}
|
||||
async requestDoCreateToken(expiresAfter: string | null): Promise<Token> {
|
||||
return await this.socketGuild.requestDoCreateToken(expiresAfter);
|
||||
}
|
||||
}
|
||||
|
@ -69,8 +69,7 @@ export default class SocketGuild extends EventEmitter<Connectable> implements As
|
||||
this.emit('update-resource', updatedResource);
|
||||
});
|
||||
|
||||
// TODO: The server does not emit new-token
|
||||
this.socket.on('new-token', async (newDataToken: any) => {
|
||||
this.socket.on('create-token', async (newDataToken: any) => {
|
||||
const newToken = Token.fromDBData(newDataToken);
|
||||
this.emit('new-tokens', [ newToken ]);
|
||||
});
|
||||
@ -182,13 +181,19 @@ export default class SocketGuild extends EventEmitter<Connectable> implements As
|
||||
async requestSetGuildIcon(guildIcon: Buffer): Promise<void> {
|
||||
await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'set-icon', guildIcon);
|
||||
}
|
||||
async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<void> {
|
||||
const _changedChannel = await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'update-channel', channelId, name, flavorText);
|
||||
async requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<Channel> {
|
||||
const dataChangedChannel = await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'update-channel', channelId, name, flavorText);
|
||||
return Channel.fromDBData(dataChangedChannel);
|
||||
}
|
||||
async requestDoCreateChannel(name: string, flavorText: string | null): Promise<void> {
|
||||
const _newChannel = await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'create-text-channel', name, flavorText);
|
||||
async requestDoCreateChannel(name: string, flavorText: string | null): Promise<Channel> {
|
||||
const dataNewChannel = await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'create-text-channel', name, flavorText);
|
||||
return Channel.fromDBData(dataNewChannel);
|
||||
}
|
||||
async requestDoRevokeToken(token: string): Promise<void> {
|
||||
await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'revoke-token', token);
|
||||
}
|
||||
async requestDoCreateToken(expiresAfter: string | null): Promise<Token> {
|
||||
const dataToken = await this.queryOnceVerified(Globals.DEFAULT_SOCKET_TIMEOUT, 'create-token', expiresAfter);
|
||||
return Token.fromDBData(dataToken);
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,10 @@ export interface AsyncRequestable {
|
||||
requestSetAvatar(avatar: Buffer): Promise<void>;
|
||||
requestSetGuildName(guildName: string): Promise<void>;
|
||||
requestSetGuildIcon(guildIcon: Buffer): Promise<void>;
|
||||
requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<void>;
|
||||
requestDoCreateChannel(name: string, flavorText: string | null): Promise<void>;
|
||||
requestDoUpdateChannel(channelId: string, name: string, flavorText: string | null): Promise<Channel>;
|
||||
requestDoCreateChannel(name: string, flavorText: string | null): Promise<Channel>;
|
||||
requestDoRevokeToken(token: string): Promise<void>;
|
||||
requestDoCreateToken(expiresAfter: string | null): Promise<Token>;
|
||||
}
|
||||
export type Requestable = AsyncRequestable;
|
||||
|
||||
|
@ -519,18 +519,18 @@ export default class DB {
|
||||
|
||||
static async getTokens(guildId: string): Promise<any[]> {
|
||||
const result = await db.query(`
|
||||
SELECT "token", "member_id", "created", "expires" FROM tokens
|
||||
SELECT "id", "token", "member_id", "created", "expires" FROM tokens
|
||||
WHERE "guild_id"=$1
|
||||
`, [ guildId]);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
//insert into tokens (guild_id, expires) VALUES ('226b3e9e-5220-4205-bf5b-6738b9b3bb39', NOW() + '7 days'::interval) RETURNING "token", "expires";
|
||||
static async createToken(guildId: string, expiresAfter: string): Promise<any> {
|
||||
static async createToken(guildId: string, expiresAfter: string | null): Promise<any> {
|
||||
const result = await db.query(`
|
||||
INSERT INTO "tokens" ("guild_id", "expires")
|
||||
VALUES ($1, NOW() + $2::interval)
|
||||
RETURNING "token", "expires"
|
||||
VALUES ($1, CASE WHEN $2::text IS NULL THEN NULL ELSE NOW() + $2::interval END)
|
||||
RETURNING "id", "token", "member_id", "created", "expires"
|
||||
`, [ guildId, expiresAfter ]);
|
||||
if (result.rows.length != 1) {
|
||||
throw new Error('unable to insert a token');
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -77,7 +77,7 @@ $$ LANGUAGE plpgsql;
|
||||
CREATE TABLE "tokens" (
|
||||
"id" UUID NOT NULL DEFAULT uuid_generate_v4()
|
||||
, "guild_id" UUID NOT NULL REFERENCES "guilds"("id")
|
||||
, "expires" TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
, "expires" TIMESTAMP WITH TIME ZONE -- Null means never expires
|
||||
, "token" UUID NOT NULL DEFAULT uuid_generate_v4()
|
||||
, "member_id" UUID REFERENCES "members"("id")
|
||||
, "created" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
|
Loading…
Reference in New Issue
Block a user