diff --git a/packages/builder/src/components/start/ExportAppModal.svelte b/packages/builder/src/components/start/ExportAppModal.svelte index 5cc3393a89..4a69aaef74 100644 --- a/packages/builder/src/components/start/ExportAppModal.svelte +++ b/packages/builder/src/components/start/ExportAppModal.svelte @@ -4,20 +4,60 @@ Toggle, Body, InlineAlert, + Input, notifications, } from "@budibase/bbui" + import { createValidationStore } from "helpers/validation/yup" export let app export let published - let excludeRows = false + let includeInternalTablesRows = true + let encypt = true - $: title = published ? "Export published app" : "Export latest app" - $: confirmText = published ? "Export published" : "Export latest" + let password = null + const validation = createValidationStore() + validation.addValidatorType("password", "password", true) + $: validation.observe("password", password) + + const Step = { CONFIG: "config", SET_PASSWORD: "set_password" } + let currentStep = Step.CONFIG + + $: exportButtonText = published ? "Export published" : "Export latest" + $: stepConfig = { + [Step.CONFIG]: { + title: published ? "Export published app" : "Export latest app", + confirmText: encypt ? "Continue" : exportButtonText, + onConfirm: () => { + if (!encypt) { + exportApp() + } else { + currentStep = Step.SET_PASSWORD + return false + } + }, + isValid: true, + }, + [Step.SET_PASSWORD]: { + title: "Add password to encrypt your export", + confirmText: exportButtonText, + onConfirm: async () => { + await validation.check({ password }) + if (!$validation.valid) { + return false + } + exportApp(password) + }, + isValid: $validation.valid, + }, + } const exportApp = async () => { const id = published ? app.prodId : app.devId const url = `/api/backups/export?appId=${id}` - await downloadFile(url, { excludeRows }) + await downloadFile(url, { + excludeRows: !includeInternalTablesRows, + encryptPassword: password, + }) } async function downloadFile(url, body) { @@ -56,13 +96,33 @@ } - - - Apps can be exported with or without data that is within internal tables - - select this below. - + + {#if currentStep === Step.CONFIG} + + + + + {#if !encypt} + + {/if} + {/if} + {#if currentStep === Step.SET_PASSWORD} + + {/if} diff --git a/packages/builder/src/helpers/validation/validation.js b/packages/builder/src/helpers/validation/validation.js index db5dfe4430..f64bf56835 100644 --- a/packages/builder/src/helpers/validation/validation.js +++ b/packages/builder/src/helpers/validation/validation.js @@ -6,7 +6,6 @@ export function createValidationStore(initialValue, ...validators) { let touched = false const value = writable(initialValue || "") - const error = derived(value, $v => validate($v, validators)) const touchedStore = derived(value, () => { if (!touched) { touched = true @@ -14,6 +13,10 @@ export function createValidationStore(initialValue, ...validators) { } return touched }) + const error = derived( + [value, touchedStore], + ([$v, $t]) => $t && validate($v, validators) + ) return [value, error, touchedStore] } diff --git a/packages/builder/src/helpers/validation/yup/index.js b/packages/builder/src/helpers/validation/yup/index.js index 20ddaebb1a..b5bdf030a5 100644 --- a/packages/builder/src/helpers/validation/yup/index.js +++ b/packages/builder/src/helpers/validation/yup/index.js @@ -5,6 +5,7 @@ import { notifications } from "@budibase/bbui" export const createValidationStore = () => { const DEFAULT = { + values: {}, errors: {}, touched: {}, valid: false, @@ -33,6 +34,9 @@ export const createValidationStore = () => { case "email": propertyValidator = string().email().nullable() break + case "password": + propertyValidator = string().nullable() + break default: propertyValidator = string().nullable() } @@ -41,9 +45,68 @@ export const createValidationStore = () => { propertyValidator = propertyValidator.required() } + // We want to do this after the possible required validation, to prioritise the required error + switch (type) { + case "password": + propertyValidator = propertyValidator.min(8) + break + } + validator[propertyName] = propertyValidator } + const observe = async (propertyName, value) => { + const values = get(validation).values + let fieldIsValid + if (!values.hasOwnProperty(propertyName)) { + // Initial setup + values[propertyName] = value + return + } + + if (value === values[propertyName]) { + return + } + + const obj = object().shape(validator) + try { + validation.update(store => { + store.errors[propertyName] = null + return store + }) + await obj.validateAt(propertyName, { [propertyName]: value }) + fieldIsValid = true + } catch (error) { + const [fieldError] = error.errors + if (fieldError) { + validation.update(store => { + store.errors[propertyName] = capitalise(fieldError) + store.valid = false + return store + }) + } + } + + if (fieldIsValid) { + // Validate the rest of the fields + try { + await obj.validate( + { ...values, [propertyName]: value }, + { abortEarly: false } + ) + validation.update(store => { + store.valid = true + return store + }) + } catch { + validation.update(store => { + store.valid = false + return store + }) + } + } + } + const check = async values => { const obj = object().shape(validator) // clear the previous errors @@ -87,5 +150,6 @@ export const createValidationStore = () => { check, addValidator, addValidatorType, + observe, } }