Move a user profile and password modals to frontend-core to allow sharing

This commit is contained in:
Andrew Kingston 2025-03-07 16:14:12 +00:00
parent b390f92019
commit 3c44c94442
No known key found for this signature in database
23 changed files with 69 additions and 62 deletions

View File

@ -5,8 +5,8 @@
import { appsStore } from "@/stores/portal" import { appsStore } from "@/stores/portal"
import { API } from "@/api" import { API } from "@/api"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { createValidationStore } from "@/helpers/validation/yup" import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
import * as appValidation from "@/helpers/validation/yup/app" import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
import EditableIcon from "@/components/common/EditableIcon.svelte" import EditableIcon from "@/components/common/EditableIcon.svelte"
import { isEqual } from "lodash" import { isEqual } from "lodash"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"

View File

@ -12,8 +12,8 @@
import { appsStore, admin, auth } from "@/stores/portal" import { appsStore, admin, auth } from "@/stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { createValidationStore } from "@/helpers/validation/yup" import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
import * as appValidation from "@/helpers/validation/yup/app" import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
import TemplateCard from "@/components/common/TemplateCard.svelte" import TemplateCard from "@/components/common/TemplateCard.svelte"
import { lowercase } from "@/helpers" import { lowercase } from "@/helpers"
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"

View File

@ -6,9 +6,9 @@
Layout, Layout,
keepOpen, keepOpen,
} from "@budibase/bbui" } from "@budibase/bbui"
import { createValidationStore } from "@/helpers/validation/yup" import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import * as appValidation from "@/helpers/validation/yup/app" import * as appValidation from "@budibase/frontend-core/src/utils/validation/yup/app"
import { appsStore, auth } from "@/stores/portal" import { appsStore, auth } from "@/stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import { API } from "@/api" import { API } from "@/api"

View File

@ -9,7 +9,7 @@
notifications, notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import { downloadFile } from "@budibase/frontend-core" import { downloadFile } from "@budibase/frontend-core"
import { createValidationStore } from "@/helpers/validation/yup" import { createValidationStore } from "@budibase/frontend-core/src/utils/validation/yup"
export let app export let app
export let published export let published

View File

@ -41,11 +41,6 @@ export const LAYOUT_NAMES = {
}, },
} }
// one or more word characters and whitespace
export const APP_NAME_REGEX = /^[\w\s]+$/
// zero or more non-whitespace characters
export const APP_URL_REGEX = /^[0-9a-zA-Z-_]+$/
export const DefaultAppTheme = { export const DefaultAppTheme = {
primaryColor: "var(--spectrum-global-color-blue-600)", primaryColor: "var(--spectrum-global-color-blue-600)",
primaryColorHover: "var(--spectrum-global-color-blue-500)", primaryColorHover: "var(--spectrum-global-color-blue-500)",

View File

@ -28,13 +28,13 @@
Constants, Constants,
Utils, Utils,
RoleUtils, RoleUtils,
emailValidator,
} from "@budibase/frontend-core" } from "@budibase/frontend-core"
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"
import { API } from "@/api" import { API } from "@/api"
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte" import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
import RoleSelect from "@/components/common/RoleSelect.svelte" import RoleSelect from "@/components/common/RoleSelect.svelte"
import UpgradeModal from "@/components/common/users/UpgradeModal.svelte" import UpgradeModal from "@/components/common/users/UpgradeModal.svelte"
import { emailValidator } from "@/helpers/validation"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte" import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
import BuilderGroupPopover from "./BuilderGroupPopover.svelte" import BuilderGroupPopover from "./BuilderGroupPopover.svelte"

View File

@ -24,13 +24,13 @@
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { AppStatus } from "@/constants" import { AppStatus } from "@/constants"
import { gradient } from "@/actions" import { gradient } from "@/actions"
import ProfileModal from "@/components/settings/ProfileModal.svelte" import { ProfileModal, ChangePasswordModal } from "@budibase/frontend-core"
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import Spaceman from "assets/bb-space-man.svg" import Spaceman from "assets/bb-space-man.svg"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { UserAvatar } from "@budibase/frontend-core" import { UserAvatar } from "@budibase/frontend-core"
import { helpers, sdk } from "@budibase/shared-core" import { helpers, sdk } from "@budibase/shared-core"
import { API } from "@/api"
let loaded = false let loaded = false
let userInfoModal let userInfoModal
@ -201,10 +201,10 @@
</Page> </Page>
</div> </div>
<Modal bind:this={userInfoModal}> <Modal bind:this={userInfoModal}>
<ProfileModal /> <ProfileModal {API} user={$auth.user} on:save={() => auth.getSelf()} />
</Modal> </Modal>
<Modal bind:this={changePasswordModal}> <Modal bind:this={changePasswordModal}>
<ChangePasswordModal /> <ChangePasswordModal {API} on:save={() => auth.getSelf()} />
</Modal> </Modal>
{/if} {/if}

View File

@ -12,7 +12,7 @@
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { TestimonialPage } from "@budibase/frontend-core/src/components" import { TestimonialPage } from "@budibase/frontend-core/src/components"
import { onMount } from "svelte" import { onMount } from "svelte"
import PasswordRepeatInput from "../../../components/common/users/PasswordRepeatInput.svelte" import PasswordRepeatInput from "@budibase/frontend-core/src/components/PasswordRepeatInput.svelte"
const resetCode = $params["?code"] const resetCode = $params["?code"]
let form let form
@ -79,11 +79,7 @@
<Layout gap="S" noPadding> <Layout gap="S" noPadding>
<Heading size="M">Reset your password</Heading> <Heading size="M">Reset your password</Heading>
<Body size="M">Must contain at least 12 characters</Body> <Body size="M">Must contain at least 12 characters</Body>
<PasswordRepeatInput <PasswordRepeatInput bind:password bind:error={passwordError} />
bind:passwordForm={form}
bind:password
bind:error={passwordError}
/>
<Button secondary cta on:click={reset}> <Button secondary cta on:click={reset}>
{#if loading} {#if loading}
<ProgressCircle overBackground={true} size="S" /> <ProgressCircle overBackground={true} size="S" />

View File

@ -2,8 +2,8 @@
import { admin, auth } from "@/stores/portal" import { admin, auth } from "@/stores/portal"
import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui" import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import ProfileModal from "@/components/settings/ProfileModal.svelte" import ProfileModal from "@budibase/frontend-core/src/components/ProfileModal.svelte"
import ChangePasswordModal from "@/components/settings/ChangePasswordModal.svelte" import ChangePasswordModal from "@budibase/frontend-core/src/components/ChangePasswordModal.svelte"
import ThemeModal from "@/components/settings/ThemeModal.svelte" import ThemeModal from "@/components/settings/ThemeModal.svelte"
import APIKeyModal from "@/components/settings/APIKeyModal.svelte" import APIKeyModal from "@/components/settings/APIKeyModal.svelte"
import { UserAvatar } from "@budibase/frontend-core" import { UserAvatar } from "@budibase/frontend-core"

View File

@ -1,7 +1,7 @@
<script> <script>
import { Button, FancyForm, FancyInput } from "@budibase/bbui" import { Button, FancyForm, FancyInput } from "@budibase/bbui"
import PanelHeader from "./PanelHeader.svelte" import PanelHeader from "./PanelHeader.svelte"
import { APP_URL_REGEX } from "@/constants" import { Constants } from "@budibase/frontend-core"
export let disabled export let disabled
export let name = "" export let name = ""
@ -28,7 +28,7 @@
return "URL must be provided" return "URL must be provided"
} }
if (!APP_URL_REGEX.test(url)) { if (!Constants.APP_URL_REGEX.test(url)) {
return "Invalid URL" return "Invalid URL"
} }
} }

View File

@ -10,8 +10,7 @@
Icon, Icon,
} from "@budibase/bbui" } from "@budibase/bbui"
import { groups, licensing } from "@/stores/portal" import { groups, licensing } from "@/stores/portal"
import { Constants } from "@budibase/frontend-core" import { Constants, emailValidator } from "@budibase/frontend-core"
import { emailValidator } from "@/helpers/validation"
import { capitalise } from "@/helpers" import { capitalise } from "@/helpers"
export let showOnboardingTypeModal export let showOnboardingTypeModal

View File

@ -8,8 +8,7 @@
Icon, Icon,
} from "@budibase/bbui" } from "@budibase/bbui"
import { groups, licensing, admin } from "@/stores/portal" import { groups, licensing, admin } from "@/stores/portal"
import { emailValidator } from "@/helpers/validation" import { emailValidator, Constants } from "@budibase/frontend-core"
import { Constants } from "@budibase/frontend-core"
import { capitalise } from "@/helpers" import { capitalise } from "@/helpers"
const BYTES_IN_MB = 1000000 const BYTES_IN_MB = 1000000

View File

@ -1,21 +1,27 @@
<script> <script lang="ts">
import { ModalContent, Body, notifications } from "@budibase/bbui" import { ModalContent, Body, notifications } from "@budibase/bbui"
import PasswordRepeatInput from "@/components/common/users/PasswordRepeatInput.svelte" import PasswordRepeatInput from "./PasswordRepeatInput.svelte"
import { auth } from "@/stores/portal" import type { APIClient } from "@budibase/frontend-core"
import { createEventDispatcher } from "svelte"
let password export let API: APIClient
let error
const dispatch = createEventDispatcher()
let password: string = ""
let error: string = ""
const updatePassword = async () => { const updatePassword = async () => {
try { try {
await auth.updateSelf({ password }) await API.updateSelf({ password })
dispatch("save")
notifications.success("Password changed successfully") notifications.success("Password changed successfully")
} catch (error) { } catch (error) {
notifications.error("Failed to update password") notifications.error("Failed to update password")
} }
} }
const handleKeydown = evt => { const handleKeydown = (evt: KeyboardEvent) => {
if (evt.key === "Enter" && !error && password) { if (evt.key === "Enter" && !error && password) {
updatePassword() updatePassword()
} }
@ -27,7 +33,7 @@
title="Update password" title="Update password"
confirmText="Update password" confirmText="Update password"
onConfirm={updatePassword} onConfirm={updatePassword}
disabled={error || !password} disabled={!!error || !password}
> >
<Body size="S">Enter your new password below.</Body> <Body size="S">Enter your new password below.</Body>
<PasswordRepeatInput bind:password bind:error /> <PasswordRepeatInput bind:password bind:error />

View File

@ -1,20 +1,14 @@
<script> <script>
import { FancyForm, FancyInput } from "@budibase/bbui" import { FancyForm, FancyInput } from "@budibase/bbui"
import { import { createValidationStore, requiredValidator } from "../utils/validation"
createValidationStore,
requiredValidator,
} from "@/helpers/validation"
import { admin } from "@/stores/portal"
export let password export let password
export let passwordForm
export let error export let error
export let minLength = 12
$: passwordMinLength = $admin.passwordMinLength ?? 12
const validatePassword = value => { const validatePassword = value => {
if (!value || value.length < passwordMinLength) { if (!value || value.length < minLength) {
return `Please enter at least ${passwordMinLength} characters. We recommend using machine generated or random passwords.` return `Please enter at least ${minLength} characters. We recommend using machine generated or random passwords.`
} }
return null return null
} }
@ -41,7 +35,7 @@
firstPasswordError firstPasswordError
</script> </script>
<FancyForm bind:this={passwordForm}> <FancyForm>
<FancyInput <FancyInput
label="Password" label="Password"
type="password" type="password"

View File

@ -1,16 +1,26 @@
<script> <script lang="ts">
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { auth } from "@/stores/portal" import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
import type { User, ContextUser } from "@budibase/types"
import type { APIClient } from "@budibase/frontend-core"
import { createEventDispatcher } from "svelte"
export let user: User | ContextUser | undefined = undefined
export let API: APIClient
$: console.log(user)
const dispatch = createEventDispatcher()
const values = writable({ const values = writable({
firstName: $auth.user.firstName, firstName: user?.firstName,
lastName: $auth.user.lastName, lastName: user?.lastName,
}) })
const updateInfo = async () => { const updateInfo = async () => {
try { try {
await auth.updateSelf($values) await API.updateSelf($values)
dispatch("save")
notifications.success("Information updated successfully") notifications.success("Information updated successfully")
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -23,7 +33,7 @@
<Body size="S"> <Body size="S">
Personalise the platform by adding your first name and last name. Personalise the platform by adding your first name and last name.
</Body> </Body>
<Input disabled bind:value={$auth.user.email} label="Email" /> <Input disabled value={user?.email || ""} label="Email" />
<Input bind:value={$values.firstName} label="First name" /> <Input bind:value={$values.firstName} label="First name" />
<Input bind:value={$values.lastName} label="Last name" /> <Input bind:value={$values.lastName} label="Last name" />
</ModalContent> </ModalContent>

View File

@ -9,3 +9,5 @@ export { Grid } from "./grid"
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte" export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
export { default as CoreFilterBuilder } from "./CoreFilterBuilder.svelte" export { default as CoreFilterBuilder } from "./CoreFilterBuilder.svelte"
export { default as FilterUsers } from "./FilterUsers.svelte" export { default as FilterUsers } from "./FilterUsers.svelte"
export { default as ChangePasswordModal } from "./ChangePasswordModal.svelte"
export { default as ProfileModal } from "./ProfileModal.svelte"

View File

@ -166,3 +166,9 @@ export const FieldPermissions = {
READONLY: "readonly", READONLY: "readonly",
HIDDEN: "hidden", HIDDEN: "hidden",
} }
// one or more word characters and whitespace
export const APP_NAME_REGEX = /^[\w\s]+$/
// zero or more non-whitespace characters
export const APP_URL_REGEX = /^[0-9a-zA-Z-_]+$/

View File

@ -14,3 +14,4 @@ export * from "./settings"
export * from "./relatedColumns" export * from "./relatedColumns"
export * from "./table" export * from "./table"
export * from "./components" export * from "./components"
export * from "./validation"

View File

@ -1,5 +1,5 @@
import { string, mixed } from "yup" import { string, mixed } from "yup"
import { APP_NAME_REGEX, APP_URL_REGEX } from "@/constants" import { APP_NAME_REGEX, APP_URL_REGEX } from "../../../constants"
export const name = (validation, { apps, currentApp } = { apps: [] }) => { export const name = (validation, { apps, currentApp } = { apps: [] }) => {
validation.addValidator( validation.addValidator(

View File

@ -1,7 +1,6 @@
import { capitalise } from "@/helpers"
import { object, string, number } from "yup" import { object, string, number } from "yup"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { notifications } from "@budibase/bbui" import { Helpers, notifications } from "@budibase/bbui"
export const createValidationStore = () => { export const createValidationStore = () => {
const DEFAULT = { const DEFAULT = {
@ -77,7 +76,7 @@ export const createValidationStore = () => {
const [fieldError] = error.errors const [fieldError] = error.errors
if (fieldError) { if (fieldError) {
validation.update(store => { validation.update(store => {
store.errors[propertyName] = capitalise(fieldError) store.errors[propertyName] = Helpers.capitalise(fieldError)
store.valid = false store.valid = false
return store return store
}) })
@ -120,7 +119,7 @@ export const createValidationStore = () => {
} else { } else {
error.inner.forEach(err => { error.inner.forEach(err => {
validation.update(store => { validation.update(store => {
store.errors[err.path] = capitalise(err.message) store.errors[err.path] = Helpers.capitalise(err.message)
return store return store
}) })
}) })