Add initial work on fancy form components for onboarding
This commit is contained in:
parent
37244d3dff
commit
b312ac68c8
|
@ -0,0 +1,29 @@
|
||||||
|
<script>
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
import FancyField from "./FancyField.svelte"
|
||||||
|
|
||||||
|
export let icon
|
||||||
|
export let disabled
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FancyField on:click clickable {disabled}>
|
||||||
|
{#if icon}
|
||||||
|
{#if icon.includes("/")}
|
||||||
|
<img src={icon} alt="button" />
|
||||||
|
{:else}
|
||||||
|
<Icon name={icon} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</FancyField>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script>
|
||||||
|
export let disabled = false
|
||||||
|
export let error = null
|
||||||
|
export let focused = false
|
||||||
|
export let clickable = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="fancy-field"
|
||||||
|
class:error
|
||||||
|
class:disabled
|
||||||
|
class:focused
|
||||||
|
class:clickable
|
||||||
|
>
|
||||||
|
<div class="content" on:click>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
{#if error}
|
||||||
|
<div class="error-message">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fancy-field {
|
||||||
|
max-width: 400px;
|
||||||
|
background: var(--spectrum-global-color-gray-75);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 130ms ease-out, background 130ms ease-out,
|
||||||
|
background 130ms ease-out;
|
||||||
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
}
|
||||||
|
.fancy-field:hover {
|
||||||
|
border-color: var(--spectrum-global-color-gray-400);
|
||||||
|
}
|
||||||
|
.fancy-field.clickable:hover {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.fancy-field.focused {
|
||||||
|
border-color: var(--spectrum-global-color-blue-400);
|
||||||
|
}
|
||||||
|
.fancy-field.error {
|
||||||
|
border-color: var(--spectrum-global-color-red-400);
|
||||||
|
}
|
||||||
|
.fancy-field.disabled {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
color: var(--spectrum-global-color-gray-400);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
height: 64px;
|
||||||
|
padding: 0 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
background: var(--spectrum-global-color-red-400);
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script>
|
||||||
|
import { setContext } from "svelte"
|
||||||
|
|
||||||
|
let fields = {}
|
||||||
|
|
||||||
|
setContext("fancy-form", {
|
||||||
|
registerField: (id, api) => {
|
||||||
|
fields = { ...fields, [id]: api }
|
||||||
|
},
|
||||||
|
unregisterField: id => {
|
||||||
|
delete fields[id]
|
||||||
|
fields = fields
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const validate = () => {
|
||||||
|
let valid = true
|
||||||
|
Object.values(fields).forEach(api => {
|
||||||
|
if (!api.validate()) {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fancy-form">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fancy-form :global(.fancy-field:not(:first-child)) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
.fancy-form :global(.fancy-field:not(:last-child)) {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, getContext, onMount } from "svelte"
|
||||||
|
import FancyField from "./FancyField.svelte"
|
||||||
|
|
||||||
|
export let label
|
||||||
|
export let value
|
||||||
|
export let type = "text"
|
||||||
|
export let disabled = false
|
||||||
|
export let error = null
|
||||||
|
export let validate = null
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const formContext = getContext("fancy-form")
|
||||||
|
const id = Math.random()
|
||||||
|
|
||||||
|
let focused = false
|
||||||
|
$: placeholder = !focused && !value
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
const newValue = e.target.value
|
||||||
|
dispatch("change", newValue)
|
||||||
|
value = newValue
|
||||||
|
if (validate) {
|
||||||
|
error = validate(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
validate: () => {
|
||||||
|
if (validate) {
|
||||||
|
error = validate(value)
|
||||||
|
} else {
|
||||||
|
error = null
|
||||||
|
}
|
||||||
|
return !error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (formContext) {
|
||||||
|
formContext.registerField(id, API)
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (formContext) {
|
||||||
|
formContext.unregisterField(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FancyField {error} {disabled} {focused}>
|
||||||
|
{#if label}
|
||||||
|
<div class="label" class:placeholder>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
{disabled}
|
||||||
|
value={value || ""}
|
||||||
|
type={type || "text"}
|
||||||
|
on:input={onChange}
|
||||||
|
on:focus={() => (focused = true)}
|
||||||
|
on:blur={() => (focused = false)}
|
||||||
|
class:placeholder
|
||||||
|
/>
|
||||||
|
</FancyField>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transform: translateY(-12px);
|
||||||
|
position: absolute;
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
transition: font-size 130ms ease-out, transform 130ms ease-out;
|
||||||
|
}
|
||||||
|
.label.placeholder {
|
||||||
|
font-size: 15px;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
z-index: 1;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
transition: transform 130ms ease-out;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateY(9px);
|
||||||
|
}
|
||||||
|
input.placeholder {
|
||||||
|
transform: translateY(0);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as FancyInput } from "./FancyInput.svelte"
|
||||||
|
export { default as FancyButton } from "./FancyButton.svelte"
|
||||||
|
export { default as FancyForm } from "./FancyForm.svelte"
|
|
@ -101,3 +101,6 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
export * as Helpers from "./helpers"
|
export * as Helpers from "./helpers"
|
||||||
|
|
||||||
|
// Fancy form components
|
||||||
|
export * from "./FancyForm"
|
||||||
|
|
|
@ -1,40 +1,30 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { FancyButton } from "@budibase/bbui"
|
||||||
import GoogleLogo from "assets/google-logo.png"
|
import GoogleLogo from "assets/google-logo.png"
|
||||||
import { auth, organisation } from "stores/portal"
|
import { auth, organisation } from "stores/portal"
|
||||||
|
|
||||||
let show
|
let show
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
$: tenantId = $auth.tenantId
|
||||||
$: show = $organisation.google
|
$: show = true //$organisation.google
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton
|
<FancyButton
|
||||||
on:click={() =>
|
on:click={() => {
|
||||||
window.open(`/api/global/auth/${tenantId}/google`, "_blank")}
|
window.open(`/api/global/auth/${tenantId}/google`, "_blank")
|
||||||
|
}}
|
||||||
|
icon={GoogleLogo}
|
||||||
>
|
>
|
||||||
<div class="inner">
|
Log in with Google
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
</FancyButton>
|
||||||
<p>Sign in with Google</p>
|
<FancyButton
|
||||||
</div>
|
disabled
|
||||||
</ActionButton>
|
on:click={() => {
|
||||||
|
window.open(`/api/global/auth/${tenantId}/google`, "_blank")
|
||||||
|
}}
|
||||||
|
icon={GoogleLogo}
|
||||||
|
>
|
||||||
|
Log in with Google
|
||||||
|
</FancyButton>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: var(--spacing-xs);
|
|
||||||
padding-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
.inner img {
|
|
||||||
width: 18px;
|
|
||||||
margin: 3px 10px 3px 3px;
|
|
||||||
}
|
|
||||||
.inner p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Heading,
|
Heading,
|
||||||
Input,
|
|
||||||
Layout,
|
Layout,
|
||||||
notifications,
|
notifications,
|
||||||
Link,
|
Link,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import { FancyInput, FancyForm } from "@budibase/bbui"
|
||||||
|
import { TestimonialPage } from "@budibase/frontend-core"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { auth, organisation, oidc, admin } from "stores/portal"
|
import { auth, organisation, oidc, admin } from "stores/portal"
|
||||||
import GoogleButton from "./_components/GoogleButton.svelte"
|
import GoogleButton from "./_components/GoogleButton.svelte"
|
||||||
|
@ -20,12 +21,16 @@
|
||||||
let username = ""
|
let username = ""
|
||||||
let password = ""
|
let password = ""
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let form
|
||||||
|
|
||||||
$: company = $organisation.company || "Budibase"
|
$: company = $organisation.company || "Budibase"
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
$: multiTenancyEnabled = $admin.multiTenancy
|
||||||
$: cloud = $admin.cloud
|
$: cloud = $admin.cloud
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
|
if (!form.validate()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await auth.login({
|
await auth.login({
|
||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
|
@ -57,32 +62,51 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
<svelte:window on:keydown={handleKeydown} />
|
||||||
<div class="login">
|
|
||||||
<div class="main">
|
<TestimonialPage>
|
||||||
<Layout>
|
<Layout noPadding>
|
||||||
<Layout noPadding justifyItems="center">
|
<Layout noPadding justifyItems="center">
|
||||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<Heading textAlign="center">Sign in to {company}</Heading>
|
<Heading textAlign="center">Log in to {company}</Heading>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<GoogleButton />
|
<GoogleButton />
|
||||||
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
|
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
|
||||||
{/if}
|
{/if}
|
||||||
<Divider noGrid />
|
<Divider />
|
||||||
<Layout gap="XS" noPadding>
|
<FancyForm bind:this={form}>
|
||||||
<Body size="S" textAlign="center">Sign in with email</Body>
|
<FancyInput
|
||||||
<Input label="Email" bind:value={username} />
|
validate={x => !x && "Please enter your work email"}
|
||||||
<Input
|
label="Your work email"
|
||||||
|
value={username}
|
||||||
|
on:change={e => (username = e.detail)}
|
||||||
|
/>
|
||||||
|
<FancyInput
|
||||||
|
disabled
|
||||||
|
label="Work email"
|
||||||
|
value={username}
|
||||||
|
on:change={e => (username = e.detail)}
|
||||||
|
/>
|
||||||
|
<FancyInput
|
||||||
|
label="Work email"
|
||||||
|
value={username}
|
||||||
|
on:change={e => (username = e.detail)}
|
||||||
|
/>
|
||||||
|
<FancyInput
|
||||||
|
validate={x => !x && "Please enter your password"}
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
on:change
|
value={password}
|
||||||
bind:value={password}
|
on:change={e => (password = e.detail)}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</FancyForm>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding justifyItems="center">
|
||||||
<Button cta disabled={!username && !password} on:click={login}
|
<div>
|
||||||
>Sign in to {company}</Button
|
<Button cta on:click={login}>
|
||||||
>
|
Log in to {company}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -103,14 +127,13 @@
|
||||||
By using Budibase Cloud
|
By using Budibase Cloud
|
||||||
<br />
|
<br />
|
||||||
you are agreeing to our
|
you are agreeing to our
|
||||||
<Link href="https://budibase.com/eula" target="_blank"
|
<Link href="https://budibase.com/eula" target="_blank">
|
||||||
>License Agreement</Link
|
License Agreement
|
||||||
>
|
</Link>
|
||||||
</Body>
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</TestimonialPage>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.login {
|
.login {
|
||||||
|
|
Loading…
Reference in New Issue