diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1c5d8b2..3e8fecd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,11 +1,13 @@ import { useMemo, useState } from 'react'; import type { Room } from 'livekit-client'; import { joinPod } from './lib/pod.js'; -import { TEAMS } from './lib/teams.js'; +import { TEAMS, teamById } from './lib/teams.js'; +import { TeamCard } from './components/TeamCard.js'; +import { PodView } from './components/PodView.js'; export default function App() { const [teamId, setTeamId] = useState(TEAMS[0]!.id); - const team = useMemo(() => TEAMS.find((t) => t.id === teamId) ?? TEAMS[0]!, [teamId]); + const team = useMemo(() => teamById(teamId), [teamId]); const [member, setMember] = useState(team.members[0]!); const [room, setRoom] = useState(null); const [joined, setJoined] = useState(false); @@ -15,8 +17,8 @@ export default function App() { function handleTeamChange(id: string) { setTeamId(id); - const next = TEAMS.find((t) => t.id === id); - if (next) setMember(next.members[0]!); + const next = teamById(id); + if (!next.members.includes(member)) setMember(next.members[0]!); } async function handleJoin() { @@ -35,72 +37,68 @@ export default function App() { } } + function handleLeave() { + room?.disconnect(); + setRoom(null); + setJoined(false); + } + return ( -
-
-

🛰️ PodMan

-

- Join a pod and share your screen — PodMan watches for collisions before you push. -

+
+
+ 🛰️ +
+

PodMan

+

+ The teammate that sees what git can't — collisions caught before you push. +

+
{joined ? ( -
-

- {member} connected to “{team.name}”. -

-

- {room ? 'Sharing screen + mic. PodMan is watching.' : 'Pod joined.'} -

- {devMode && ( -

- DEV MODE — LiveKit not configured, so screen capture is off. Set LIVEKIT_* in the - backend .env (and serve over HTTPS) for a real join. -

- )} -
+ ) : ( -
- +
+ - + + {connecting ? 'Joining…' : 'Join pod'} + + {error &&

{error}

} - +
)} - + ); } diff --git a/frontend/src/components/Avatar.tsx b/frontend/src/components/Avatar.tsx new file mode 100644 index 0000000..f5217f9 --- /dev/null +++ b/frontend/src/components/Avatar.tsx @@ -0,0 +1,36 @@ +function colorFor(name: string): string { + let h = 0; + for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) % 360; + return `hsl(${h} 55% 42%)`; +} + +function initials(name: string): string { + return name + .split(/\s+/) + .map((w) => w[0] ?? '') + .join('') + .slice(0, 2) + .toUpperCase(); +} + +export function Avatar({ + name, + size = 36, + ring = false, +}: { + name: string; + size?: number; + ring?: boolean; +}) { + return ( +
+ {initials(name)} +
+ ); +} diff --git a/frontend/src/components/PodView.tsx b/frontend/src/components/PodView.tsx new file mode 100644 index 0000000..d885cb9 --- /dev/null +++ b/frontend/src/components/PodView.tsx @@ -0,0 +1,84 @@ +import type { DemoTeam } from '../lib/teams.js'; +import { Avatar } from './Avatar.js'; + +export function PodView({ + team, + me, + devMode, + onLeave, +}: { + team: DemoTeam; + me: string; + devMode: boolean; + onLeave: () => void; +}) { + return ( +
+
+
+

{team.name}

+

{team.repo}

+
+ +
+ + {devMode && ( +

+ DEV MODE — LiveKit not configured / insecure context, so screen capture is off. +

+ )} + +
+ {/* Pod members */} +
+

In this pod

+
+ {team.members.map((m) => { + const isMe = m === me; + return ( +
+ +
+

{m}

+

{isMe ? 'you' : 'in pod'}

+
+
+ ); + })} +
+
+ + {/* PodMan panel */} + +
+
+ ); +} diff --git a/frontend/src/components/TeamCard.tsx b/frontend/src/components/TeamCard.tsx new file mode 100644 index 0000000..e948ca5 --- /dev/null +++ b/frontend/src/components/TeamCard.tsx @@ -0,0 +1,37 @@ +import type { DemoTeam } from '../lib/teams.js'; +import { Avatar } from './Avatar.js'; + +export function TeamCard({ + team, + selected, + onSelect, +}: { + team: DemoTeam; + selected: boolean; + onSelect: (id: string) => void; +}) { + return ( + + ); +} diff --git a/frontend/src/lib/teams.ts b/frontend/src/lib/teams.ts index 175dff1..abba810 100644 --- a/frontend/src/lib/teams.ts +++ b/frontend/src/lib/teams.ts @@ -6,6 +6,7 @@ export interface DemoTeam { id: string; name: string; repo: string; + description: string; members: string[]; } @@ -14,18 +15,25 @@ export const TEAMS: DemoTeam[] = [ id: 'demo-pod', name: 'Demo Pod', repo: 'karti-ai/podman', + description: 'The full crew — used for the live demo.', members: ['Karti', 'Yahya', 'Ramis', 'Zander', 'Shakthi'], }, { id: 'frontend-squad', name: 'Frontend Squad', repo: 'karti-ai/podman', + description: 'PWA, capture, and PodMan card UI.', members: ['Karti', 'Zander'], }, { id: 'backend-squad', name: 'Backend Squad', repo: 'karti-ai/podman', + description: 'LiveKit agent, vision, collision detector.', members: ['Yahya', 'Ramis'], }, ]; + +export function teamById(id: string): DemoTeam { + return TEAMS.find((t) => t.id === id) ?? TEAMS[0]!; +}