Rewrite create app modal to work with new spectrum fields
This commit is contained in:
parent
591d1c0fc5
commit
910ac855cf
4 changed files with 82 additions and 137 deletions
|
@ -4,7 +4,6 @@
|
||||||
import { store, automationStore, hostingStore } from "builderStore"
|
import { store, automationStore, hostingStore } from "builderStore"
|
||||||
import { string, object } from "yup"
|
import { string, object } from "yup"
|
||||||
import api, { get } from "builderStore/api"
|
import api, { get } from "builderStore/api"
|
||||||
import Form from "@svelteschool/svelte-forms"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import { Info, User } from "./Steps"
|
import { Info, User } from "./Steps"
|
||||||
import Indicator from "./Indicator.svelte"
|
import Indicator from "./Indicator.svelte"
|
||||||
|
@ -16,44 +15,40 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import Logo from "/assets/bb-logo.svg"
|
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
|
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."),
|
applicationName: string().required("Your application must have a name."),
|
||||||
}
|
},
|
||||||
const userValidation = {
|
{
|
||||||
email: string()
|
email: string()
|
||||||
.email()
|
.email()
|
||||||
.required("Your application needs a first user."),
|
.required("Your application needs a first user."),
|
||||||
password: string().required("Please enter a password for your first user."),
|
password: string().required(
|
||||||
roleId: string().required("You need to select a role for your user."),
|
"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 submitting = false
|
||||||
let errors = {}
|
let valid = false
|
||||||
let validationErrors = {}
|
$: checkValidity($values, validators[$currentStep])
|
||||||
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)]
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let hostingInfo = await hostingStore.actions.fetch()
|
const hostingInfo = await hostingStore.actions.fetch()
|
||||||
// re-init the steps based on whether self hosting or cloud hosted
|
|
||||||
if (hostingInfo.type === "self") {
|
if (hostingInfo.type === "self") {
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
await hostingStore.actions.fetchDeployedApps()
|
||||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
||||||
infoValidation.applicationName = string()
|
validators[0].applicationName = string()
|
||||||
.required("Your application must have a name.")
|
.required("Your application must have a name.")
|
||||||
.test(
|
.test(
|
||||||
"non-existing-app-name",
|
"non-existing-app-name",
|
||||||
|
@ -63,54 +58,20 @@
|
||||||
appName => appName.toLowerCase() === value.toLowerCase()
|
appName => appName.toLowerCase() === value.toLowerCase()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
steps = [buildStep(Info), buildStep(User)]
|
|
||||||
validationSchemas = [infoValidation, userValidation]
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handles form navigation
|
const checkValidity = async (values, validator) => {
|
||||||
const back = () => {
|
const obj = object().shape(validator)
|
||||||
if ($createAppStore.currentStep > 0) {
|
Object.keys(validator).forEach(key => ($errors[key] = null))
|
||||||
$createAppStore.currentStep -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const next = () => {
|
|
||||||
$createAppStore.currentStep += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// $: errors = validationSchemas.validate(values);
|
|
||||||
$: getErrors(
|
|
||||||
$createAppStore.values,
|
|
||||||
validationSchemas[$createAppStore.currentStep]
|
|
||||||
)
|
|
||||||
|
|
||||||
async function getErrors(values, schema) {
|
|
||||||
try {
|
try {
|
||||||
validationErrors = {}
|
await obj.validate(values, { abortEarly: false })
|
||||||
await object(schema).validate(values, { abortEarly: false })
|
} catch (validationErrors) {
|
||||||
} catch (error) {
|
validationErrors.inner.forEach(error => {
|
||||||
validationErrors = extractErrors(error)
|
$errors[error.path] = error.message
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
valid = await obj.isValid(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNewApp() {
|
async function createNewApp() {
|
||||||
|
@ -118,7 +79,7 @@
|
||||||
try {
|
try {
|
||||||
// Create form data to create app
|
// Create form data to create app
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("name", $createAppStore.values.applicationName)
|
data.append("name", $values.applicationName)
|
||||||
data.append("useTemplate", template != null)
|
data.append("useTemplate", template != null)
|
||||||
if (template) {
|
if (template) {
|
||||||
data.append("templateName", template.name)
|
data.append("templateName", template.name)
|
||||||
|
@ -134,7 +95,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
analytics.captureEvent("App Created", {
|
analytics.captureEvent("App Created", {
|
||||||
name: $createAppStore.values.applicationName,
|
name: $values.applicationName,
|
||||||
appId: appJson._id,
|
appId: appJson._id,
|
||||||
template,
|
template,
|
||||||
})
|
})
|
||||||
|
@ -153,12 +114,12 @@
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
const user = {
|
const user = {
|
||||||
email: $createAppStore.values.email,
|
email: $values.email,
|
||||||
password: $createAppStore.values.password,
|
password: $values.password,
|
||||||
roleId: $createAppStore.values.roleId,
|
roleId: $values.roleId,
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users`, user)
|
const userResp = await api.post(`/api/users`, user)
|
||||||
const json = await userResp.json()
|
await userResp.json()
|
||||||
$goto(`./${appJson._id}`)
|
$goto(`./${appJson._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -166,33 +127,14 @@
|
||||||
submitting = false
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
{#each steps as { active, done }, i}
|
{#each steps as component, i}
|
||||||
<Indicator
|
<Indicator
|
||||||
active={$createAppStore.currentStep === i}
|
active={$currentStep === i}
|
||||||
done={i < $createAppStore.currentStep}
|
done={i < $currentStep}
|
||||||
step={i + 1} />
|
step={i + 1} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -201,34 +143,32 @@
|
||||||
<h3 class="header">Get Started with Budibase</h3>
|
<h3 class="header">Get Started with Budibase</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step">
|
||||||
<Form bind:values={$createAppStore.values}>
|
{#each steps as component, i (i)}
|
||||||
{#each steps as step, i (i)}
|
<div class:hidden={$currentStep !== i}>
|
||||||
<div class:hidden={$createAppStore.currentStep !== i}>
|
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={step.component}
|
this={component}
|
||||||
{template}
|
{template}
|
||||||
{validationErrors}
|
{values}
|
||||||
options={step.options}
|
{errors}
|
||||||
name={step.name} />
|
{touched} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
{#if $createAppStore.currentStep > 0}
|
{#if $currentStep > 0}
|
||||||
<Button medium secondary on:click={back}>Back</Button>
|
<Button medium secondary on:click={() => $currentStep--}>Back</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $createAppStore.currentStep < steps.length - 1}
|
{#if $currentStep < steps.length - 1}
|
||||||
<Button medium blue on:click={next} disabled={!currentStepIsValid}>
|
<Button medium blue on:click={() => $currentStep++} disabled={!valid}>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $createAppStore.currentStep === steps.length - 1}
|
{#if $currentStep === steps.length - 1}
|
||||||
<Button
|
<Button
|
||||||
medium
|
medium
|
||||||
blue
|
blue
|
||||||
on:click={createNewApp}
|
on:click={createNewApp}
|
||||||
disabled={!fullFormIsValid || submitting}>
|
disabled={!valid || submitting}>
|
||||||
{submitting ? 'Loading...' : 'Submit'}
|
{submitting ? 'Loading...' : 'Submit'}
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Heading, Input } from "@budibase/bbui"
|
import { Label, Heading, Input } from "@budibase/bbui"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
const BYTES_IN_MB = 1000000
|
const BYTES_IN_MB = 1000000
|
||||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
||||||
|
|
||||||
export let validationErrors
|
|
||||||
export let template
|
export let template
|
||||||
|
export let values
|
||||||
|
export let errors
|
||||||
|
export let touched
|
||||||
|
|
||||||
let blurred = { appName: false }
|
let blurred = { appName: false }
|
||||||
let file
|
let file
|
||||||
|
@ -51,12 +54,11 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.appName = true)}
|
on:change={() => ($touched.applicationName = true)}
|
||||||
|
bind:value={$values.applicationName}
|
||||||
label="Web App Name"
|
label="Web App Name"
|
||||||
name="applicationName"
|
|
||||||
placeholder="Enter name of your web application"
|
placeholder="Enter name of your web application"
|
||||||
type="name"
|
error={$touched.applicationName && $errors.applicationName} />
|
||||||
error={blurred.appName && validationErrors.applicationName} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select } from "@budibase/bbui"
|
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>
|
</script>
|
||||||
|
|
||||||
<h2>Create your first User</h2>
|
<h2>Create your first User</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.email = true)}
|
bind:value={$values.email}
|
||||||
|
on:change={() => ($touched.email = true)}
|
||||||
label="Email"
|
label="Email"
|
||||||
name="email"
|
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
type="email"
|
type="email"
|
||||||
error={blurred.email && validationErrors.email} />
|
error={$touched.email && $errors.email} />
|
||||||
<Input
|
<Input
|
||||||
on:input={() => (blurred.password = true)}
|
bind:value={$values.password}
|
||||||
|
on:change={() => ($touched.password = true)}
|
||||||
label="Password"
|
label="Password"
|
||||||
name="password"
|
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
type="password"
|
||||||
error={blurred.password && validationErrors.password} />
|
error={$touched.password && $errors.password} />
|
||||||
<Select label="Role" secondary name="roleId">
|
<Select
|
||||||
<option value="ADMIN">Admin</option>
|
bind:value={$values.roleId}
|
||||||
<option value="POWER_USER">Power User</option>
|
label="Role"
|
||||||
</Select>
|
options={[{ label: 'Admin', value: 'ADMIN' }, { label: 'Power User', value: 'POWER_USER' }]}
|
||||||
|
getOptionLabel={option => option.label}
|
||||||
|
getOptionValue={option => option.value}
|
||||||
|
error={$errors.roleId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export { default as API } from "./API.svelte"
|
|
||||||
export { default as Info } from "./Info.svelte"
|
export { default as Info } from "./Info.svelte"
|
||||||
export { default as User } from "./User.svelte"
|
export { default as User } from "./User.svelte"
|
||||||
|
|
Loading…
Reference in a new issue