@@ -6,6 +6,47 @@ import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react";
66import { usePermissions } from "../../hooks/use-permissions" ;
77import { OnboardingContainer , type OnboardingNext } from "./shared" ;
88
9+ type PermissionBlockProps = {
10+ name : string ;
11+ status : string | undefined ;
12+ description : { authorized : string ; unauthorized : string } ;
13+ isPending : boolean ;
14+ onAction : ( ) => void ;
15+ } ;
16+
17+ function PermissionBlock ( { name, status, description, isPending, onAction } : PermissionBlockProps ) {
18+ const isAuthorized = status === "authorized" ;
19+
20+ return (
21+ < div className = "flex items-center justify-between" >
22+ < div className = "flex flex-col gap-2" >
23+ < div
24+ className = { cn ( [
25+ "flex items-center gap-2" ,
26+ ! isAuthorized ? "text-red-500" : "text-neutral-900" ,
27+ ] ) }
28+ >
29+ { ! isAuthorized && < AlertCircleIcon className = "size-4" /> }
30+ < span className = "text-base font-medium" > { name } </ span >
31+ </ div >
32+ < p className = "text-sm text-neutral-500" >
33+ { isAuthorized ? description . authorized : description . unauthorized }
34+ </ p >
35+ </ div >
36+ < Button
37+ variant = { isAuthorized ? "outline" : "default" }
38+ size = "icon"
39+ onClick = { onAction }
40+ disabled = { isPending || isAuthorized }
41+ className = { cn ( [ "size-8" , isAuthorized && "bg-stone-100 text-stone-800" ] ) }
42+ aria-label = { isAuthorized ? `${ name } permission granted` : `Request ${ name . toLowerCase ( ) } permission` }
43+ >
44+ { isAuthorized ? < CheckIcon className = "size-5" /> : < ArrowRightIcon className = "size-5" /> }
45+ </ Button >
46+ </ div >
47+ ) ;
48+ }
49+
950type PermissionsProps = {
1051 onNext : OnboardingNext ;
1152} ;
@@ -30,99 +71,29 @@ export function Permissions({ onNext }: PermissionsProps) {
3071 return (
3172 < OnboardingContainer title = "Quick permissions before we begin" >
3273 < div className = "flex flex-col gap-4" >
33- < div className = "flex items-center justify-between" >
34- < div className = "gap-2" >
35- < div
36- className = { cn ( [
37- "flex items-center gap-2" ,
38- micPermissionStatus . data !== "authorized" ? "text-red-500" : "text-neutral-900" ,
39- ] ) }
40- >
41- { micPermissionStatus . data !== "authorized" && < AlertCircleIcon className = "size-4" /> }
42- < span className = "text-base font-medium" > Microphone</ span >
43- </ div >
44- < p className = "text-sm text-neutral-500" >
45- { micPermissionStatus . data === "authorized" ? "Good to go :)" : "To capture your voice" }
46- </ p >
47- </ div >
48- < Button
49- variant = { micPermissionStatus . data === "authorized" ? "outline" : "default" }
50- size = "icon"
51- onClick = { handleMicPermissionAction }
52- disabled = { micPermission . isPending || micPermissionStatus . data === "authorized" }
53- className = { cn ( [ "size-8" , micPermissionStatus . data === "authorized" && "bg-stone-100 text-stone-800" ] ) }
54- >
55- { micPermissionStatus . data === "authorized"
56- ? < CheckIcon className = "size-5" />
57- : < ArrowRightIcon className = "size-5" /> }
58- </ Button >
59- </ div >
74+ < PermissionBlock
75+ name = "Microphone"
76+ status = { micPermissionStatus . data }
77+ description = { { authorized : "Good to go :)" , unauthorized : "To capture your voice" } }
78+ isPending = { micPermission . isPending }
79+ onAction = { handleMicPermissionAction }
80+ />
6081
61- < div className = "flex items-center justify-between" >
62- < div className = "gap-2" >
63- < div
64- className = { cn ( [
65- "flex items-center gap-2" ,
66- systemAudioPermissionStatus . data !== "authorized" ? "text-red-500" : "text-neutral-900" ,
67- ] ) }
68- >
69- { systemAudioPermissionStatus . data !== "authorized" && < AlertCircleIcon className = "size-4" /> }
70- < span className = "text-base font-medium" > System audio</ span >
71- </ div >
72- < p className = "text-sm text-neutral-500" >
73- { systemAudioPermissionStatus . data === "authorized"
74- ? "Good to go :)"
75- : "To capture what other people are saying" }
76- </ p >
77- </ div >
78- < Button
79- variant = { systemAudioPermissionStatus . data === "authorized" ? "outline" : "default" }
80- size = "icon"
81- onClick = { handleSystemAudioPermissionAction }
82- disabled = { systemAudioPermission . isPending || systemAudioPermissionStatus . data === "authorized" }
83- className = { cn ( [
84- "size-8" ,
85- systemAudioPermissionStatus . data === "authorized" && "bg-stone-100 text-stone-800" ,
86- ] ) }
87- >
88- { systemAudioPermissionStatus . data === "authorized"
89- ? < CheckIcon className = "size-5" />
90- : < ArrowRightIcon className = "size-5" /> }
91- </ Button >
92- </ div >
82+ < PermissionBlock
83+ name = "System audio"
84+ status = { systemAudioPermissionStatus . data }
85+ description = { { authorized : "Good to go :)" , unauthorized : "To capture what other people are saying" } }
86+ isPending = { systemAudioPermission . isPending }
87+ onAction = { handleSystemAudioPermissionAction }
88+ />
9389
94- < div className = "flex items-center justify-between" >
95- < div className = "gap-2" >
96- < div
97- className = { cn ( [
98- "flex items-center gap-2" ,
99- accessibilityPermissionStatus . data !== "authorized" ? "text-red-500" : "text-neutral-900" ,
100- ] ) }
101- >
102- { accessibilityPermissionStatus . data !== "authorized" && < AlertCircleIcon className = "size-4" /> }
103- < span className = "text-base font-medium" > Accessibility</ span >
104- </ div >
105- < p className = "text-sm text-neutral-500" >
106- { accessibilityPermissionStatus . data === "authorized"
107- ? "Good to go :)"
108- : "To sync mic inputs & mute from meetings" }
109- </ p >
110- </ div >
111- < Button
112- variant = { accessibilityPermissionStatus . data === "authorized" ? "outline" : "default" }
113- size = "icon"
114- onClick = { handleAccessibilityPermissionAction }
115- disabled = { accessibilityPermission . isPending || accessibilityPermissionStatus . data === "authorized" }
116- className = { cn ( [
117- "size-8" ,
118- accessibilityPermissionStatus . data === "authorized" && "bg-stone-100 text-stone-800" ,
119- ] ) }
120- >
121- { accessibilityPermissionStatus . data === "authorized"
122- ? < CheckIcon className = "size-5" />
123- : < ArrowRightIcon className = "size-5" /> }
124- </ Button >
125- </ div >
90+ < PermissionBlock
91+ name = "Accessibility"
92+ status = { accessibilityPermissionStatus . data }
93+ description = { { authorized : "Good to go :)" , unauthorized : "To sync mic inputs & mute from meetings" } }
94+ isPending = { accessibilityPermission . isPending }
95+ onAction = { handleAccessibilityPermissionAction }
96+ />
12697 </ div >
12798
12899 < Button onClick = { ( ) => onNext ( ) } className = "w-full" disabled = { ! allPermissionsGranted } >
0 commit comments