diff --git a/apps/web/package.json b/apps/web/package.json
index a2b2347c..7d2ade8d 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -66,6 +66,7 @@
"next": "^14.2.32",
"next-auth": "5.0.0-beta.29",
"python-bridge": "^1.1.0",
+ "qrcode.react": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-resizable-layout": "^0.7.3",
diff --git a/apps/web/src/components/common/QRCodeButton.tsx b/apps/web/src/components/common/QRCodeButton.tsx
new file mode 100644
index 00000000..ab25ebab
--- /dev/null
+++ b/apps/web/src/components/common/QRCodeButton.tsx
@@ -0,0 +1,198 @@
+import * as Popover from '@radix-ui/react-popover'
+import type { CSSProperties } from 'react'
+import { useState } from 'react'
+import { QRCodeSVG } from 'qrcode.react'
+import { useClipboard } from '@/hooks/useClipboard'
+
+export interface QRCodeButtonProps {
+ /**
+ * The URL to encode in the QR code and display
+ */
+ url: string
+
+ /**
+ * Optional custom styles for the trigger button
+ */
+ style?: CSSProperties
+}
+
+/**
+ * Button that opens a popover with a QR code for the share link
+ * Includes the URL text with a copy button
+ */
+export function QRCodeButton({ url, style }: QRCodeButtonProps) {
+ const [open, setOpen] = useState(false)
+ const { copied, copy } = useClipboard()
+
+ const buttonStyles: CSSProperties = {
+ width: '100%',
+ cursor: 'pointer',
+ transition: 'all 0.2s ease',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: '8px',
+ marginBottom: '6px',
+ border: '2px solid rgba(251, 146, 60, 0.4)',
+ background: 'linear-gradient(135deg, rgba(251, 146, 60, 0.2), rgba(251, 146, 60, 0.3))',
+ borderRadius: '8px',
+ padding: '10px 16px',
+ fontSize: '13px',
+ fontWeight: '600',
+ color: 'rgba(253, 186, 116, 1)',
+ ...style,
+ }
+
+ const hoverStyles: CSSProperties = {
+ background: 'linear-gradient(135deg, rgba(251, 146, 60, 0.3), rgba(251, 146, 60, 0.4))',
+ borderColor: 'rgba(251, 146, 60, 0.6)',
+ }
+
+ return (
+
+
+
+
+
+
+
+ {/* Title */}
+
+ Scan to Join
+
+
+ {/* QR Code */}
+
+
+
+
+ {/* URL with copy button */}
+
+
+ Or copy link:
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/web/src/components/nav/RoomShareButtons.tsx b/apps/web/src/components/nav/RoomShareButtons.tsx
index abb4a78a..c5e73da0 100644
--- a/apps/web/src/components/nav/RoomShareButtons.tsx
+++ b/apps/web/src/components/nav/RoomShareButtons.tsx
@@ -1,4 +1,5 @@
import { CopyButton } from '@/components/common/CopyButton'
+import { QRCodeButton } from '@/components/common/QRCodeButton'
export interface RoomShareButtonsProps {
/**
@@ -41,6 +42,8 @@ export function RoomShareButtons({ joinCode, shareUrl }: RoomShareButtonsProps)
}
copiedLabel="Link Copied!"
/>
+
+
>
)
}