feat: Finished saving functionality for dashboard
This commit is contained in:
parent
3c404ab5fa
commit
136468b4bd
5 changed files with 499 additions and 233 deletions
11
add-test-server.http
Normal file
11
add-test-server.http
Normal file
|
@ -0,0 +1,11 @@
|
|||
POST http://localhost:3000/api/598539452343648256/config
|
||||
# Content-Type: application/json
|
||||
Authorization: Basic {{$dotenv DISCORD_CLIENT_ID}}:{{$dotenv DISCORD_CLIENT_SECRET}}
|
||||
Origin: http://localhost:3000
|
||||
|
||||
###
|
||||
|
||||
DELETE http://localhost:3000/api/598539452343648256/config
|
||||
# Content-Type: application/json
|
||||
Authorization: Basic {{$dotenv DISCORD_CLIENT_ID}}:{{$dotenv DISCORD_CLIENT_SECRET}}
|
||||
Origin: http://localhost:3000
|
|
@ -35,6 +35,16 @@ export const splitInterval = (tpInterval: number) => {
|
|||
return { targetMinute, targetHour, targetDay };
|
||||
};
|
||||
|
||||
export const combineInterval = (
|
||||
targetMinute: number,
|
||||
targetHour: number,
|
||||
targetDay: number,
|
||||
) => {
|
||||
const tpInterval = targetMinute | (targetHour << 6) | (targetDay << 11);
|
||||
|
||||
return tpInterval;
|
||||
};
|
||||
|
||||
export const DayKeys = ["0", "1", "2", "3", "4", "5", "6"] as const;
|
||||
export type DayKeys = (typeof DayKeys)[number];
|
||||
export type Messages = Record<DayKeys, string | null>;
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useLocation, useNavigate, useParams } from "@solidjs/router";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { PgUpdateSetSource } from "drizzle-orm/pg-core";
|
||||
import moment from "moment-timezone";
|
||||
import createClient from "openapi-fetch";
|
||||
import {
|
||||
For,
|
||||
Index,
|
||||
Show,
|
||||
batch,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
createSignal,
|
||||
} from "solid-js";
|
||||
|
@ -13,7 +18,11 @@ import { createStore } from "solid-js/store";
|
|||
import { getRequestEvent } from "solid-js/web";
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
import getAccessToken from "~/lib/accessToken";
|
||||
import { combineInterval, splitInterval } from "~/lib/responseBuilders";
|
||||
import { zodBigIntId } from "~/lib/zod";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
|
@ -22,26 +31,37 @@ if (typeof import.meta.env.VITE_DISCORD_BOT_TOKEN === "undefined")
|
|||
|
||||
const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
const initialValue = (params: ReturnType<typeof useParams>) => ({
|
||||
success: null as boolean | null,
|
||||
guild: {
|
||||
id: params.guildId,
|
||||
name: undefined as string | undefined,
|
||||
icon: undefined as string | null | undefined,
|
||||
channel: "",
|
||||
channels: [] as { id: string; name: string }[],
|
||||
},
|
||||
tzNames: [guessTZ()],
|
||||
});
|
||||
|
||||
const getPayload = async (
|
||||
id: string,
|
||||
): Promise<
|
||||
| { success: false; message: string }
|
||||
| (ReturnType<typeof initialValue> & { success: true })
|
||||
| {
|
||||
success: true;
|
||||
guild: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null | undefined;
|
||||
tpChannelId: string;
|
||||
channels: {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
tpInterval: number;
|
||||
pingableRoles: boolean;
|
||||
timezone: string;
|
||||
};
|
||||
tzNames: string[];
|
||||
}
|
||||
> => {
|
||||
"use server";
|
||||
|
||||
let guildId: bigint;
|
||||
try {
|
||||
guildId = zodBigIntId.parse(id);
|
||||
} catch (e) {
|
||||
return { success: false, message: "ID is invalid" };
|
||||
}
|
||||
|
||||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
|
@ -63,7 +83,7 @@ const getPayload = async (
|
|||
{
|
||||
params: {
|
||||
path: {
|
||||
guild_id: id,
|
||||
guild_id: String(guildId),
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
|
@ -81,7 +101,7 @@ const getPayload = async (
|
|||
};
|
||||
}
|
||||
|
||||
const guild = guildsData?.find((e) => e.id === id);
|
||||
const guild = guildsData?.find((e) => e.id === String(guildId));
|
||||
|
||||
if (!guild)
|
||||
return {
|
||||
|
@ -95,7 +115,10 @@ const getPayload = async (
|
|||
"User is no MANAGE_GUILD permissions on this guild with requested id!",
|
||||
};
|
||||
|
||||
const channels: ReturnType<typeof initialValue>["guild"]["channels"] = [];
|
||||
const channels: {
|
||||
id: string;
|
||||
name: string;
|
||||
}[] = [];
|
||||
channelsData?.forEach((channel) => {
|
||||
if (channel.type !== 0) return;
|
||||
channels.push({
|
||||
|
@ -104,6 +127,12 @@ const getPayload = async (
|
|||
});
|
||||
});
|
||||
|
||||
const config = await db.query.guilds
|
||||
.findFirst({ where: eq(guilds.id, guildId) })
|
||||
.execute();
|
||||
|
||||
if (!config) return { success: false, message: "No config found!" };
|
||||
|
||||
console.log(new URL(event.request.url).pathname, "success");
|
||||
|
||||
return {
|
||||
|
@ -112,47 +141,80 @@ const getPayload = async (
|
|||
id: guild.id,
|
||||
name: guild.name,
|
||||
icon: guild.icon,
|
||||
channel: "",
|
||||
tpChannelId: String(config.tpChannelId ?? ""),
|
||||
channels,
|
||||
tpInterval: config.tpInterval,
|
||||
pingableRoles: config.tpRolesEnabled,
|
||||
timezone: config.timezone,
|
||||
},
|
||||
tzNames: moment.tz.names(),
|
||||
};
|
||||
};
|
||||
|
||||
const saveConfig = async (
|
||||
id: string,
|
||||
updateValues: PgUpdateSetSource<typeof guilds>,
|
||||
): Promise<{ success: false; message: string } | { success: true }> => {
|
||||
"use server";
|
||||
|
||||
let guildId: bigint;
|
||||
try {
|
||||
guildId = zodBigIntId.parse(id);
|
||||
} catch (e) {
|
||||
return { success: false, message: "ID is invalid" };
|
||||
}
|
||||
|
||||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
console.log({ updateValues });
|
||||
|
||||
await db
|
||||
.update(guilds)
|
||||
.set(updateValues)
|
||||
.where(eq(guilds.id, guildId))
|
||||
.execute();
|
||||
|
||||
console.log(new URL(event.request.url).pathname, "config save success");
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
function config() {
|
||||
const params = useParams();
|
||||
const navigator = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const [timezoneRef, setTimezoneRef] = createSignal<HTMLInputElement>();
|
||||
const [timePlanningRef, setTimePlanningRef] =
|
||||
createSignal<HTMLInputElement>();
|
||||
const [tpEnabledRef, setTpEnabledRef] = createSignal<HTMLInputElement>();
|
||||
const [channelRef, setChannelRef] = createSignal<HTMLSelectElement>();
|
||||
const [targetMinuteRef, setTargetMinuteRef] =
|
||||
createSignal<HTMLSelectElement>();
|
||||
const [targetHourRef, setTargetHourRef] = createSignal<HTMLSelectElement>();
|
||||
const [targetDayRef, setTargetDayRef] = createSignal<HTMLSelectElement>();
|
||||
const [pingableRolesRef, setPingableRolesRef] =
|
||||
createSignal<HTMLInputElement>();
|
||||
|
||||
const [timezone, setTimezone] = createSignal(guessTZ());
|
||||
const [payload] = createResource(
|
||||
const [payload, { refetch }] = createResource(
|
||||
params.guildId,
|
||||
async (id) => {
|
||||
const payload = await getPayload(id).catch((e) => console.warn(e, id));
|
||||
const payload = await getPayload(id);
|
||||
|
||||
if (!payload) {
|
||||
console.error(location.pathname, payload);
|
||||
return initialValue(params);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!payload.success) {
|
||||
console.log(payload);
|
||||
console.log(location.pathname, payload.message, "No success");
|
||||
navigator("/config", { replace: false });
|
||||
return initialValue(params);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
{
|
||||
initialValue: initialValue(params),
|
||||
deferStream: true,
|
||||
},
|
||||
);
|
||||
|
@ -160,168 +222,337 @@ function config() {
|
|||
features: {
|
||||
timePlanning: {
|
||||
enabled: false,
|
||||
channelId: "833442323160891452",
|
||||
channelId: "",
|
||||
targetMinute: 0,
|
||||
targetHour: 0,
|
||||
targetDay: 0,
|
||||
pingableRoles: false,
|
||||
timezone: guessTZ(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updateValues = createMemo(() => {
|
||||
const guild = payload()?.guild;
|
||||
if (!guild) return {};
|
||||
const data = config.features.timePlanning;
|
||||
const tpInterval = combineInterval(
|
||||
data.targetMinute,
|
||||
data.targetHour,
|
||||
data.targetDay,
|
||||
);
|
||||
|
||||
const result: PgUpdateSetSource<typeof guilds> = {
|
||||
timezone: data.timezone !== guild.timezone ? data.timezone : undefined,
|
||||
tpChannelId:
|
||||
data.enabled || !data.channelId
|
||||
? data.channelId && data.channelId !== guild.tpChannelId
|
||||
? BigInt(data.channelId)
|
||||
: undefined
|
||||
: null,
|
||||
tpInterval:
|
||||
data.enabled && data.channelId && tpInterval !== guild.tpInterval
|
||||
? tpInterval
|
||||
: undefined,
|
||||
tpRolesEnabled:
|
||||
data.enabled &&
|
||||
data.channelId &&
|
||||
data.pingableRoles !== guild.pingableRoles
|
||||
? data.pingableRoles
|
||||
: undefined,
|
||||
};
|
||||
return result;
|
||||
});
|
||||
const willUpdateValues = () =>
|
||||
Object.values(updateValues()).filter((e) => typeof e !== "undefined")
|
||||
.length;
|
||||
|
||||
createEffect(() => {
|
||||
const channelId = payload().guild.channel;
|
||||
setConfig("features", "timePlanning", "channelId", channelId);
|
||||
const ref = channelRef();
|
||||
if (!ref) return;
|
||||
ref.value = channelId;
|
||||
const guild = payload()?.guild;
|
||||
if (!guild) return;
|
||||
const channelId = guild.tpChannelId;
|
||||
const pingableRoles = guild.pingableRoles;
|
||||
const { targetMinute, targetHour, targetDay } = splitInterval(
|
||||
guild.tpInterval,
|
||||
);
|
||||
const timezone = guild.timezone;
|
||||
|
||||
batch(() => {
|
||||
setConfig("features", "timePlanning", "enabled", !!channelId);
|
||||
setConfig("features", "timePlanning", "channelId", channelId);
|
||||
setConfig("features", "timePlanning", "targetMinute", targetMinute);
|
||||
setConfig("features", "timePlanning", "targetHour", targetHour);
|
||||
setConfig("features", "timePlanning", "targetDay", targetDay);
|
||||
setConfig("features", "timePlanning", "pingableRoles", pingableRoles);
|
||||
setConfig("features", "timePlanning", "timezone", timezone);
|
||||
});
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = timezoneRef();
|
||||
if (!ref) return;
|
||||
ref.value = timezone();
|
||||
if (ref) ref.value = config.features.timePlanning.timezone;
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = timePlanningRef();
|
||||
if (!ref) return;
|
||||
ref.checked = config.features.timePlanning.enabled;
|
||||
const ref = tpEnabledRef();
|
||||
if (ref) ref.checked = config.features.timePlanning.enabled;
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = channelRef();
|
||||
if (ref) ref.value = config.features.timePlanning.channelId;
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = targetMinuteRef();
|
||||
if (ref) ref.value = String(config.features.timePlanning.targetMinute);
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = targetHourRef();
|
||||
if (ref) ref.value = String(config.features.timePlanning.targetHour);
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = targetDayRef();
|
||||
if (ref) ref.value = String(config.features.timePlanning.targetDay);
|
||||
});
|
||||
createEffect(() => {
|
||||
const ref = pingableRolesRef();
|
||||
if (!ref) return;
|
||||
ref.checked = config.features.timePlanning.pingableRoles;
|
||||
if (ref) ref.checked = config.features.timePlanning.pingableRoles;
|
||||
});
|
||||
|
||||
return (
|
||||
<Layout site="config">
|
||||
<h3>Configure li'l Judd in</h3>
|
||||
<div>
|
||||
<div class="group">
|
||||
<h3>Configure li'l Judd in</h3>
|
||||
<div>
|
||||
<div class="flex-row centered">
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
payload().guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${payload().guild.id}/${
|
||||
payload().guild.icon
|
||||
}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{payload().guild.name}</h1>
|
||||
<div>
|
||||
<div class="flex-row centered">
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
payload()?.guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${payload()?.guild.id}/${
|
||||
payload()?.guild.icon
|
||||
}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{payload()?.guild.name}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Guild</h2>
|
||||
<p>General settings for this guild.</p>
|
||||
<div class="flex-row">
|
||||
<label for="timezone">Timezone for your server:</label>
|
||||
<input
|
||||
type="text"
|
||||
list="timezones"
|
||||
id="timezone"
|
||||
ref={(e) => setTimezoneRef(e)}
|
||||
// disabled={!tzNames().find((e) => e === timezone())}
|
||||
onInput={(e) => setTimezone(e.target.value)}
|
||||
/>
|
||||
|
||||
<datalist id="timezones">
|
||||
<Index each={payload().tzNames}>
|
||||
{(zone) => <option value={zone()} />}
|
||||
</Index>
|
||||
</datalist>
|
||||
|
||||
<button
|
||||
disabled={guessTZ() === timezone()}
|
||||
title={"Detected: " + guessTZ()}
|
||||
onClick={() => setTimezone(guessTZ())}
|
||||
>
|
||||
Auto-detect
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Features</h2>
|
||||
<p>Configure the features of the bot</p>
|
||||
<label for="timePlanning" class="flex-row">
|
||||
<p>Time Planning </p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.enabled ? faToggleOn : faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="timePlanning"
|
||||
ref={(e) => setTimePlanningRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig("features", "timePlanning", "enabled", e.target.checked)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
class="sub"
|
||||
classList={{ disabled: !config.features.timePlanning.enabled }}
|
||||
>
|
||||
<section class="box">
|
||||
<h2>Guild</h2>
|
||||
<p>General settings for this guild.</p>
|
||||
<div class="flex-row">
|
||||
<label>Target channel:</label>
|
||||
<select
|
||||
ref={(e) => setChannelRef(e)}
|
||||
<label for="timezone">Timezone for your server:</label>
|
||||
<input
|
||||
type="text"
|
||||
list="timezones"
|
||||
id="timezone"
|
||||
ref={(e) => setTimezoneRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"channelId",
|
||||
"timezone",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option disabled value="">
|
||||
--Select a Channel--
|
||||
</option>
|
||||
<For each={payload().guild.channels}>
|
||||
{(channel) => (
|
||||
<option value={channel.id}>{channel.name}</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<label for="pingableRoles" class="flex-row">
|
||||
<p>Enable pingable Roles:</p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.pingableRoles
|
||||
? faToggleOn
|
||||
: faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="pingableRoles"
|
||||
ref={(e) => setPingableRolesRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"pingableRoles",
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<button>Apply</button>
|
||||
<button onClick={() => navigator("/config")}>Back</button>
|
||||
</section>
|
||||
<datalist id="timezones">
|
||||
<Index each={payload()?.tzNames}>
|
||||
{(zone) => <option value={zone()} />}
|
||||
</Index>
|
||||
</datalist>
|
||||
|
||||
<button
|
||||
disabled={guessTZ() === config.features.timePlanning.timezone}
|
||||
title={"Detected: " + guessTZ()}
|
||||
onClick={() =>
|
||||
setConfig("features", "timePlanning", "timezone", guessTZ())
|
||||
}
|
||||
>
|
||||
Auto-detect
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="box">
|
||||
<h2>Features</h2>
|
||||
<p>Configure the features of the bot</p>
|
||||
<label for="timePlanning" class="flex-row">
|
||||
<p>Time Planning </p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.enabled
|
||||
? faToggleOn
|
||||
: faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="timePlanning"
|
||||
ref={(e) => setTpEnabledRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"enabled",
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
class="sub"
|
||||
classList={{ disabled: !config.features.timePlanning.enabled }}
|
||||
>
|
||||
<div class="flex-row">
|
||||
<label>Target channel:</label>
|
||||
<select
|
||||
ref={(e) => setChannelRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"channelId",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option disabled value="">
|
||||
--Select a Channel--
|
||||
</option>
|
||||
<For each={payload()?.guild.channels}>
|
||||
{(channel) => (
|
||||
<option value={channel.id}>{channel.name}</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
<Show
|
||||
when={
|
||||
config.features.timePlanning.enabled &&
|
||||
!config.features.timePlanning.channelId
|
||||
}
|
||||
>
|
||||
{"<-- or changes won't be saved"}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<label>Target Interval:</label>
|
||||
<select
|
||||
ref={(e) => setTargetDayRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"targetDay",
|
||||
Number(e.target.value),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Index each={Array.from(Array(7)).map((_e, i) => i)}>
|
||||
{(id) => {
|
||||
const weekdays = [
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
];
|
||||
return (
|
||||
<option value={String(id())}>{weekdays[id()]}</option>
|
||||
);
|
||||
}}
|
||||
</Index>
|
||||
</select>
|
||||
at
|
||||
<select
|
||||
ref={(e) => setTargetHourRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"targetHour",
|
||||
Number(e.target.value),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Index each={Array.from(Array(24)).map((_e, i) => i)}>
|
||||
{(id) => (
|
||||
<option value={String(id())}>{String(id())}</option>
|
||||
)}
|
||||
</Index>
|
||||
</select>
|
||||
:
|
||||
<select
|
||||
ref={(e) => setTargetMinuteRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"targetMinute",
|
||||
Number(e.target.value),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Index each={Array.from(Array(60)).map((_e, i) => i)}>
|
||||
{(id) => (
|
||||
<option value={String(id())}>
|
||||
{String(id()).padStart(2, "0")}
|
||||
</option>
|
||||
)}
|
||||
</Index>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<label for="pingableRoles" class="flex-row">
|
||||
<p>Enable pingable Roles:</p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.pingableRoles
|
||||
? faToggleOn
|
||||
: faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="pingableRoles"
|
||||
ref={(e) => setPingableRolesRef(e)}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"pingableRoles",
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="box">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const id = payload()?.guild.id;
|
||||
if (!id || !willUpdateValues()) return;
|
||||
await saveConfig(id, updateValues());
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
<button onClick={() => navigator("/config")}>Back</button>
|
||||
<Show when={willUpdateValues()}>UNSAVED CHANGES</Show>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { faPlusCircle, faWrench } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useLocation, useNavigate } from "@solidjs/router";
|
||||
import createClient from "openapi-fetch";
|
||||
import { For, createResource } from "solid-js";
|
||||
import { For, Show, createResource } from "solid-js";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
import getAccessToken from "~/lib/accessToken";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
@ -15,13 +17,16 @@ if (typeof import.meta.env.VITE_DISCORD_CLIENT_ID === "undefined")
|
|||
if (typeof import.meta.env.VITE_DISCORD_OAUTH2_PERMISSIONS === "undefined")
|
||||
throw new Error("No env VITE_DISCORD_OAUTH2_PERMISSIONS found!");
|
||||
|
||||
interface Guild {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null | undefined;
|
||||
}
|
||||
|
||||
const initialValue = () => ({
|
||||
success: null as boolean | null,
|
||||
guilds: [] as {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null | undefined;
|
||||
}[],
|
||||
joined: [] as Guild[],
|
||||
canJoin: [] as Guild[],
|
||||
});
|
||||
|
||||
const getPayload = async (): Promise<
|
||||
|
@ -43,9 +48,10 @@ const getPayload = async (): Promise<
|
|||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data: guilds, error } = await GET("/users/@me/guilds", {
|
||||
const { data, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
const configs = await db.select().from(guilds).execute();
|
||||
|
||||
if (error) {
|
||||
console.log("Discord api error:", { error });
|
||||
|
@ -53,12 +59,21 @@ const getPayload = async (): Promise<
|
|||
}
|
||||
console.log(pathname, "success");
|
||||
|
||||
const joined: Guild[] = [];
|
||||
const canJoin: Guild[] = [];
|
||||
|
||||
data
|
||||
?.filter((e) => parseInt(e.permissions) & (1 << 5))
|
||||
.forEach(({ id, name, icon }) => {
|
||||
configs.map((e) => e.id.toString()).includes(id)
|
||||
? joined.push({ id, name, icon })
|
||||
: canJoin.push({ id, name, icon });
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
guilds:
|
||||
guilds
|
||||
?.filter((e) => parseInt(e.permissions) & (1 << 5))
|
||||
.map(({ id, name, icon }) => ({ id, name, icon })) ?? [],
|
||||
joined,
|
||||
canJoin,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -84,64 +99,62 @@ function index() {
|
|||
|
||||
return payload;
|
||||
},
|
||||
{ deferStream: true },
|
||||
{ deferStream: true, initialValue: initialValue() },
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout site="config">
|
||||
<div class="group">
|
||||
<h3>Configure li'l Judd in</h3>
|
||||
<div>
|
||||
<For each={payload()?.guilds}>
|
||||
{(guild) => {
|
||||
return (
|
||||
<a href={`/config/${guild.id}`} class="flex-row centered">
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{guild.name}</h1>
|
||||
<FontAwesomeIcon icon={faWrench} size="xl" />
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
<GuildList list={payload().joined} joined={true} />
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Add li'l Judd to</h3>
|
||||
<div>
|
||||
<For each={payload()?.guilds}>
|
||||
{(guild) => {
|
||||
return (
|
||||
<a
|
||||
href={`https://discord.com/api/oauth2/authorize?client_id=${import.meta.env.VITE_DISCORD_CLIENT_ID}&permissions=${import.meta.env.VITE_DISCORD_OAUTH2_PERMISSIONS}&scope=bot&guild_id=${guild.id}`}
|
||||
class="flex-row centered"
|
||||
>
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{guild.name}</h1>
|
||||
<FontAwesomeIcon icon={faPlusCircle} size="xl" />
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
<GuildList list={payload().canJoin} joined={false} />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function GuildList(props: { list: Guild[]; joined: boolean }) {
|
||||
return (
|
||||
<div>
|
||||
<Show
|
||||
when={props.list.length}
|
||||
fallback={<div class="box flex-row centered">Nothing here</div>}
|
||||
>
|
||||
<For each={props.list}>
|
||||
{(guild) => {
|
||||
return (
|
||||
<a
|
||||
href={
|
||||
props.joined
|
||||
? `/config/${guild.id}`
|
||||
: `https://discord.com/api/oauth2/authorize?client_id=${import.meta.env.VITE_DISCORD_CLIENT_ID}&permissions=${import.meta.env.VITE_DISCORD_OAUTH2_PERMISSIONS}&scope=bot&guild_id=${guild.id}`
|
||||
}
|
||||
class="box effect flex-row centered"
|
||||
>
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{guild.name}</h1>
|
||||
<FontAwesomeIcon
|
||||
icon={props.joined ? faWrench : faPlusCircle}
|
||||
size="xl"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default index;
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
section,
|
||||
a {
|
||||
.box {
|
||||
transition: 0.3s;
|
||||
transition-timing-function: ease-out;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
@ -28,28 +27,30 @@
|
|||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
padding: 0 1rem;
|
||||
&.effect {
|
||||
svg {
|
||||
padding: 0 1rem;
|
||||
|
||||
path {
|
||||
transition: 0.1s;
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
path {
|
||||
transition: 0.1s;
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: 1s;
|
||||
transition-delay: 0.1s;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
|
||||
svg path {
|
||||
transition: 0.5s;
|
||||
&:hover {
|
||||
transition: 1s;
|
||||
transition-delay: 0.1s;
|
||||
transition-timing-function: ease-out;
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
|
||||
svg path {
|
||||
transition: 0.5s;
|
||||
transition-delay: 0.1s;
|
||||
transition-timing-function: ease-out;
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue