diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 01cd20f605..9ac8a1e7c6 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -139,6 +139,8 @@ spec: value: {{ .Values.globals.automationMaxIterations | quote }} - name: TENANT_FEATURE_FLAGS value: {{ .Values.globals.tenantFeatureFlags | quote }} + - name: ENCRYPTION_KEY + value: {{ .Values.globals.bbEncryptionKey | quote }} {{ if .Values.globals.bbAdminUserEmail }} - name: BB_ADMIN_USER_EMAIL value: {{ .Values.globals.bbAdminUserEmail | quote }} diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index ff56fb5019..a16f839ea7 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -146,6 +146,8 @@ spec: value: {{ .Values.globals.google.secret | quote }} - name: TENANT_FEATURE_FLAGS value: {{ .Values.globals.tenantFeatureFlags | quote }} + - name: ENCRYPTION_KEY + value: {{ .Values.globals.bbEncryptionKey | quote }} {{ if .Values.globals.elasticApmEnabled }} - name: ELASTIC_APM_ENABLED value: {{ .Values.globals.elasticApmEnabled | quote }} diff --git a/lerna.json b/lerna.json index e8d717f430..79545f79a2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.48", + "version": "2.2.12-alpha.57", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index cbc902913a..01d6768549 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.48", + "version": "2.2.12-alpha.57", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.48", + "@budibase/types": "2.2.12-alpha.57", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 9b4761d961..a60a748bdc 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -15,18 +15,47 @@ import { getCouchInfo } from "./connections" import { directCouchCall } from "./utils" import { getPouchDB } from "./pouchDB" import { WriteStream, ReadStream } from "fs" +import { newid } from "../../newid" + +function buildNano(couchInfo: { url: string; cookie: string }) { + return Nano({ + url: couchInfo.url, + requestDefaults: { + headers: { + Authorization: couchInfo.cookie, + }, + }, + parseUrl: false, + }) +} + +export function DatabaseWithConnection( + dbName: string, + connection: string, + opts?: DatabaseOpts +) { + if (!connection) { + throw new Error("Must provide connection details") + } + return new DatabaseImpl(dbName, opts, connection) +} export class DatabaseImpl implements Database { public readonly name: string private static nano: Nano.ServerScope + private readonly instanceNano?: Nano.ServerScope private readonly pouchOpts: DatabaseOpts - constructor(dbName?: string, opts?: DatabaseOpts) { + constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) { if (dbName == null) { throw new Error("Database name cannot be undefined.") } this.name = dbName this.pouchOpts = opts || {} + if (connection) { + const couchInfo = getCouchInfo(connection) + this.instanceNano = buildNano(couchInfo) + } if (!DatabaseImpl.nano) { DatabaseImpl.init() } @@ -34,15 +63,7 @@ export class DatabaseImpl implements Database { static init() { const couchInfo = getCouchInfo() - DatabaseImpl.nano = Nano({ - url: couchInfo.url, - requestDefaults: { - headers: { - Authorization: couchInfo.cookie, - }, - }, - parseUrl: false, - }) + DatabaseImpl.nano = buildNano(couchInfo) } async exists() { @@ -50,6 +71,10 @@ export class DatabaseImpl implements Database { return response.status === 200 } + private nano() { + return this.instanceNano || DatabaseImpl.nano + } + async checkSetup() { let shouldCreate = !this.pouchOpts?.skip_setup // check exists in a lightweight fashion @@ -58,9 +83,9 @@ export class DatabaseImpl implements Database { throw new Error("DB does not exist") } if (!exists) { - await DatabaseImpl.nano.db.create(this.name) + await this.nano().db.create(this.name) } - return DatabaseImpl.nano.db.use(this.name) + return this.nano().db.use(this.name) } private async updateOutput(fnc: any) { @@ -101,6 +126,13 @@ export class DatabaseImpl implements Database { return this.updateOutput(() => db.destroy(_id, _rev)) } + async post(document: AnyDocument, opts?: DatabasePutOpts) { + if (!document._id) { + document._id = newid() + } + return this.put(document, opts) + } + async put(document: AnyDocument, opts?: DatabasePutOpts) { if (!document._id) { throw new Error("Cannot store document without _id field.") @@ -146,7 +178,7 @@ export class DatabaseImpl implements Database { async destroy() { try { - await DatabaseImpl.nano.db.destroy(this.name) + await this.nano().db.destroy(this.name) } catch (err: any) { // didn't exist, don't worry if (err.statusCode === 404) { diff --git a/packages/backend-core/src/db/couch/connections.ts b/packages/backend-core/src/db/couch/connections.ts index a2206de634..06c661f350 100644 --- a/packages/backend-core/src/db/couch/connections.ts +++ b/packages/backend-core/src/db/couch/connections.ts @@ -1,7 +1,7 @@ import env from "../../environment" -export const getCouchInfo = () => { - const urlInfo = getUrlInfo() +export const getCouchInfo = (connection?: string) => { + const urlInfo = getUrlInfo(connection) let username let password if (env.COUCH_DB_USERNAME) { diff --git a/packages/backend-core/src/events/publishers/user.ts b/packages/backend-core/src/events/publishers/user.ts index c296a8bc49..1fe50149b5 100644 --- a/packages/backend-core/src/events/publishers/user.ts +++ b/packages/backend-core/src/events/publishers/user.ts @@ -13,6 +13,7 @@ import { UserPermissionAssignedEvent, UserPermissionRemovedEvent, UserUpdatedEvent, + UserOnboardingEvent, } from "@budibase/types" async function created(user: User, timestamp?: number) { @@ -36,6 +37,13 @@ async function deleted(user: User) { await publishEvent(Event.USER_DELETED, properties) } +export async function onboardingComplete(user: User) { + const properties: UserOnboardingEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_ONBOARDING_COMPLETE, properties) +} + // PERMISSIONS async function permissionAdminAssigned(user: User, timestamp?: number) { @@ -126,6 +134,7 @@ export default { permissionAdminRemoved, permissionBuilderAssigned, permissionBuilderRemoved, + onboardingComplete, invited, inviteAccepted, passwordForceReset, diff --git a/packages/bbui/package.json b/packages/bbui/package.json index e62222e549..c7982f1b18 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.2.12-alpha.48", + "version": "2.2.12-alpha.57", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.48", + "@budibase/string-templates": "2.2.12-alpha.57", "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index cc4417be2a..640f696458 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -86,7 +86,7 @@ margin-left: 0; transition: color ease-out 130ms; } - .is-selected:not(.spectrum-ActionButton--emphasized) { + .is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) { background: var(--spectrum-global-color-gray-300); border-color: var(--spectrum-global-color-gray-700); } diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 463b69169f..1884bbf2d6 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -3,6 +3,9 @@ export default function positionDropdown( { anchor, align, maxWidth, useAnchorWidth } ) { const update = () => { + if (!anchor) { + return + } const anchorBounds = anchor.getBoundingClientRect() const elementBounds = element.getBoundingClientRect() let styles = { @@ -14,7 +17,9 @@ export default function positionDropdown( } // Determine vertical styles - if (window.innerHeight - anchorBounds.bottom < 100) { + if (align === "right-outside") { + styles.top = anchorBounds.top + } else if (window.innerHeight - anchorBounds.bottom < 100) { styles.top = anchorBounds.top - elementBounds.height - 5 } else { styles.top = anchorBounds.bottom + 5 @@ -30,8 +35,8 @@ export default function positionDropdown( } if (align === "right") { styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width - } else if (align === "right-side") { - styles.left = anchorBounds.left + anchorBounds.width + } else if (align === "right-outside") { + styles.left = anchorBounds.right + 10 } else { styles.left = anchorBounds.left } @@ -54,8 +59,11 @@ export default function positionDropdown( const resizeObserver = new ResizeObserver(entries => { entries.forEach(update) }) - resizeObserver.observe(anchor) + if (anchor) { + resizeObserver.observe(anchor) + } resizeObserver.observe(element) + resizeObserver.observe(document.body) document.addEventListener("scroll", update, true) diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index 979ec6a728..b8ffe9f7e6 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -15,11 +15,13 @@ export let tooltip = undefined export let dataCy export let newStyles = true + export let id let showTooltip = false + + + + + + diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index d995bd507b..7daf2173a7 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -179,7 +179,7 @@ - + + + diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte new file mode 100644 index 0000000000..b04c796d04 --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -0,0 +1,173 @@ + + +{#key tourStepKey} + + +
+ {tourStep?.title || "-"} +
{`${tourStepIdx + 1}/${tourSteps?.length}`}
+
+ + + {#if tourStep.layout} + + {:else} + {tourStep?.body || ""} + {/if} + + + +
+
+{/key} + + diff --git a/packages/builder/src/components/portal/onboarding/TourWrap.svelte b/packages/builder/src/components/portal/onboarding/TourWrap.svelte new file mode 100644 index 0000000000..1be149f7fa --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/TourWrap.svelte @@ -0,0 +1,29 @@ + + + diff --git a/packages/builder/src/components/portal/onboarding/steps/OnboardingData.svelte b/packages/builder/src/components/portal/onboarding/steps/OnboardingData.svelte new file mode 100644 index 0000000000..674d5c14ab --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/steps/OnboardingData.svelte @@ -0,0 +1,10 @@ +
+ In this section you can mange the data for your app: +
    +
  • Connect data sources
  • +
  • Edit data
  • +
  • Manage read & write access
  • +
  • Create views
  • +
  • Add bindings
  • +
+
diff --git a/packages/builder/src/components/portal/onboarding/steps/OnboardingDesign.svelte b/packages/builder/src/components/portal/onboarding/steps/OnboardingDesign.svelte new file mode 100644 index 0000000000..84d84777f5 --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/steps/OnboardingDesign.svelte @@ -0,0 +1,10 @@ +
+ After setting up your data, Design is where you build the screens for your + app: +
    +
  • Add screens
  • +
  • Add components
  • +
  • Choose your theme
  • +
  • Edit navigation
  • +
+
diff --git a/packages/builder/src/components/portal/onboarding/steps/OnboardingPublish.svelte b/packages/builder/src/components/portal/onboarding/steps/OnboardingPublish.svelte new file mode 100644 index 0000000000..8913d77482 --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/steps/OnboardingPublish.svelte @@ -0,0 +1,7 @@ +
+ Once you’re happy with your app you can publish it to production! +

+ After publishing, any changes you make will not take affect until you next + publish. +

+
diff --git a/packages/builder/src/components/portal/onboarding/steps/index.js b/packages/builder/src/components/portal/onboarding/steps/index.js new file mode 100644 index 0000000000..8e27748f36 --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/steps/index.js @@ -0,0 +1,3 @@ +export { default as OnboardingData } from "./OnboardingData.svelte" +export { default as OnboardingDesign } from "./OnboardingDesign.svelte" +export { default as OnboardingPublish } from "./OnboardingPublish.svelte" diff --git a/packages/builder/src/components/portal/onboarding/tourHandler.js b/packages/builder/src/components/portal/onboarding/tourHandler.js new file mode 100644 index 0000000000..d4a564f23a --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/tourHandler.js @@ -0,0 +1,47 @@ +import { store } from "builderStore/index" +import { get } from "svelte/store" + +const registerNode = async (node, tourStepKey) => { + if (!node) { + console.log("Tour Handler - an anchor node is required") + } + + if (!get(store).tourKey) { + console.log("Tour Handler - No active tour ", tourStepKey, node) + return + } + + store.update(state => { + const update = { + ...state, + tourNodes: { + ...state.tourNodes, + [tourStepKey]: node, + }, + } + return update + }) +} + +export function tourHandler(node, tourStepKey) { + if (node && tourStepKey) { + registerNode(node, tourStepKey) + } + return { + destroy: () => { + const updatedTourNodes = get(store).tourNodes + if (updatedTourNodes && updatedTourNodes[tourStepKey]) { + delete updatedTourNodes[tourStepKey] + store.update(state => { + const update = { + ...state, + tourNodes: { + ...updatedTourNodes, + }, + } + return update + }) + } + }, + } +} diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js new file mode 100644 index 0000000000..8acd5bb8ce --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -0,0 +1,95 @@ +import { get } from "svelte/store" +import { store } from "builderStore" +import { users, auth } from "stores/portal" +import analytics from "analytics" +import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps" +const ONBOARDING_EVENT_PREFIX = "onboarding" + +export const TOUR_STEP_KEYS = { + BUILDER_APP_PUBLISH: "builder-app-publish", + BUILDER_DATA_SECTION: "builder-data-section", + BUILDER_DESIGN_SECTION: "builder-design-section", + BUILDER_AUTOMATE_SECTION: "builder-automate-section", +} + +export const TOUR_KEYS = { + TOUR_BUILDER_ONBOARDING: "builder-onboarding", +} + +const tourEvent = eventKey => { + analytics.captureEvent(`${ONBOARDING_EVENT_PREFIX}:${eventKey}`, { + eventSource: EventSource.PORTAL, + }) +} + +const getTours = () => { + return { + [TOUR_KEYS.TOUR_BUILDER_ONBOARDING]: [ + { + id: TOUR_STEP_KEYS.BUILDER_DATA_SECTION, + title: "Data", + route: "/builder/app/:application/data", + layout: OnboardingData, + query: ".topcenternav .spectrum-Tabs-item#builder-data-tab", + onLoad: async () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_DATA_SECTION) + }, + align: "left", + }, + { + id: TOUR_STEP_KEYS.BUILDER_DESIGN_SECTION, + title: "Design", + route: "/builder/app/:application/design", + layout: OnboardingDesign, + query: ".topcenternav .spectrum-Tabs-item#builder-design-tab", + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_DESIGN_SECTION) + }, + align: "left", + }, + { + id: TOUR_STEP_KEYS.BUILDER_AUTOMATE_SECTION, + title: "Automations", + route: "/builder/app/:application/automate", + query: ".topcenternav .spectrum-Tabs-item#builder-automate-tab", + body: "Once you have your app screens made, you can set up automations to fit in with your current workflow", + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_AUTOMATE_SECTION) + }, + align: "left", + }, + { + id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH, + title: "Publish", + layout: OnboardingPublish, + query: ".toprightnav #builder-app-publish-button", + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH) + }, + onComplete: async () => { + // Mark the users onboarding as complete + // Clear all tour related state + if (get(auth).user) { + await users.save({ + ...get(auth).user, + onboardedAt: new Date().toISOString(), + }) + + // Update the cached user + await auth.getSelf() + + store.update(state => ({ + ...state, + tourNodes: undefined, + tourKey: undefined, + tourKeyStep: undefined, + onboarding: false, + })) + } + }, + }, + ], + } +} + +export const TOURS = getTours() diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index 99731b8285..cc1a70b3bc 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -4,37 +4,45 @@ Heading, notifications, Layout, - Input, Body, - ActionButton, Modal, } from "@budibase/bbui" import { goto } from "@roxi/routify" import { API } from "api" import { admin, auth } from "stores/portal" - import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte" import ImportAppsModal from "./_components/ImportAppsModal.svelte" import Logo from "assets/bb-emblem.svg" import { onMount } from "svelte" + import { FancyForm, FancyInput, ActionButton } from "@budibase/bbui" + import { TestimonialPage } from "@budibase/frontend-core/src/components" + import { passwordsMatch, handleError } from "../auth/_components/utils" - let adminUser = {} - let error let modal + let form + let errors = {} + let formData = {} + let submitted = false $: tenantId = $auth.tenantId - $: multiTenancyEnabled = $admin.multiTenancy $: cloud = $admin.cloud $: imported = $admin.importComplete async function save() { + form.validate() + if (Object.keys(errors).length > 0) { + return + } + submitted = true try { - adminUser.tenantId = tenantId + let adminUser = { ...formData, tenantId } + delete adminUser.confirmationPassword // Save the admin user await API.createAdminUser(adminUser) notifications.success("Admin user created") await admin.init() $goto("../portal") } catch (error) { + submitted = false notifications.error("Failed to create admin user") } } @@ -53,35 +61,103 @@ -
-
- + + + + logo - - Create an admin user - - The admin user has access to everything in Budibase. - - - - - - - - - {#if multiTenancyEnabled} - { - admin.unload() - $goto("../auth/org") - }} - > - Change organisation - - {:else if !cloud && !imported} + Create an admin user + The admin user has access to everything in Budibase. + + + + { + formData = { + ...formData, + email: e.detail, + } + }} + validate={() => { + let fieldError = { + email: !formData.email ? "Please enter a valid email" : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + disabled={submitted} + error={errors.email} + /> + { + formData = { + ...formData, + password: e.detail, + } + }} + validate={() => { + let fieldError = {} + + fieldError["password"] = !formData.password + ? "Please enter a password" + : undefined + + fieldError["confirmationPassword"] = + !passwordsMatch( + formData.password, + formData.confirmationPassword + ) && formData.confirmationPassword + ? "Passwords must match" + : undefined + + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.password} + disabled={submitted} + /> + { + formData = { + ...formData, + confirmationPassword: e.detail, + } + }} + validate={() => { + let fieldError = { + confirmationPassword: + !passwordsMatch( + formData.password, + formData.confirmationPassword + ) && formData.password + ? "Passwords must match" + : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.confirmationPassword} + disabled={submitted} + /> + + + + + + + -
-
+
+ diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 06ae57fa85..c99776320f 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -1,6 +1,7 @@ {#if show} - window.open(`/api/global/auth/${tenantId}/google`, "_blank")} > -
- google icon -

Sign in with Google

-
-
+ Log in with Google + {/if} - - diff --git a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte index 27f5bde186..396bde3cb0 100644 --- a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte @@ -1,5 +1,5 @@ {#if show} - window.open( `/api/global/auth/${$auth.tenantId}/oidc/configs/${$oidc.uuid}`, "_blank" )} > -
- oidc icon -

{`Sign in with ${$oidc.name || "OIDC"}`}

-
-
+ {`Log in with ${$oidc.name || "OIDC"}`} + {/if} - - diff --git a/packages/builder/src/pages/builder/auth/_components/utils.js b/packages/builder/src/pages/builder/auth/_components/utils.js new file mode 100644 index 0000000000..ebdb4e9881 --- /dev/null +++ b/packages/builder/src/pages/builder/auth/_components/utils.js @@ -0,0 +1,17 @@ +export const handleError = err => { + let update = { ...err } + return Object.keys(update).reduce((acc, key) => { + if (update[key]) { + acc[key] = update[key] + } + return acc + }, {}) +} + +export const passwordsMatch = (password, confirmation) => { + let confirm = confirmation?.trim() + let pwd = password?.trim() + return ( + typeof confirm === "string" && typeof pwd === "string" && confirm == pwd + ) +} diff --git a/packages/builder/src/pages/builder/auth/forgot.svelte b/packages/builder/src/pages/builder/auth/forgot.svelte index 7227fd6377..d58fdf99e0 100644 --- a/packages/builder/src/pages/builder/auth/forgot.svelte +++ b/packages/builder/src/pages/builder/auth/forgot.svelte @@ -1,25 +1,35 @@ - + + + + { + email = e.detail + }} + validate={() => { + if (!email) { + return "Please enter your email" + } + return null + }} + {error} + disabled={submitted} + /> + + +
+ +
+ + diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index d8633a4fbc..032cf850fa 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -5,7 +5,6 @@ Button, Divider, Heading, - Input, Layout, notifications, Link, @@ -14,22 +13,30 @@ import { auth, organisation, oidc, admin } from "stores/portal" import GoogleButton from "./_components/GoogleButton.svelte" import OIDCButton from "./_components/OIDCButton.svelte" + import { handleError } from "./_components/utils" import Logo from "assets/bb-emblem.svg" + import { TestimonialPage } from "@budibase/frontend-core/src/components" + import { FancyForm, FancyInput } from "@budibase/bbui" import { onMount } from "svelte" - let username = "" - let password = "" let loaded = false + let form + let errors = {} + let formData = {} $: company = $organisation.company || "Budibase" - $: multiTenancyEnabled = $admin.multiTenancy $: cloud = $admin.cloud async function login() { + form.validate() + if (Object.keys(errors).length > 0) { + console.log("errors") + return + } try { await auth.login({ - username: username.trim(), - password, + username: formData?.username.trim(), + password: formData?.password, }) if ($auth?.user?.forceResetPassword) { $goto("./reset") @@ -57,75 +64,96 @@ - + + {#if loaded && ($organisation.google || $organisation.oidc)} + + + + + + {/if} + + { + formData = { + ...formData, + username: e.detail, + } + }} + validate={() => { + let fieldError = { + username: !formData.username + ? "Please enter a valid email" + : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.username} + /> + { + formData = { + ...formData, + password: e.detail, + } + }} + validate={() => { + let fieldError = { + password: !formData.password + ? "Please enter your password" + : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.password} + /> + + + + + + + + + + {#if cloud} + + By using Budibase Cloud +
+ you are agreeing to our + + License Agreement + + + {/if} + + diff --git a/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte b/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte index 5b02bb1e84..d8481efc41 100644 --- a/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte +++ b/packages/builder/src/pages/builder/portal/_components/UpgradeButton.svelte @@ -2,26 +2,29 @@ import { Button } from "@budibase/bbui" import { goto } from "@roxi/routify" import { auth, admin } from "stores/portal" + import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" -{#if $admin.cloud && $auth?.user?.accountPortalAccess} - -{:else if !$admin.cloud && $auth.isAdmin} - +{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING)} + {#if $admin.cloud && $auth?.user?.accountPortalAccess} + + {:else if !$admin.cloud && $auth.isAdmin} + + {/if} {/if} diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index 3cb60e26cf..1f95c2f694 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -1,18 +1,20 @@ -{#if $auth.user && loaded} +{#if fullScreen} + +{:else if $auth.user && loaded} +