11import React , { useEffect , useState } from 'react' ;
2- import { Box , Text } from 'ink' ;
3- // import Spinner from 'ink-spinner';
2+ import { Box , Newline , Text , useApp } from 'ink' ;
43import SelectInput from 'ink-select-input' ;
54import { Flags } from '../flags.js' ;
65import { Result } from 'meow' ;
7- import TextInput from 'ink-text-input' ;
6+ import TextInput , { UncontrolledTextInput } from 'ink-text-input' ;
87import Spinner from 'ink-spinner' ;
8+ import os from 'os' ;
9+ import path from 'path' ;
10+ import fs from 'fs' ;
911
1012type Item < V > = {
1113 key ?: string ;
1214 label : string ;
1315 value : V ;
1416} ;
1517
16- type LoginMethod = 'token' | 'flow' ;
17-
1818export default function Login ( { cli} : { cli : Result < Flags > } ) {
19- const [ isCheckingAuth , setIsCheckingAuth ] = useState ( false ) ;
19+ const [ loginMethod , setLoginMethod ] = useState < string | undefined > ( undefined ) ;
20+ const [ isShowingSelect , setIsShowingSelect ] = useState ( false ) ;
21+ const [ isShowingInput , setIsShowingInput ] = useState ( false ) ;
22+
23+ const [ isCheckingAuth , setIsCheckingAuth ] = useState ( true ) ;
2024 const [ isAuthenticated , setIsAuthenticated ] = useState ( false ) ;
21- const [ loginMethod , setLoginMethod ] = useState < LoginMethod | undefined > (
22- undefined ,
23- ) ;
2425 const [ token , setToken ] = useState < string > ( '' ) ;
2526
26- const items : Item < LoginMethod > [ ] = [
27+ const [ isTokenInEnv , setIsTokenInEnv ] = useState ( false ) ;
28+ const [ isTokenSaved , setIsTokenSaved ] = useState ( false ) ;
29+
30+ const [ tokenInput , setTokenInput ] = useState < string > ( '' ) ;
31+ const [ saveTokenInput , setSaveTokenInput ] = useState < string > ( '' ) ;
32+
33+ const loginMethodItems : Item < string > [ ] = [
2734 {
2835 label : 'Login using access token' ,
2936 value : 'token' ,
@@ -34,21 +41,30 @@ export default function Login({cli}: {cli: Result<Flags>}) {
3441 } ,
3542 ] ;
3643
44+ const { exit} = useApp ( ) ;
45+
3746 // Check login method
3847 useEffect ( ( ) => {
39- const tokenEnv = process . env [ 'PE_ACCESS_TOKEN' ] ;
40- if ( tokenEnv ) {
48+ const savedToken = getToken ( ) ;
49+ if ( savedToken ) {
4150 setLoginMethod ( 'token' ) ;
42- setToken ( tokenEnv ) ;
43- }
44-
45- if ( cli . flags . token ) {
51+ setToken ( savedToken ) ;
52+ return ;
53+ } else if ( cli . flags . token ) {
4654 setLoginMethod ( 'token' ) ;
4755 } else if ( cli . flags . flow ) {
4856 setLoginMethod ( 'flow' ) ;
4957 }
5058 } , [ cli ] ) ;
5159
60+ useEffect ( ( ) => {
61+ setIsShowingSelect ( loginMethod === undefined ) ;
62+ } , [ loginMethod ] ) ;
63+
64+ useEffect ( ( ) => {
65+ setIsShowingInput ( ! isShowingSelect ) ;
66+ } , [ isShowingSelect ] ) ;
67+
5268 // Check token validity
5369 useEffect ( ( ) => {
5470 async function checkToken ( token : string ) {
@@ -69,60 +85,134 @@ export default function Login({cli}: {cli: Result<Flags>}) {
6985
7086 // Only check token validity when it is set
7187 if ( loginMethod === 'token' && token . length > 0 ) {
72- setIsCheckingAuth ( true ) ;
7388 checkToken ( token ) . then ( isValid => {
7489 setIsAuthenticated ( isValid ) ;
7590 setIsCheckingAuth ( false ) ;
7691 } ) ;
7792 }
7893 } , [ token , loginMethod ] ) ;
7994
80- if ( loginMethod === undefined ) {
81- // Prompt user to select login method
82- return (
83- < >
84- < Text > Login to the Pulse Editor Platform</ Text >
85- < SelectInput
86- items = { items }
87- onSelect = { item => setLoginMethod ( item . value ) }
88- />
89- </ >
90- ) ;
91- } else if ( loginMethod === 'token' ) {
92- // Prompt user to enter token if not already set
93- if ( token . length === 0 ) {
94- return (
95- < >
96- < Text > Enter your Pulse Editor access token:</ Text >
97- < TextInput
98- value = { token }
99- onChange = { setToken }
100- onSubmit = { value => setToken ( value ) }
101- />
102- </ >
103- ) ;
95+ function saveToken ( token : string ) {
96+ // Save the token to .pulse-editor/config.json in user home directory
97+ const configDir = path . join ( os . homedir ( ) , '.pulse-editor' ) ;
98+ const configFile = path . join ( configDir , 'config.json' ) ;
99+ const config = {
100+ accessToken : token ,
101+ } ;
102+ if ( ! fs . existsSync ( configDir ) ) {
103+ fs . mkdirSync ( configDir , { recursive : true } ) ;
104104 }
105+ fs . writeFileSync ( configFile , JSON . stringify ( config , null , 2 ) ) ;
106+ }
105107
106- // Check token validity
107- if ( isCheckingAuth ) {
108- return (
109- < Box >
110- < Spinner type = "dots" />
111- < Text > Checking authentication...</ Text >
112- </ Box >
113- ) ;
108+ function getToken ( ) {
109+ // First try to get the token from the environment variable
110+ const tokenEnv = process . env [ 'PE_ACCESS_TOKEN' ] ;
111+ if ( tokenEnv ) {
112+ setIsTokenInEnv ( true ) ;
113+ return tokenEnv ;
114114 }
115115
116- if ( isAuthenticated ) {
117- return < Text > You are signed in successfully.</ Text > ;
116+ // If not found, try to get the token from the config file
117+ const configDir = path . join ( os . homedir ( ) , '.pulse-editor' ) ;
118+ const configFile = path . join ( configDir , 'config.json' ) ;
119+ if ( fs . existsSync ( configFile ) ) {
120+ const config = JSON . parse ( fs . readFileSync ( configFile , 'utf8' ) ) ;
121+ if ( config . accessToken ) {
122+ return config . accessToken as string ;
123+ }
118124 }
119- return < Text > Authentication error: please enter valid credentials.</ Text > ;
125+
126+ // If not found, return undefined
127+ return undefined ;
120128 }
121129
122130 return (
123131 < >
124- < Text > (WIP) Open the following URL in your browser:</ Text >
125- < Text > https://pulse-editor.com/login</ Text >
132+ { isShowingSelect && (
133+ < >
134+ < Text > Login to the Pulse Editor Platform</ Text >
135+ < SelectInput
136+ items = { loginMethodItems }
137+ onSelect = { item => {
138+ setLoginMethod ( item . value ) ;
139+ } }
140+ />
141+ </ >
142+ ) }
143+
144+ { isShowingInput &&
145+ loginMethod === 'token' &&
146+ ( token . length === 0 ? (
147+ < >
148+ < Text > Enter your Pulse Editor access token:</ Text >
149+ < TextInput
150+ mask = "*"
151+ value = { tokenInput }
152+ onChange = { setTokenInput }
153+ onSubmit = { value => {
154+ if ( value . length === 0 ) {
155+ return ;
156+ }
157+ setToken ( value ) ;
158+ } }
159+ />
160+ </ >
161+ ) : isCheckingAuth ? (
162+ < Box >
163+ < Spinner type = "dots" />
164+ < Text > Checking authentication...</ Text >
165+ </ Box >
166+ ) : isAuthenticated ? (
167+ < >
168+ < Text > ✅ You are signed in successfully.</ Text >
169+ { ! isTokenInEnv && getToken ( ) !== token && (
170+ < >
171+ < Text >
172+ 🟢 It is recommended to save your access token as an
173+ environment variable PE_ACCESS_TOKEN.
174+ </ Text >
175+ < Box >
176+ < Text >
177+ ⚠️ (NOT recommended) Or, do you want to save access token to
178+ user home directory? (y/n){ ' ' }
179+ </ Text >
180+ < TextInput
181+ value = { saveTokenInput }
182+ onChange = { setSaveTokenInput }
183+ onSubmit = { value => {
184+ if ( value . length === 0 ) {
185+ return ;
186+ }
187+ if ( value === 'y' ) {
188+ saveToken ( token ) ;
189+ setIsTokenSaved ( true ) ;
190+ setTimeout ( ( ) => {
191+ exit ( ) ;
192+ } , 100 ) ;
193+ } else {
194+ exit ( ) ;
195+ }
196+ } }
197+ />
198+ </ Box >
199+ </ >
200+ ) }
201+ { isTokenSaved && (
202+ < Text >
203+ Token saved to { path . join ( os . homedir ( ) , '.pulse-editor' ) }
204+ </ Text >
205+ ) }
206+ </ >
207+ ) : (
208+ < Text > Authentication error: please enter valid credentials.</ Text >
209+ ) ) }
210+ { isShowingInput && loginMethod === 'flow' && (
211+ < >
212+ < Text > (WIP) Open the following URL in your browser:</ Text >
213+ < Text > https://pulse-editor.com/login</ Text >
214+ </ >
215+ ) }
126216 </ >
127217 ) ;
128218}
0 commit comments