1- import { useEffect } from 'react' ;
1+ import { useEffect , useState } from 'react' ;
2+ import { useNavigate } from 'react-router-dom' ;
23import { useVoiceStore , useVoicesDataStore } from '../store' ;
34import { useAWSServices } from '../hooks/useAWSServices' ;
45import { VoiceSelect } from '../components/shared/VoiceSelect' ;
@@ -16,14 +17,19 @@ function getUserLanguage() {
1617 return ( navigator . language ?? 'en' ) . substring ( 0 , 2 ) ;
1718}
1819
20+ type View = 'landing' | 'streamer' | 'viewer' ;
21+
1922export default function LoginPage ( ) {
23+ const navigate = useNavigate ( ) ;
2024 const { initialized, error } = useAWSServices ( ) ;
2125 const {
2226 systemVoice, systemVoiceOption, defaultChatterVoice, defaultChatterVoiceOption,
2327 dstLangSelect, setVoice,
2428 } = useVoiceStore ( ) ;
2529 const { } = useVoicesDataStore ( ) ;
2630
31+ const [ view , setView ] = useState < View > ( 'landing' ) ;
32+
2733 useEffect ( ( ) => {
2834 if ( ! dstLangSelect ) {
2935 setVoice ( 'dstLangSelect' , getUserLanguage ( ) ) ;
@@ -37,87 +43,181 @@ export default function LoginPage() {
3743 } ;
3844
3945 return (
40- < div className = "container-fluid min-vh-100 d-flex align-items-center justify-content-center" style = { { background : '#1a1a2e' } } >
41- < div className = "card bg-dark text-white p-4" style = { { maxWidth : 600 , width : '100%' } } >
42- < h2 className = "text-center mb-1" > tts.bot</ h2 >
43- < p className = "text-center text-muted mb-4" > by Security_Live</ p >
44-
45- { ! initialized && ! error && (
46- < div className = "text-center py-4" >
47- < div className = "spinner-border text-primary" role = "status" />
48- < p className = "mt-2 text-muted" > Initializing AWS services…</ p >
49- </ div >
50- ) }
46+ < div
47+ style = { {
48+ backgroundColor : '#000' ,
49+ backgroundImage : 'url(/bg.jpg)' ,
50+ backgroundPosition : 'center center' ,
51+ backgroundSize : 'cover' ,
52+ fontFamily : '"Poppins", "Helvetica Neue Light", Helvetica, Arial, sans-serif' ,
53+ color : 'white' ,
54+ display : 'flex' ,
55+ justifyContent : 'center' ,
56+ alignItems : 'center' ,
57+ minHeight : '100vh' ,
58+ margin : 0 ,
59+ } }
60+ >
61+ < div style = { { maxWidth : 550 , width : '100%' , textAlign : 'center' , padding : '0 20px' } } >
62+
63+ { /* Logo */ }
64+ < div style = { {
65+ fontFamily : 'Poppins, sans-serif' ,
66+ marginBottom : 20 ,
67+ fontSize : 84 ,
68+ lineHeight : '80px' ,
69+ fontWeight : 900 ,
70+ textShadow : '-6px 8px 12px #000' ,
71+ } } >
72+ < img src = "/profile_image.png" alt = "tts.bot" style = { { height : 70 , marginBottom : - 10 } } />
73+ { ' ' } tts.bot for < span style = { { color : '#b36aff' } } > Twitch</ span >
74+ </ div >
75+
76+ < h1 style = { { textShadow : '6px 8px 4px rgba(0,0,0,0.25)' } } >
77+ Access requires a{ ' ' }
78+ < span style = { { color : '#b36aff' } } >
79+ < a style = { { color : '#b36aff' } } href = "https://www.twitch.tv/signup" target = "_blank" rel = "noreferrer" >
80+ Twitch
81+ </ a >
82+ </ span > { ' ' }
83+ account.
84+ </ h1 >
5185
52- { error && (
53- < div className = "alert alert-danger" > Failed to connect to AWS: { error . message } </ div >
86+ { /* ── Landing: Streamer / Viewer choice ── */ }
87+ { view === 'landing' && (
88+ < div style = { { display : 'flex' , flexDirection : 'column' , gap : 10 } } >
89+ < button className = "login-button btn" onClick = { ( ) => setView ( 'streamer' ) } >
90+ Streamer
91+ </ button >
92+ < button className = "login-button btn" onClick = { ( ) => navigate ( '/viewer' ) } >
93+ Viewer
94+ </ button >
95+ </ div >
5496 ) }
5597
56- { initialized && (
98+ { /* ── Streamer options ── */ }
99+ { view === 'streamer' && (
57100 < >
58- < div className = "row g-3 mb-3" >
59- < div className = "col-md-6" >
60- < VoiceSelect
61- label = "System Voice"
62- voiceId = { systemVoice }
63- voiceOption = { systemVoiceOption }
64- onVoiceChange = { ( v ) => setVoice ( 'systemVoice' , v ) }
65- onOptionChange = { ( v ) => setVoice ( 'systemVoiceOption' , v ) }
66- />
67- </ div >
68- < div className = "col-md-6" >
69- < VoiceSelect
70- label = "Default Chatter Voice"
71- voiceId = { defaultChatterVoice }
72- voiceOption = { defaultChatterVoiceOption }
73- onVoiceChange = { ( v ) => setVoice ( 'defaultChatterVoice' , v ) }
74- onOptionChange = { ( v ) => setVoice ( 'defaultChatterVoiceOption' , v ) }
75- />
101+ < h2 style = { { fontWeight : 100 } } > Streamer Options</ h2 >
102+
103+ { ! initialized && ! error && (
104+ < div style = { { padding : '20px 0' } } >
105+ < div className = "spinner-border text-light" role = "status" />
106+ < p className = "mt-2" style = { { opacity : 0.7 } } > Initializing AWS services…</ p >
76107 </ div >
77- </ div >
108+ ) }
78109
79- < div className = "mb-4" >
80- < label className = "form-label" > Translation Language</ label >
81- < select
82- className = "form-select form-select-sm bg-dark text-white"
83- value = { dstLangSelect }
84- onChange = { ( e ) => setVoice ( 'dstLangSelect' , e . target . value ) }
85- >
86- < option value = "" > Choose your language</ option >
87- < option value = "en" > English</ option >
88- < option value = "es" > Spanish</ option >
89- < option value = "fr" > French</ option >
90- < option value = "de" > German</ option >
91- < option value = "pt" > Portuguese</ option >
92- < option value = "ja" > Japanese</ option >
93- < option value = "ko" > Korean</ option >
94- < option value = "zh" > Chinese</ option >
95- < option value = "ar" > Arabic</ option >
96- < option value = "ru" > Russian</ option >
97- < option value = "it" > Italian</ option >
98- < option value = "pl" > Polish</ option >
99- < option value = "nl" > Dutch</ option >
100- < option value = "sv" > Swedish</ option >
101- < option value = "tr" > Turkish</ option >
102- </ select >
103- </ div >
110+ { error && (
111+ < div className = "alert alert-danger" > Failed to connect to AWS: { error . message } </ div >
112+ ) }
113+
114+ { initialized && (
115+ < dl style = { { textAlign : 'left' } } >
116+ < div style = { { marginBottom : 16 } } >
117+ < VoiceSelect
118+ label = "System Voice"
119+ voiceId = { systemVoice }
120+ voiceOption = { systemVoiceOption }
121+ onVoiceChange = { ( v ) => setVoice ( 'systemVoice' , v ) }
122+ onOptionChange = { ( v ) => setVoice ( 'systemVoiceOption' , v ) }
123+ />
124+ </ div >
125+ < div style = { { marginBottom : 16 } } >
126+ < VoiceSelect
127+ label = "Default Chatter Voice"
128+ voiceId = { defaultChatterVoice }
129+ voiceOption = { defaultChatterVoiceOption }
130+ onVoiceChange = { ( v ) => setVoice ( 'defaultChatterVoice' , v ) }
131+ onOptionChange = { ( v ) => setVoice ( 'defaultChatterVoiceOption' , v ) }
132+ />
133+ </ div >
134+ < div style = { { marginBottom : 16 } } >
135+ < label style = { { display : 'block' , marginBottom : 6 } } > Translation Language</ label >
136+ < select
137+ style = { {
138+ width : '100%' , padding : 12 , borderRadius : 10 ,
139+ border : '2px solid #b36aff' , background : '#666' , color : 'white' ,
140+ boxSizing : 'border-box' ,
141+ } }
142+ value = { dstLangSelect }
143+ onChange = { ( e ) => setVoice ( 'dstLangSelect' , e . target . value ) }
144+ >
145+ < option value = "" > Choose your language</ option >
146+ < option value = "en" > English</ option >
147+ < option value = "es" > Spanish</ option >
148+ < option value = "fr" > French</ option >
149+ < option value = "de" > German</ option >
150+ < option value = "pt" > Portuguese</ option >
151+ < option value = "ja" > Japanese</ option >
152+ < option value = "ko" > Korean</ option >
153+ < option value = "zh" > Chinese</ option >
154+ < option value = "ar" > Arabic</ option >
155+ < option value = "ru" > Russian</ option >
156+ < option value = "it" > Italian</ option >
157+ < option value = "pl" > Polish</ option >
158+ < option value = "nl" > Dutch</ option >
159+ < option value = "sv" > Swedish</ option >
160+ < option value = "tr" > Turkish</ option >
161+ </ select >
162+ </ div >
163+ </ dl >
164+ ) }
165+
166+ < h2 style = { { fontWeight : 100 } } > Choose your desired capabilities and click authenticate.</ h2 >
104167
105- < div className = "d-grid gap-2" >
168+ < dl style = { { textAlign : 'left' , marginBottom : 16 } } >
169+ < dd style = { { margin : '10px 0' } } >
170+ < input type = "radio" id = "moderation" name = "capability" defaultChecked readOnly />
171+ < label htmlFor = "moderation" style = { { marginLeft : 8 } } > Moderation (Recommended)</ label >
172+ </ dd >
173+ < dd style = { { margin : '10px 0' } } >
174+ < input type = "radio" id = "chat" name = "capability" readOnly />
175+ < label htmlFor = "chat" style = { { marginLeft : 8 } } > Basic Chat</ label >
176+ </ dd >
177+ </ dl >
178+
179+ < button
180+ className = "login-button btn"
181+ onClick = { ( ) => handleLogin ( true ) }
182+ disabled = { ! initialized }
183+ >
184+ Authenticate
185+ </ button >
186+
187+ < div style = { { marginTop : 10 } } >
106188 < button
107- className = "btn btn-primary"
108- onClick = { ( ) => handleLogin ( true ) }
189+ className = "login-button btn"
190+ style = { { fontSize : 13 , padding : '12px 20px' , opacity : 0.8 } }
191+ onClick = { ( ) => handleLogin ( false ) }
192+ disabled = { ! initialized }
109193 >
110- Authorize on Twitch (Full Permissions)
194+ Authenticate with minimal permissions
111195 </ button >
196+ </ div >
197+
198+ < div style = { { marginTop : 10 } } >
112199 < button
113- className = "btn btn-outline-secondary btn-sm"
114- onClick = { ( ) => handleLogin ( false ) }
200+ className = "login-button btn"
201+ style = { { fontSize : 13 , padding : '12px 20px' , opacity : 0.6 } }
202+ onClick = { ( ) => setView ( 'landing' ) }
115203 >
116- Authorize with minimal permissions
204+ ← Back
117205 </ button >
118206 </ div >
119207 </ >
120208 ) }
209+
210+ { /* Footer */ }
211+ < div style = { { fontSize : 10 , marginTop : 30 } } >
212+ < p > Super Secure Login< br /> Like Banks Have</ p >
213+ < p > *** Voice Recognition is only supported with Chrome ***</ p >
214+ < p >
215+ < a style = { { color : '#b36aff' } } href = "https://aws.amazon.com/polly/" target = "_blank" rel = "noreferrer" >
216+ Powered by Amazon Web Services (AWS) API Gateway, Lambda, DynamoDB, Polly and Translate
217+ </ a >
218+ </ p >
219+ </ div >
220+
121221 </ div >
122222 </ div >
123223 ) ;
0 commit comments