diff --git a/api/src/routes/device-socket.ts b/api/src/routes/device-socket.ts
index 6190c6a..b4dfb93 100644
--- a/api/src/routes/device-socket.ts
+++ b/api/src/routes/device-socket.ts
@@ -276,13 +276,29 @@ export const deviceSocketApp = new Elysia().group("/dev-socket", (app) =>
export const deviceClaimApp = new Elysia()
.use(betterAuthElysia)
.group("/devices", (app) =>
- app.post(
- "/:deviceId/connect",
- async ({ user, params }) => {
- await claimDeviceForUser(params.deviceId, user.id);
- sendDeviceEvent(params.deviceId, { type: "device_connected" });
- return { ok: true, deviceId: params.deviceId, userId: user.id };
- },
- { auth: true },
- ),
+ app
+ .get(
+ "/mine",
+ async ({ user }) => {
+ const devices = await db
+ .select({
+ id: deviceConnection.id,
+ lastSeen: deviceConnection.lastSeen,
+ })
+ .from(deviceConnection)
+ .where(eq(deviceConnection.userId, user.id));
+
+ return { devices };
+ },
+ { auth: true },
+ )
+ .post(
+ "/:deviceId/connect",
+ async ({ user, params }) => {
+ await claimDeviceForUser(params.deviceId, user.id);
+ sendDeviceEvent(params.deviceId, { type: "device_connected" });
+ return { ok: true, deviceId: params.deviceId, userId: user.id };
+ },
+ { auth: true },
+ ),
);
diff --git a/web/src/components/device-choice.tsx b/web/src/components/device-choice.tsx
new file mode 100644
index 0000000..5a5aa4f
--- /dev/null
+++ b/web/src/components/device-choice.tsx
@@ -0,0 +1,67 @@
+import { useQuery } from "@tanstack/react-query";
+import { useState } from "react";
+import { Button } from "#/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "#/components/ui/card";
+import { useParty } from "#/hooks/use-party";
+import { useUser } from "#/hooks/user";
+import { client } from "#/lib/eden";
+
+const CONTROLLER_DEVICE_ID = "esp32-1";
+
+export function DeviceChoice() {
+ const { user } = useUser();
+ const { party } = useParty();
+ const [dismissed, setDismissed] = useState(false);
+ const [isClaiming, setIsClaiming] = useState(false);
+
+ const { data, isLoading } = useQuery({
+ queryKey: ["devices", "mine"],
+ queryFn: () => client.api.devices.mine.get(),
+ enabled: Boolean(user && !party && !dismissed),
+ });
+
+ if (!user || party || dismissed || isLoading) return null;
+
+ const devices = data?.data?.devices ?? [];
+ if (devices.length > 0) return null;
+
+ return (
+
+
+ Play with the controller?
+
+ Claim {CONTROLLER_DEVICE_ID} for this session, or continue on this
+ device only.
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx
index 796be4c..35081f9 100644
--- a/web/src/routes/index.tsx
+++ b/web/src/routes/index.tsx
@@ -1,4 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
+import { DeviceChoice } from "#/components/device-choice";
import { PartyView } from "#/components/party/party-view";
import { PartyQr } from "#/components/party-qr";
import { StartParty } from "#/components/start-party";
@@ -16,8 +17,9 @@ function App() {
return (
+
{!user?.lastSyncAt && }
- {user && party?.data?.status != "running" && }
+ {user && party?.data?.status !== "running" && }
{party && !party.data && members.length > 1 && }
{party?.data && }