Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dcd6955
create scaffolding for embedg-service
merlinfuchs Nov 12, 2025
365bf4f
implement all postgres stores
merlinfuchs Nov 12, 2025
e461a22
hook up stateway to disgo
merlinfuchs Nov 14, 2025
3be6600
continue service implementation
merlinfuchs Nov 14, 2025
205f8fc
implement webhook manager
merlinfuchs Nov 14, 2025
4ffaf7f
copy and refactor api handlers
merlinfuchs Nov 14, 2025
5eef94f
connect the dots MVP
merlinfuchs Nov 15, 2025
89fb5f4
fixes
merlinfuchs Nov 15, 2025
81b29d2
hook up action handler
merlinfuchs Nov 15, 2025
086b68a
replace zerolog with slog
merlinfuchs Nov 15, 2025
9dbceb3
remove embedg-server imports
merlinfuchs Nov 15, 2025
eb7f4cc
remove viper usage
merlinfuchs Nov 15, 2025
ff45831
start hooking up custom bots to gateway
merlinfuchs Nov 16, 2025
ea135d6
fix interactions of http
merlinfuchs Nov 16, 2025
8773496
surface interaction errors to users
merlinfuchs Nov 16, 2025
4317a92
fix and improvements
merlinfuchs Nov 16, 2025
2a6e47e
start working through remaining TODOs
merlinfuchs Nov 16, 2025
b4f3a53
start implementing missing commands
merlinfuchs Nov 18, 2025
0bf6c45
implement embed command
merlinfuchs Nov 26, 2025
6a37651
various fixes and improvements
merlinfuchs Nov 26, 2025
867c0b8
make some variables easier to access
merlinfuchs Nov 26, 2025
e147e03
allow non-snowflake SKU IDs
merlinfuchs Nov 28, 2025
4e9e9a4
hook up more efficient state cache methods to access manager
merlinfuchs Nov 28, 2025
c1776cb
fix some errors and update stateway-lib
merlinfuchs Nov 29, 2025
1353a71
handle more errors gracefully
merlinfuchs Nov 30, 2025
9f25767
add loading indicators
merlinfuchs Nov 30, 2025
57ae48d
populate channel select even when incomplete
merlinfuchs Jan 11, 2026
e6fb254
minor
merlinfuchs Jan 11, 2026
9e321e0
add migration cmd to embedg-service
merlinfuchs Jan 11, 2026
bd4d08a
add back analytics
merlinfuchs Jan 11, 2026
63f2393
minor
merlinfuchs Feb 13, 2026
76d8191
add proxy support
merlinfuchs Feb 14, 2026
30824eb
fix redirect attack after auth
merlinfuchs Feb 14, 2026
80765e6
replace swetrix with openpanel
merlinfuchs Apr 5, 2026
607f07c
fix getOauthRedirectURL regression
merlinfuchs Apr 5, 2026
90f8e92
restore components v2 flag
merlinfuchs Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,710 changes: 3,710 additions & 0 deletions embedg-app/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions embedg-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@emoji-mart/react": "^1.1.1",
"@formkit/auto-animate": "^1.0.0-beta.6",
"@heroicons/react": "^2.0.17",
"@openpanel/web": "^1.3.0",
"@types/debounce": "^1.2.1",
"@types/react-twemoji": "^0.4.1",
"@uiw/codemirror-theme-github": "^4.19.11",
Expand Down
27 changes: 27 additions & 0 deletions embedg-app/src/components/AnalyticsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OpenPanel } from "@openpanel/web";
import { useEffect } from "react";
import { useUserQuery } from "../api/queries";

export const op = new OpenPanel({
clientId: "f4dd2f20-2d9f-4ff5-9486-6d88b5326fc7",
apiUrl: "https://analytics.vaven.io/api",
trackScreenViews: true,
trackOutgoingLinks: true,
});

export default function AnalyticsProvider() {
const { data } = useUserQuery();

const user = data?.success ? data.data : null;

useEffect(() => {
if (user?.id) {
op.identify({
profileId: user.id,
firstName: user.name,
});
}
}, [user?.id, op.identify]);

return null;
}
38 changes: 36 additions & 2 deletions embedg-app/src/components/ChannelSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
ArrowPathIcon,
ChatBubbleLeftRightIcon,
ChevronDownIcon,
} from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useGuildChannelsQuery } from "../api/queries";
import ClickOutsideHandler from "./ClickOutsideHandler";
import { useToasts } from "../util/toasts";

interface Props {
guildId: string | null;
Expand All @@ -27,6 +29,7 @@ function canSelectChannelType(type: number) {

export function ChannelSelect({ guildId, channelId, onChange }: Props) {
const { data } = useGuildChannelsQuery(guildId);
const toast = useToasts((state) => state.create);

const inputRef = useRef<HTMLInputElement>(null);

Expand All @@ -48,6 +51,16 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
setOpen(false);
}

useEffect(() => {
if (data?.success === false) {
toast({
title: "Failed to load channels",
message: data.error.message,
type: "error",
});
}
}, [data]);

const channels = useMemo(() => {
const rawChannels = data?.success ? data.data : [];

Expand All @@ -56,6 +69,7 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
a.position === b.position && a.type === 4 ? 1 : a.position - b.position
);

const added = new Set<string>();
const res = [];

// This is really inefficient but it should be fine because there are never more than 500 channels
Expand All @@ -70,6 +84,7 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
rootChannel.type === 15
) {
// text, category, announcement, stage, forum
added.add(rootChannel.id);
res.push({
...rootChannel,
level: 0,
Expand All @@ -93,6 +108,7 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
childChannel.type === 15
) {
// text, announcement, announcement thread, text thread, stage, forum
added.add(childChannel.id);
res.push({
...childChannel,
level: 1,
Expand All @@ -112,6 +128,7 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
childThread.type === 12
) {
// announcement thread, text thread
added.add(childThread.id);
res.push({
...childThread,
level: 2,
Expand All @@ -125,6 +142,18 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
}
}

for (const channel of rawChannels) {
if (added.has(channel.id)) continue;
res.push({
...channel,
level: 2,
canSelect:
channel.user_access &&
channel.bot_access &&
canSelectChannelType(channel.type),
});
}

return res;
}, [data]);

Expand Down Expand Up @@ -159,7 +188,12 @@ export function ChannelSelect({ guildId, channelId, onChange }: Props) {
)}
/>
<div className={open ? "md:hidden" : ""}>
{channel ? (
{!data ? (
<div className="flex items-center space-x-2">
<ArrowPathIcon className="h-5 w-5 text-gray-300 animate-spin" />
<div className="text-gray-400">Loading...</div>
</div>
) : channel ? (
<div className="flex items-center space-x-2 cursor-pointer w-full">
{channel.type === 15 ? (
<ChatBubbleLeftRightIcon className="h-5 w-5 text-gray-300" />
Expand Down
20 changes: 19 additions & 1 deletion embedg-app/src/components/GuildSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useEffect, useMemo, useState } from "react";
import { useGuildsQuery } from "../api/queries";
import { guildIconUrl } from "../discord/cdn";
import ClickOutsideHandler from "./ClickOutsideHandler";
import { useToasts } from "../util/toasts";
import { ArrowPathIcon } from "@heroicons/react/24/outline";

interface Props {
guildId: string | null;
Expand All @@ -12,6 +14,17 @@ interface Props {

export default function GuildSelect({ guildId, onChange }: Props) {
const { data: guilds, isLoading } = useGuildsQuery();
const toast = useToasts((state) => state.create);

useEffect(() => {
if (guilds?.success === false) {
toast({
title: "Failed to load guilds",
message: guilds.error.message,
type: "error",
});
}
}, [guilds]);

const guild = useMemo(
() => guilds?.success && guilds.data.find((g) => g.id === guildId),
Expand Down Expand Up @@ -56,7 +69,12 @@ export default function GuildSelect({ guildId, onChange }: Props) {
role="button"
className="flex-auto"
>
{guild ? (
{!guilds ? (
<div className="flex items-center space-x-2">
<ArrowPathIcon className="h-5 w-5 text-gray-300 animate-spin" />
<div className="text-gray-400">Loading...</div>
</div>
) : guild ? (
<div className="flex items-center space-x-2 cursor-pointer w-full">
<img
src={guildIconUrl(guild)}
Expand Down
2 changes: 2 additions & 0 deletions embedg-app/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { QueryClientProvider } from "react-query";
import queryClient from "./api/client";
import { BrowserRouter } from "react-router-dom";
import { baseUrl } from "./util/url";
import AnalyticsProvider from "./components/AnalyticsProvider";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter basename={baseUrl}>
<App />
<AnalyticsProvider />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
Expand Down
Loading