Skip to content

Commit

Permalink
feat(front): Add apikey input form (#58)
Browse files Browse the repository at this point in the history
set custom api key form

catch 401 error

trigger new request on api key change

style and focus on text input by default
  • Loading branch information
ekelen authored Apr 14, 2020
1 parent 940d223 commit 8c49f2f
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 16 deletions.
1 change: 1 addition & 0 deletions web/src/assets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@

/* ------------------------------ Buttons ------------------------------ */

.btn,
.btn.btn-primary:not([class^='btn-outline']):not([class*=' btn-outline']):not([class^='btn-ghost']):not([class*=' btn-ghost']):not(:focus):not(.focus),
.btn.btn-primary:not([class^='btn-outline']):not([class*=' btn-outline']):not([class^='btn-ghost']):not([class*=' btn-ghost']):not(.btn-secondary) {
@include themify($themes) {
Expand Down
37 changes: 37 additions & 0 deletions web/src/ui/components/ApiKeyPrompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, {useState, useRef, useEffect} from 'react';

const ApiKeyPrompt = ({failedKey, setApiKey: submitNewApiKey}) => {
const [formApiKey, updateFormApiKey] = useState(failedKey);
const inputEl = useRef(null);
useEffect(() => inputEl.current.focus());

return (
<section>
<div className="form-group">
<div className="input mt-3 mb-3">
<input
ref={inputEl}
type="text"
className="form-control"
placeholder={
'Current key: ' +
(failedKey || process.env.YOLO_APP_PW || 'no key set')
}
onChange={(e) => {
updateFormApiKey(e.target.value);
}}
/>
</div>
<button
className="btn"
onClick={() => submitNewApiKey(formApiKey)}
disabled={!formApiKey}
>
Update
</button>
</div>
</section>
);
};

export default ApiKeyPrompt;
51 changes: 38 additions & 13 deletions web/src/ui/components/BuildList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ import React, {useState, useEffect} from 'react';
import Card from './Card';
import axios from 'axios';
import {results} from '../../assets/sample-build-response';
import ApiKeyPrompt from './ApiKeyPrompt';
import {has} from 'lodash';

const BuildList = ({platformName, platformId}) => {
const BuildList = ({platformName, platformId, apiKey, setApiKey}) => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
const [baseURL, setBaseURL] = useState('');
const [hasValidAPIKey, setApiKeyValidity] = useState(true);

// TODO: Move these controllers out of component code
useEffect(() => {
console.log(`process.env.YOLO_UI_TEST:`, process.env.YOLO_UI_TEST);
setError(null);
setIsLoaded(false);
const source = () =>
process.env.YOLO_UI_TEST === 'true'
? Promise.resolve(results)
: axios.get(
`${process.env.API_URL}/build-list?artifact_kind=${platformId}&`,
{
headers: process.env.YOLO_APP_PW
headers: apiKey
? {
Authorization:
'Basic ' + btoa(`${process.env.YOLO_APP_PW}`),
Authorization: 'Basic ' + btoa(`${apiKey}`),
}
: {},
}
Expand All @@ -32,28 +35,50 @@ const BuildList = ({platformName, platformId}) => {
const {
data: {builds},
} = result;
setError(null);
setIsLoaded(true);
setApiKeyValidity(true);
setBaseURL(
`${document.location.protocol}//${document.location.host}`
);
setItems(builds);
},
(error) => {
setIsLoaded(true);
setError(error);
const status = has(error, 'response.status')
? error.response.status
: 0;
const message = has(error, 'response.statusText')
? error.response.statusText
: error.message || 'Unknown error';

setError({message, status});
if (status === 401) setApiKeyValidity(false);
}
);
if (!isLoaded) {
getBuilds(source);
}
});
getBuilds(source);
}, [apiKey, platformId]);

const errorDisplay = () => (
<h3 className="title">
Error {error.status || ''}: {error.message}
</h3>
);

// TODO: Factor out HOC
if (error) {
if (!hasValidAPIKey && error && error.status && error.status === 401) {
return (
<div>
<h3 className="m-3">Builds for{' ' + platformName}</h3>Error:{' '}
{error.message}
{errorDisplay()}
<small>No valid API token detected.</small>
<ApiKeyPrompt failedKey={apiKey} setApiKey={setApiKey} />
</div>
);
} else if (error) {
return (
<div>
<h3 className="m-3">Builds for{' ' + platformName}</h3>
{errorDisplay()}
</div>
);
} else if (!isLoaded) {
Expand Down
21 changes: 18 additions & 3 deletions web/src/ui/pages/Home.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useState} from 'react';
import React, {useState, useEffect} from 'react';
import BuildList from '../components/BuildList';

import './Home.scss';
Expand All @@ -11,6 +11,10 @@ const PLATFORMS = {

const Home = () => {
const [platformId, setPlatformId] = useState(PLATFORMS.none);
const [apiKey, setApiKey] = useState(`${process.env.YOLO_APP_PW || ''}`);
const updateApiKey = (key) => {
return setApiKey(key);
};

return (
<div className="container mt-3">
Expand All @@ -33,11 +37,22 @@ const Home = () => {
<option value={PLATFORMS.android}>Android</option>
</select>
</div>
{/* TODO: Factor */}
{platformId === PLATFORMS.android && (
<BuildList platformName="Android" platformId={PLATFORMS.android} />
<BuildList
platformName="Android"
platformId={PLATFORMS.android}
apiKey={apiKey}
setApiKey={updateApiKey}
/>
)}
{platformId === PLATFORMS.iOS && (
<BuildList platformName="iOS" platformId={PLATFORMS.iOS} />
<BuildList
platformName="iOS"
platformId={PLATFORMS.iOS}
apiKey={apiKey}
setApiKey={updateApiKey}
/>
)}
</div>
);
Expand Down

0 comments on commit 8c49f2f

Please sign in to comment.