api key logic works correctly

This commit is contained in:
kevmodrome 2020-08-03 15:59:50 +02:00
parent f78e816f20
commit 9b7adf9d8c
6 changed files with 275 additions and 78 deletions

View File

@ -55,11 +55,12 @@
] ]
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.17.0", "@budibase/bbui": "^1.18.0",
"@budibase/client": "^0.1.1", "@budibase/client": "^0.1.1",
"@budibase/colorpicker": "^1.0.1", "@budibase/colorpicker": "^1.0.1",
"@nx-js/compiler-util": "^2.0.0", "@nx-js/compiler-util": "^2.0.0",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@svelteschool/svelte-forms": "^0.7.0",
"codemirror": "^5.51.0", "codemirror": "^5.51.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
@ -74,7 +75,8 @@
"string_decoder": "^1.2.0", "string_decoder": "^1.2.0",
"svelte-portal": "^0.1.0", "svelte-portal": "^0.1.0",
"svelte-simple-modal": "^0.4.2", "svelte-simple-modal": "^0.4.2",
"uikit": "^3.1.7" "uikit": "^3.1.7",
"yup": "^0.29.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.5", "@babel/core": "^7.5.5",

View File

@ -1,4 +1,14 @@
<script context="module">
// Init store here so that it doesn't get destroyed and removed when the component instance is destroyed. This is to make sure no data is lost if the user closes the model
import { writable } from "svelte/store"
export const createAppStore = writable({ currentStep: 0, values: {} })
</script>
<script> <script>
import { string, object } from "yup"
import api from "builderStore/api"
import Form from "@svelteschool/svelte-forms"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import { API, Info, User } from "./Steps" import { API, Info, User } from "./Steps"
import Indicator from "./Indicator.svelte" import Indicator from "./Indicator.svelte"
@ -10,52 +20,136 @@
import { post } from "builderStore/api" import { post } from "builderStore/api"
import analytics from "../../analytics" import analytics from "../../analytics"
export let hasAPIKey = false
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
let name = "" export let hasKey
let description = ""
let loading = false
let error = {}
const createNewApp = async () => { let submitting = false
if ((name.length > 100 || name.length < 1) && description.length < 1) { let errors = {}
error = { let validationErrors = {}
name: true, let validationSchemas = [
description: true, {
} apiKey: string().required("Please enter your API key."),
} else if (description.length < 1) { },
error = { {
name: false, applicationName: string().required("Your application must have a name."),
description: true, },
} {
} else if (name.length > 100 || name.length < 1) { username: string().required("Your application needs a first user."),
error = { password: string().required(
name: true, "Please enter a password for your first user."
} ),
} else { accessLevelId: string().required(
error = {} "You need to select an access level for your user."
const data = { name, description } ),
loading = true },
try { ]
const response = await post("/api/applications", data)
const res = await response.json() let steps = [
{
component: API,
errors,
},
{
component: Info,
errors,
},
{
component: User,
errors,
},
]
analytics.captureEvent("web_app_created", { if (hasKey) {
name, validationSchemas.shift()
description, validationSchemas = validationSchemas
appId: res._id, steps.shift()
}) steps = steps
$goto(`./${res._id}`) }
} catch (error) {
console.error(error) // Handles form navigation
} const back = () => {
if ($createAppStore.currentStep > 0) {
$createAppStore.currentStep -= 1
}
}
const next = () => {
$createAppStore.currentStep += 1
}
// $: errors = validationSchemas.validate(values);
$: getErrors(
$createAppStore.values,
validationSchemas[$createAppStore.currentStep]
)
async function getErrors(values, schema) {
try {
validationErrors = {}
await object(schema).validate(values, { abortEarly: false })
} catch (error) {
validationErrors = extractErrors(error)
} }
} }
let value const checkValidity = async (values, currentStep) => {
const validity = await object()
.shape(validationSchemas[currentStep])
.isValid(values)
currentStepIsValid = validity
// Check full form on last step
if (currentStep === steps.length - 1) {
// Make one big schema from all the small ones
const fullSchema = Object.assign({}, ...validationSchemas)
// Check full form schema
const formIsValid = await object()
.shape(fullSchema)
.isValid(values)
fullFormIsValid = formIsValid
}
}
async function signUp() {
submitting = true
try {
if (!hasKey) {
await updateKey(["budibase", $createAppStore.values.apiKey])
}
const response = await post("/api/applications", {
name: $createAppStore.values.applicationName,
})
const res = await response.json()
analytics.captureEvent("web_app_created", {
name,
description,
appId: res._id,
})
console.log("App created!")
// $goto(`./${res._id}`)
} catch (error) {
console.error(error)
}
}
async function updateKey([key, value]) {
console.log("Saving API Key")
const response = await api.put(`/api/keys/${key}`, { value })
const res = await response.json()
return res
}
function extractErrors({ inner }) {
return inner.reduce((acc, err) => {
return { ...acc, [err.path]: err.message }
}, {})
}
let currentStepIsValid = false
let fullFormIsValid = false
$: checkValidity($createAppStore.values, $createAppStore.currentStep)
let onChange = () => {} let onChange = () => {}
function _onCancel() { function _onCancel() {
@ -65,21 +159,14 @@
async function _onOkay() { async function _onOkay() {
await createNewApp() await createNewApp()
} }
let currentStep = 0
let steps = [
{ title: "Setup your API Key" },
{ title: "Create your first web app" },
{ title: "Create new user" },
]
</script> </script>
<div class="container"> <div class="container">
<div class="sidebar"> <div class="sidebar">
{#each steps as { active, done }, i} {#each steps as { active, done }, i}
<Indicator <Indicator
active={currentStep === i} active={$createAppStore.currentStep === i}
done={i < currentStep} done={i < $createAppStore.currentStep}
step={i + 1} /> step={i + 1} />
{/each} {/each}
</div> </div>
@ -88,38 +175,41 @@
<h3 class="header">Get Started with Budibase</h3> <h3 class="header">Get Started with Budibase</h3>
</div> </div>
<div class="step"> <div class="step">
<Input <Form bind:values={$createAppStore.values}>
error={error.name} {#each steps as step, i (i)}
name="name" <div class:hidden={$createAppStore.currentStep !== i}>
label="Name" <svelte:component
placeholder="Enter application name" this={step.component}
on:change={e => (name = e.target.value)} {validationErrors}
on:input={e => (name = e.target.value)} /> options={step.options}
<TextArea name={step.name} />
bind:value={description} </div>
name="description" {/each}
label="Description" </Form>
placeholder="Describe your application" />
{#if error.description}
<span class="error">
Please enter a short description of your application
</span>
{/if}
</div> </div>
<div class="footer"> <div class="footer">
{#if currentStep !== 0} {#if $createAppStore.currentStep > 0}
<Button primary thin on:click={_onOkay}>Back</Button> <Button secondary on:click={back}>Back</Button>
{/if} {/if}
<Button primary thin on:click={_onOkay}>Next</Button> {#if $createAppStore.currentStep < steps.length - 1}
{#if currentStep + 1 === steps.length} <Button secondary on:click={next} disabled={!currentStepIsValid}>
<Button primary thin on:click={_onOkay}>Launch</Button> Next
</Button>
{/if}
{#if $createAppStore.currentStep === steps.length - 1}
<Button
secondary
on:click={signUp}
disabled={!fullFormIsValid || submitting}>
{submitting ? 'Loading...' : 'Submit'}
</Button>
{/if} {/if}
</div> </div>
</div> </div>
<div class="close-button" on:click={_onCancel}> <div class="close-button" on:click={_onCancel}>
<CloseIcon /> <CloseIcon />
</div> </div>
{#if loading} {#if submitting}
<div in:fade class="spinner-container"> <div in:fade class="spinner-container">
<Spinner /> <Spinner />
<span class="spinner-text">Creating your app...</span> <span class="spinner-text">Creating your app...</span>
@ -129,6 +219,7 @@
<style> <style>
.container { .container {
min-height: 600px;
display: grid; display: grid;
grid-template-columns: 80px 1fr; grid-template-columns: 80px 1fr;
position: relative; position: relative;
@ -165,6 +256,7 @@
.body { .body {
padding: 40px 60px 60px 60px; padding: 40px 60px 60px 60px;
display: grid; display: grid;
align-items: center;
grid-template-rows: auto 1fr auto; grid-template-rows: auto 1fr auto;
} }
.footer { .footer {
@ -189,9 +281,8 @@
.spinner-text { .spinner-text {
font-size: 2em; font-size: 2em;
} }
.error {
color: var(--red); .hidden {
font-weight: bold; display: none;
font-size: 0.8em;
} }
</style> </style>

View File

@ -0,0 +1,25 @@
<script>
import { Input } from "@budibase/bbui"
export let validationErrors
let blurred = { api: false }
</script>
<h2>Setup your API Key</h2>
<div class="container">
<Input
on:input={() => (blurred.api = true)}
label="API Key"
name="apiKey"
placeholder="Enter your API Key"
type="password"
error={blurred.api && validationErrors.apiKey} />
<a target="_blank" href="https://portal.budi.live/">Get API Key</a>
</div>
<style>
.container {
display: grid;
grid-gap: 40px;
}
</style>

View File

@ -0,0 +1,24 @@
<script>
import { Input } from "@budibase/bbui"
export let validationErrors
let blurred = { appName: false }
</script>
<h2>Create your first web app</h2>
<div class="container">
<Input
on:input={() => (blurred.appName = true)}
label="Web app name"
name="applicationName"
placeholder="Enter name of your web application"
type="name"
error={blurred.appName && validationErrors.applicationName} />
</div>
<style>
.container {
display: grid;
grid-gap: 40px;
}
</style>

View File

@ -0,0 +1,35 @@
<script>
import { Input, Select } from "@budibase/bbui"
export let validationErrors
let blurred = { username: false, password: false }
</script>
<h2>Create new user</h2>
<div class="container">
<Input
on:input={() => (blurred.username = true)}
label="Username"
name="username"
placeholder="Username"
type="name"
error={blurred.username && validationErrors.username} />
<Input
on:input={() => (blurred.password = true)}
label="Password"
name="password"
placeholder="Password"
type="pasword"
error={blurred.password && validationErrors.password} />
<Select name="accessLevelId">
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
</div>
<style>
.container {
display: grid;
grid-gap: 40px;
}
</style>

View File

@ -1,6 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { store } from "builderStore" import { store } from "builderStore"
import api from "builderStore/api"
import AppList from "components/start/AppList.svelte" import AppList from "components/start/AppList.svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
@ -23,6 +24,24 @@
} }
} }
let hasKey
async function fetchKeys() {
const response = await api.get(`/api/keys/`)
const res = await response.json()
return res.budibase
}
async function checkIfKeysAndApps() {
const key = await fetchKeys()
const apps = await getApps()
if (key) {
hasKey = true
} else {
showCreateAppModal()
}
}
// Handle create app modal // Handle create app modal
const { open } = getContext("simple-modal") const { open } = getContext("simple-modal")
@ -30,8 +49,7 @@
open( open(
CreateAppModal, CreateAppModal,
{ {
message: "What is your name?", hasKey,
hasForm: true,
}, },
{ {
closeButton: false, closeButton: false,
@ -42,6 +60,8 @@
} }
) )
} }
checkIfKeysAndApps()
</script> </script>
<div class="header"> <div class="header">