Rewrite create app modal to work with new spectrum fields

This commit is contained in:
Andrew Kingston 2021-04-15 19:28:50 +01:00
parent 591d1c0fc5
commit 910ac855cf
4 changed files with 82 additions and 137 deletions

View File

@ -4,7 +4,6 @@
import { store, automationStore, hostingStore } from "builderStore"
import { string, object } from "yup"
import api, { get } from "builderStore/api"
import Form from "@svelteschool/svelte-forms"
import Spinner from "components/common/Spinner.svelte"
import { Info, User } from "./Steps"
import Indicator from "./Indicator.svelte"
@ -16,44 +15,40 @@
import { onMount } from "svelte"
import Logo from "/assets/bb-logo.svg"
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
const createAppStore = writable({ currentStep: 0, values: {} })
export let template
const infoValidation = {
const currentStep = writable(0)
const values = writable({ roleId: "ADMIN" })
const errors = writable({})
const touched = writable({})
const steps = [Info, User]
let validators = [
{
applicationName: string().required("Your application must have a name."),
}
const userValidation = {
},
{
email: string()
.email()
.required("Your application needs a first user."),
password: string().required("Please enter a password for your first user."),
roleId: string().required("You need to select a role for your user."),
}
password: string().required(
"Please enter a password for your first user."
),
roleId: string()
.nullable()
.required("You need to select a role for your user."),
},
]
let submitting = false
let errors = {}
let validationErrors = {}
let validationSchemas = [infoValidation, userValidation]
function buildStep(component) {
return {
component,
errors,
}
}
// steps need to be initialized for cypress from the get go
let steps = [buildStep(Info), buildStep(User)]
let valid = false
$: checkValidity($values, validators[$currentStep])
onMount(async () => {
let hostingInfo = await hostingStore.actions.fetch()
// re-init the steps based on whether self hosting or cloud hosted
const hostingInfo = await hostingStore.actions.fetch()
if (hostingInfo.type === "self") {
await hostingStore.actions.fetchDeployedApps()
const existingAppNames = svelteGet(hostingStore).deployedAppNames
infoValidation.applicationName = string()
validators[0].applicationName = string()
.required("Your application must have a name.")
.test(
"non-existing-app-name",
@ -63,54 +58,20 @@
appName => appName.toLowerCase() === value.toLowerCase()
)
)
steps = [buildStep(Info), buildStep(User)]
validationSchemas = [infoValidation, userValidation]
}
})
// 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) {
const checkValidity = async (values, validator) => {
const obj = object().shape(validator)
Object.keys(validator).forEach(key => ($errors[key] = null))
try {
validationErrors = {}
await object(schema).validate(values, { abortEarly: false })
} catch (error) {
validationErrors = extractErrors(error)
}
}
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
await obj.validate(values, { abortEarly: false })
} catch (validationErrors) {
validationErrors.inner.forEach(error => {
$errors[error.path] = error.message
})
}
valid = await obj.isValid(values)
}
async function createNewApp() {
@ -118,7 +79,7 @@
try {
// Create form data to create app
let data = new FormData()
data.append("name", $createAppStore.values.applicationName)
data.append("name", $values.applicationName)
data.append("useTemplate", template != null)
if (template) {
data.append("templateName", template.name)
@ -134,7 +95,7 @@
}
analytics.captureEvent("App Created", {
name: $createAppStore.values.applicationName,
name: $values.applicationName,
appId: appJson._id,
template,
})
@ -153,12 +114,12 @@
// Create user
const user = {
email: $createAppStore.values.email,
password: $createAppStore.values.password,
roleId: $createAppStore.values.roleId,
email: $values.email,
password: $values.password,
roleId: $values.roleId,
}
const userResp = await api.post(`/api/users`, user)
const json = await userResp.json()
await userResp.json()
$goto(`./${appJson._id}`)
} catch (error) {
console.error(error)
@ -166,33 +127,14 @@
submitting = false
}
}
async function updateKey([key, value]) {
const response = await api.put(`/api/keys/${key}`, { value })
const res = await response.json()
return res
}
function extractErrors({ inner }) {
if (!inner) return {}
return inner.reduce((acc, err) => {
return { ...acc, [err.path]: err.message }
}, {})
}
let currentStepIsValid = false
let fullFormIsValid = false
$: checkValidity($createAppStore.values, $createAppStore.currentStep)
let onChange = () => {}
</script>
<div class="container">
<div class="sidebar">
{#each steps as { active, done }, i}
{#each steps as component, i}
<Indicator
active={$createAppStore.currentStep === i}
done={i < $createAppStore.currentStep}
active={$currentStep === i}
done={i < $currentStep}
step={i + 1} />
{/each}
</div>
@ -201,34 +143,32 @@
<h3 class="header">Get Started with Budibase</h3>
</div>
<div class="step">
<Form bind:values={$createAppStore.values}>
{#each steps as step, i (i)}
<div class:hidden={$createAppStore.currentStep !== i}>
{#each steps as component, i (i)}
<div class:hidden={$currentStep !== i}>
<svelte:component
this={step.component}
this={component}
{template}
{validationErrors}
options={step.options}
name={step.name} />
{values}
{errors}
{touched} />
</div>
{/each}
</Form>
</div>
<div class="footer">
{#if $createAppStore.currentStep > 0}
<Button medium secondary on:click={back}>Back</Button>
{#if $currentStep > 0}
<Button medium secondary on:click={() => $currentStep--}>Back</Button>
{/if}
{#if $createAppStore.currentStep < steps.length - 1}
<Button medium blue on:click={next} disabled={!currentStepIsValid}>
{#if $currentStep < steps.length - 1}
<Button medium blue on:click={() => $currentStep++} disabled={!valid}>
Next
</Button>
{/if}
{#if $createAppStore.currentStep === steps.length - 1}
{#if $currentStep === steps.length - 1}
<Button
medium
blue
on:click={createNewApp}
disabled={!fullFormIsValid || submitting}>
disabled={!valid || submitting}>
{submitting ? 'Loading...' : 'Submit'}
</Button>
{/if}

View File

@ -1,11 +1,14 @@
<script>
import { Label, Heading, Input } from "@budibase/bbui"
import { notifications } from "@budibase/bbui"
const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
export let validationErrors
export let template
export let values
export let errors
export let touched
let blurred = { appName: false }
let file
@ -51,12 +54,11 @@
</div>
{/if}
<Input
on:input={() => (blurred.appName = true)}
on:change={() => ($touched.applicationName = true)}
bind:value={$values.applicationName}
label="Web App Name"
name="applicationName"
placeholder="Enter name of your web application"
type="name"
error={blurred.appName && validationErrors.applicationName} />
error={$touched.applicationName && $errors.applicationName} />
</div>
<style>

View File

@ -1,30 +1,34 @@
<script>
import { Input, Select } from "@budibase/bbui"
export let validationErrors
let blurred = { email: false, password: false }
export let values
export let errors
export let touched
</script>
<h2>Create your first User</h2>
<div class="container">
<Input
on:input={() => (blurred.email = true)}
bind:value={$values.email}
on:change={() => ($touched.email = true)}
label="Email"
name="email"
placeholder="Email"
type="email"
error={blurred.email && validationErrors.email} />
error={$touched.email && $errors.email} />
<Input
on:input={() => (blurred.password = true)}
bind:value={$values.password}
on:change={() => ($touched.password = true)}
label="Password"
name="password"
placeholder="Password"
type="password"
error={blurred.password && validationErrors.password} />
<Select label="Role" secondary name="roleId">
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>
error={$touched.password && $errors.password} />
<Select
bind:value={$values.roleId}
label="Role"
options={[{ label: 'Admin', value: 'ADMIN' }, { label: 'Power User', value: 'POWER_USER' }]}
getOptionLabel={option => option.label}
getOptionValue={option => option.value}
error={$errors.roleId} />
</div>
<style>

View File

@ -1,3 +1,2 @@
export { default as API } from "./API.svelte"
export { default as Info } from "./Info.svelte"
export { default as User } from "./User.svelte"