1
0
Fork 0
mirror of synced 2024-10-01 09:38:55 +13:00

Working commit

This commit is contained in:
Dean 2023-03-13 12:33:16 +00:00
parent fa6f5caa75
commit 5ce52cad06
21 changed files with 721 additions and 182 deletions

View file

@ -0,0 +1,133 @@
<script>
import ActionButton from "../../ActionButton/ActionButton.svelte"
import { uuid } from "../../helpers"
import Icon from "../../Icon/Icon.svelte"
import { createEventDispatcher } from "svelte"
export let value = null //support multi?
export let title = "Upload file"
export let disabled = false
export let extensions = "*"
export let handleFileTooLarge = null
export let handleTooManyFiles = null
export let fileSizeLimit = BYTES_IN_MB * 20 // Centralise
export let id = null
const dispatch = createEventDispatcher()
//Is this necessary?
const fieldId = id || uuid()
// Centralise all in new file section?
const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000
let fileInput
// $: file = value[0] || null
// const imageExtensions = [
// "png",
// "tiff",
// "gif",
// "raw",
// "jpg",
// "jpeg",
// "svg",
// "bmp",
// "jfif",
// ]
// Should support only 1 file for now.
// Files[0]
//What is the limit? 50mb?
async function processFileList(fileList) {
if (
handleFileTooLarge &&
Array.from(fileList).some(file => file.size >= fileSizeLimit)
) {
handleFileTooLarge(fileSizeLimit, value)
return
}
const fileCount = fileList.length + value.length
if (handleTooManyFiles && maximum && fileCount > maximum) {
handleTooManyFiles(maximum)
return
}
if (processFiles) {
const processedFiles = await processFiles(fileList)
const newValue = [...value, ...processedFiles]
dispatch("change", newValue)
selectedImageIdx = newValue.length - 1
} else {
dispatch("change", fileList)
}
}
function handleFile(evt) {
console.log("Hello ", evt.target.files[0])
dispatch("change", evt.target.files[0])
//processFileList(evt.target.files)
}
function clearFile() {
dispatch("change", null)
}
</script>
<input
id={fieldId}
{disabled}
type="file"
accept={extensions}
bind:this={fileInput}
on:change={handleFile}
/>
<div class="field">
{#if value}
<div class="file-view">
<!-- <img alt="" src={value.url} /> -->
<div class="filename">{value.name}</div>
{#if value.size}
<div class="filesize">
{#if value.size <= BYTES_IN_MB}
{`${value.size / BYTES_IN_KB} KB`}
{:else}
{`${value.size / BYTES_IN_MB} MB`}
{/if}
</div>
{/if}
{#if !disabled}
<div class="delete-button" on:click={clearFile}>
<Icon name="Close" size="XS" />
</div>
{/if}
</div>
{/if}
<ActionButton on:click={fileInput.click()}>{title}</ActionButton>
</div>
<style>
.field {
display: flex;
gap: var(--spacing-m);
}
.file-view {
display: flex;
gap: var(--spacing-l);
align-items: center;
border: 1px solid var(--spectrum-alias-border-color);
border-radius: var(--spectrum-global-dimension-size-50);
padding: 0px var(--spectrum-alias-item-padding-m);
}
input[type="file"] {
display: none;
}
.delete-button {
cursor: pointer;
}
</style>

View file

@ -13,3 +13,4 @@ export { default as CoreDropzone } from "./Dropzone.svelte"
export { default as CoreStepper } from "./Stepper.svelte"
export { default as CoreRichTextField } from "./RichTextField.svelte"
export { default as CoreSlider } from "./Slider.svelte"
export { default as CoreFile } from "./File.svelte"

View file

@ -0,0 +1,23 @@
<script>
import Field from "./Field.svelte"
import { CoreFile } from "./Core"
import { createEventDispatcher } from "svelte"
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
export let title = null
export let value = null
export let tooltip = null
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {error} {tooltip}>
<CoreFile {error} {disabled} {title} {value} on:change={onChange} />
</Field>

View file

@ -78,6 +78,7 @@ export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
export { default as Slider } from "./Form/Slider.svelte"
export { default as Accordion } from "./Accordion/Accordion.svelte"
export { default as File } from "./Form/File.svelte"
// Renderers
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"

View file

@ -1,17 +1,17 @@
<!doctype html>
<html class="spectrum spectrum--medium spectrum--darkest" lang="en" dir="ltr">
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Budibase</title>
<link rel='icon' href='/src/favicon.png'>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
rel="stylesheet" />
</head>
<body id="app">
<script type="module" src='/src/main.js'></script>
</body>
</html>

View file

@ -4,6 +4,7 @@
import { onMount } from "svelte"
import { CookieUtils, Constants } from "@budibase/frontend-core"
import { API } from "api"
import Branding from "./Branding.svelte"
let loaded = false
@ -146,6 +147,9 @@
}
</script>
<!--Portal branding overrides -->
<Branding />
{#if loaded}
<slot />
{/if}

View file

@ -64,99 +64,106 @@
</script>
<svelte:window on:keydown={handleKeydown} />
<TestimonialPage>
<Layout gap="L" noPadding>
<Layout justifyItems="center" noPadding>
{#if loaded}
<img alt="logo" src={$organisation.logoUrl || Logo} />
{/if}
<Heading size="M">Log in to Budibase</Heading>
</Layout>
<Layout gap="S" noPadding>
{#if loaded && ($organisation.google || $organisation.oidc)}
<FancyForm>
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
<GoogleButton />
</FancyForm>
{/if}
{#if loaded}
<TestimonialPage enabled={$organisation.testimonialsEnabled}>
<Layout gap="L" noPadding>
<Layout justifyItems="center" noPadding>
{#if loaded}
<img alt="logo" src={$organisation.logoUrl || Logo} />
{/if}
<Heading size="M">
{$organisation.loginHeading || "Log in to Budibase"}
</Heading>
</Layout>
<Layout gap="S" noPadding>
{#if loaded && ($organisation.google || $organisation.oidc)}
<FancyForm>
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
<GoogleButton />
</FancyForm>
{/if}
{#if !$organisation.isSSOEnforced}
<Divider />
<FancyForm bind:this={form}>
<FancyInput
label="Your work email"
value={formData.username}
on:change={e => {
formData = {
...formData,
username: e.detail,
}
}}
validate={() => {
let fieldError = {
username: !formData.username
? "Please enter a valid email"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.username}
/>
<FancyInput
label="Password"
value={formData.password}
type="password"
on:change={e => {
formData = {
...formData,
password: e.detail,
}
}}
validate={() => {
let fieldError = {
password: !formData.password
? "Please enter your password"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.password}
/>
</FancyForm>
{/if}
</Layout>
{#if !$organisation.isSSOEnforced}
<Divider />
<FancyForm bind:this={form}>
<FancyInput
label="Your work email"
value={formData.username}
on:change={e => {
formData = {
...formData,
username: e.detail,
}
}}
validate={() => {
let fieldError = {
username: !formData.username
? "Please enter a valid email"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.username}
/>
<FancyInput
label="Password"
value={formData.password}
type="password"
on:change={e => {
formData = {
...formData,
password: e.detail,
}
}}
validate={() => {
let fieldError = {
password: !formData.password
? "Please enter your password"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.password}
/>
</FancyForm>
<Layout gap="XS" noPadding justifyItems="center">
<Button
size="L"
cta
disabled={Object.keys(errors).length > 0}
on:click={login}
>
{$organisation.loginButton || `Log in to {company}`}
</Button>
</Layout>
<Layout gap="XS" noPadding justifyItems="center">
<div class="user-actions">
<ActionButton size="L" quiet on:click={() => $goto("./forgot")}>
Forgot password?
</ActionButton>
</div>
</Layout>
{/if}
{#if cloud && $organisation.licenceAgreementEnabled}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />
you are agreeing to our
<Link
href="https://budibase.com/eula"
target="_blank"
secondary={true}
>
License Agreement
</Link>
</Body>
{/if}
</Layout>
{#if !$organisation.isSSOEnforced}
<Layout gap="XS" noPadding justifyItems="center">
<Button
size="L"
cta
disabled={Object.keys(errors).length > 0}
on:click={login}
>
Log in to {company}
</Button>
</Layout>
<Layout gap="XS" noPadding justifyItems="center">
<div class="user-actions">
<ActionButton size="L" quiet on:click={() => $goto("./forgot")}>
Forgot password?
</ActionButton>
</div>
</Layout>
{/if}
{#if cloud}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />
you are agreeing to our
<Link href="https://budibase.com/eula" target="_blank" secondary={true}>
License Agreement
</Link>
</Body>
{/if}
</Layout>
</TestimonialPage>
</TestimonialPage>
{/if}
<style>
.user-actions {

View file

@ -4,53 +4,328 @@
Heading,
Body,
Divider,
Label,
Dropzone,
File,
notifications,
Tags,
Tag,
Button,
Toggle,
Input,
Label,
} from "@budibase/bbui"
import { auth, organisation } from "stores/portal"
import { auth, organisation, licensing, admin } from "stores/portal"
import { API } from "api"
import { onMount } from "svelte"
$: logo: $organisation.logoUrl
? { url: $organisation.logoUrl, type: "image", name: "Logo" }
: null,
async function uploadLogo(file) {
try {
let data = new FormData()
data.append("file", file)
await API.uploadLogo(data)
} catch (error) {
notifications.error("Error uploading logo")
let loaded = false
let saving = false
let logoFile = null
let faviconFile = null
let config = {}
let updated = false
$: onConfigUpdate(config)
const onConfigUpdate = config => {
if (!loaded || updated) {
return
}
updated = true
console.log("config updated ", config)
}
$: logo = config.logoUrl
? { url: config.logoUrl, type: "image", name: "Logo" }
: null
$: favicon = config.faviconUrl
? { url: config.logoUrl, type: "image", name: "Favicon" }
: null
async function uploadLogo(file) {
let response = {}
try {
let data = new FormData()
data.append("file", file)
response = await API.uploadLogo(data)
} catch (error) {
notifications.error("Error uploading logo")
}
return response
}
// Limit file types
// PNG, GIF, or ICO?
async function uploadFavicon(file) {
let response = {}
try {
let data = new FormData()
data.append("file", file)
response = await API.uploadFavicon(data)
} catch (error) {
notifications.error("Error uploading favicon")
}
return response
}
async function saveConfig() {
saving = true
console.log("Save Config")
if (logoFile) {
const logoResp = await uploadLogo(logoFile)
if (logoResp.url) {
config = {
...config,
logoUrl: logoResp.url,
}
} else {
//would have to delete
}
}
if (faviconFile) {
const faviconResp = await uploadFavicon(faviconFile)
if (faviconResp.url) {
config = {
...config,
faviconUrl: faviconResp.url,
}
}
}
console.log("SAVE CONFIG ", config)
try {
// Update settings
await organisation.save(config)
await organisation.init()
notifications.success("Branding settings updated")
} catch (e) {
console.error("Branding updated failed", e)
notifications.error("Branding updated failed")
}
saving = false
}
onMount(async () => {
await organisation.init()
const {
faviconUrl,
logoUrl,
platformTitle,
emailBrandingEnabled,
appFooterEnabled,
loginHeading,
loginButton,
licenceAgreementEnabled,
testimonialsEnabled,
} = $organisation
config = {
faviconUrl,
logoUrl,
platformTitle,
emailBrandingEnabled,
appFooterEnabled,
loginHeading,
loginButton,
licenceAgreementEnabled,
testimonialsEnabled,
}
loaded = true
})
</script>
{#if $auth.isAdmin}
{#if $auth.isAdmin && loaded}
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Branding</Heading>
<Body />
<div class="title">
<Heading size="M">Branding</Heading>
{#if !$licensing.isBusinessPlan}
<Tags>
<Tag icon="LockClosed">Business</Tag>
</Tags>
{/if}
</div>
<Body>Remove all Budibase branding and use your own.</Body>
</Layout>
<Divider />
<div class="fields">
<div class="field logo">
<div class="branding fields">
<div class="field">
<Label size="L">Logo</Label>
<div class="file">
<Dropzone
value={[logo]}
on:change={e => {
if (!e.detail || e.detail.length === 0) {
logo = null
} else {
logo = e.detail[0]
}
}}
/>
<File
title="Upload image"
on:change={e => {
console.log("Updated Logo")
let clone = { ...config }
if (e.detail) {
logoFile = e.detail
} else {
clone.logoUrl = ""
}
config = clone
}}
value={logo}
/>
</div>
<div class="field">
<Label size="L">Favicon</Label>
<File
title="Upload image"
on:change={e => {
console.log("Updated Favicon")
let clone = { ...config }
if (e.detail) {
faviconFile = e.detail
} else {
clone.faviconUrl = ""
}
config = clone
}}
value={favicon}
/>
</div>
<div class="field">
<Label size="L">Title</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.platformTitle = e.detail ? e.detail : ""
config = clone
}}
value={config.platformTitle || "Budibase"}
/>
</div>
<div>
<Toggle
text={"Remove Buidbase brand from emails"}
on:change={e => {
let clone = { ...config }
clone.emailBrandingEnabled = !e.detail
config = clone
}}
value={!config.emailBrandingEnabled}
/>
<Toggle
text={"Remove Budibase footer from apps"}
on:change={e => {
let clone = { ...config }
clone.appFooterEnabled = !e.detail
config = clone
}}
value={!config.appFooterEnabled}
/>
</div>
</div>
<!-- Should this be displayed? -->
{#if !$admin.cloud}
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">Login page (Self host)</Heading>
<Body>You can only customise your login page in self host</Body>
</Layout>
<div class="login">
<div class="fields">
<div class="field">
<Label size="L">Header</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.loginHeading = e.detail ? e.detail : ""
config = clone
}}
value={config.loginHeading || "Log in to Budibase"}
/>
</div>
<div class="field">
<Label size="L">Button</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.loginButton = e.detail ? e.detail : ""
config = clone
}}
value={config.loginButton || "Log in to Budibase"}
/>
</div>
<div>
<Toggle
text={"Remove customer testimonials"}
on:change={e => {
let clone = { ...config }
clone.testimonialsEnabled = !e.detail
config = clone
}}
value={!config.testimonialsEnabled}
/>
<Toggle
text={"Remove licence agreement"}
on:change={e => {
let clone = { ...config }
clone.licenceAgreementEnabled = !e.detail
config = clone
}}
value={!config.licenceAgreementEnabled}
/>
</div>
</div>
</div>
{/if}
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">Application previews</Heading>
<Body>Customise the meta tags on your app preview</Body>
</Layout>
<div class="app-previews">
<div class="fields">
<div class="field">
<!-- <Label size="L">Header</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.loginHeading = e.detail ? e.detail : ""
config = clone
}}
value={config.loginHeading || "Log in to Budibase"}
/> -->
</div>
</div>
</div>
<div>
<Button on:click={saveConfig} cta disabled={saving || !updated}>
Save
</Button>
</div>
</Layout>
{/if}
<style>
.title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: var(--spacing-m);
}
.branding,
.login {
width: 60%;
}
.fields {
display: grid;
grid-gap: var(--spacing-m);
}
.field {
display: grid;
grid-template-columns: 80px 1fr;
grid-gap: var(--spacing-l);
align-items: center;
}
</style>

View file

@ -28,21 +28,21 @@
company: $organisation.company,
platformUrl: $organisation.platformUrl,
analyticsEnabled: $organisation.analyticsEnabled,
logo: $organisation.logoUrl
? { url: $organisation.logoUrl, type: "image", name: "Logo" }
: null,
// logo: $organisation.logoUrl
// ? { url: $organisation.logoUrl, type: "image", name: "Logo" }
// : null,
})
let loading = false
async function uploadLogo(file) {
try {
let data = new FormData()
data.append("file", file)
await API.uploadLogo(data)
} catch (error) {
notifications.error("Error uploading logo")
}
}
// async function uploadLogo(file) {
// try {
// let data = new FormData()
// data.append("file", file)
// await API.uploadLogo(data)
// } catch (error) {
// notifications.error("Error uploading logo")
// }
// }
async function saveConfig() {
loading = true
@ -87,21 +87,7 @@
<Label size="L">Org. name</Label>
<Input thin bind:value={$values.company} />
</div>
<!-- <div class="field logo">
<Label size="L">Logo</Label>
<div class="file">
<Dropzone
value={[$values.logo]}
on:change={e => {
if (!e.detail || e.detail.length === 0) {
$values.logo = null
} else {
$values.logo = e.detail[0]
}
}}
/>
</div>
</div> -->
{#if !$admin.cloud}
<div class="field">
<Label
@ -137,10 +123,4 @@
grid-gap: var(--spacing-l);
align-items: center;
}
.file {
max-width: 30ch;
}
.logo {
align-items: start;
}
</style>

View file

@ -13,6 +13,7 @@ export const createLicensingStore = () => {
license: undefined,
isFreePlan: true,
isEnterprisePlan: true,
isBusinessPlan: true,
// features
groupsEnabled: false,
backupsEnabled: false,
@ -57,6 +58,7 @@ export const createLicensingStore = () => {
const planType = license?.plan.type
const isEnterprisePlan = planType === Constants.PlanType.ENTERPRISE
const isFreePlan = planType === Constants.PlanType.FREE
const isBusinessPlan = planType === Constants.PlanType.BUSINESS
const groupsEnabled = license.features.includes(
Constants.Features.USER_GROUPS
)
@ -79,6 +81,7 @@ export const createLicensingStore = () => {
license,
isEnterprisePlan,
isFreePlan,
isBusinessPlan,
groupsEnabled,
backupsEnabled,
environmentVariablesEnabled,

View file

@ -5,7 +5,19 @@ import _ from "lodash"
const DEFAULT_CONFIG = {
platformUrl: "",
logoUrl: undefined,
faviconUrl: undefined,
emailBrandingEnabled: true,
appFooterEnabled: true,
// Self host only
testimonialsEnabled: true,
licenceAgreementEnabled: true,
platformTitle: "Budibase",
loginHeading: undefined,
loginButton: undefined,
docsUrl: undefined,
company: "Budibase",
oidc: undefined,

View file

@ -73,6 +73,18 @@ export const buildConfigEndpoints = API => ({
})
},
/**
* Updates the company favicon for the environment.
* @param data the favicon form data
*/
uploadFavicon: async data => {
return await API.post({
url: "/api/global/configs/upload/settings/faviconUrl",
body: data,
json: false,
})
},
/**
* Uploads a logo for an OIDC provider.
* @param name the name of the OIDC provider

View file

@ -5,6 +5,8 @@
import Covanta from "../../assets/covanta.png"
import Schnellecke from "../../assets/schnellecke.png"
export let enabled = true
const testimonials = [
{
text: "Budibase was the only solution that checked all the boxes for Covanta. Covanta expects to realize $3.2MM in savings due to the elimination of redundant data entry.",
@ -33,23 +35,25 @@
<SplitPage>
<slot />
<div class="wrapper" slot="right">
<div class="testimonial">
<Layout noPadding gap="S">
<img
width={testimonial.imageSize}
alt="a-happy-budibase-user"
src={testimonial.image}
/>
<div class="text">
"{testimonial.text}"
</div>
<div class="author">
<div class="name">{testimonial.name}</div>
<div class="company">{testimonial.role}</div>
</div>
</Layout>
</div>
<div class:wrapper={enabled} slot="right">
{#if enabled}
<div class="testimonial">
<Layout noPadding gap="S">
<img
width={testimonial.imageSize}
alt="a-happy-budibase-user"
src={testimonial.image}
/>
<div class="text">
"{testimonial.text}"
</div>
<div class="author">
<div class="name">{testimonial.name}</div>
<div class="company">{testimonial.role}</div>
</div>
</Layout>
</div>
{/if}
</div>
</SplitPage>

View file

@ -105,14 +105,16 @@ export const serveApp = async function (ctx: any) {
if (!env.isJest()) {
const App = require("./templates/BudibaseApp.svelte").default
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
console.log(appInfo)
const { head, html, css } = App.render({
metaImage:
"https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
title: appInfo.name,
title: appInfo.name, //Replace Title here?
production: env.isProd(),
appId,
clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version),
usedPlugins: plugins,
favicon: objectStore.getGlobalFileUrl("settings", "faviconUrl"),
})
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)

View file

@ -22,7 +22,6 @@
<meta property="og:title" content="{title} - built with Budibase" />
<meta property="og:type" content="website" />
<meta property="og:image" content={metaImage} />
<title>{title}</title>
<link rel="icon" type="image/png" href={favicon} />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />

View file

@ -27,6 +27,20 @@ export interface SettingsInnerConfig {
company?: string
logoUrl?: string // Populated on read
logoUrlEtag?: string
faviconUrl?: string
faviconUrlEtag?: string
emailBrandingEnabled?: boolean
// Self host only
appFooterEnabled?: boolean
testimonialsEnabled?: boolean
licenceAgreementEnabled?: boolean
platformTitle?: string
loginHeading?: string
loginButton?: string
uniqueTenantId?: string
analyticsEnabled?: boolean
isSSOEnforced?: boolean

View file

@ -285,6 +285,14 @@ export async function publicSettings(
)
}
if (config.faviconUrl && config.faviconUrl !== "") {
config.faviconUrl = objectStore.getGlobalFileUrl(
"settings",
"faviconUrl",
config.faviconUrl
)
}
// google
const googleConfig = await configs.getGoogleConfig()
const preActivated = googleConfig?.activated == null

View file

@ -0,0 +1,31 @@
{{#if enableEmailBranding}}
<tr>
<td cellpadding="0" cellspacing="0">
<table
align="center"
width="570"
cellpadding="0"
cellspacing="0"
role="presentation"
>
<tbody>
<tr>
<td
style="padding:0 35px;vertical-align: middle;"
cellpadding="0"
cellspacing="0"
>
<img
width="32px"
style="margin-right: 16px;"
alt="Budibase Logo"
src="https://i.imgur.com/Xhdt1YP.png"
/>
<h2 style="margin: 0px">Budibase</h2>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
{{/if}}

View file

@ -21,6 +21,8 @@ export const EmailTemplates = {
join(__dirname, "welcome.hbs")
),
[EmailTemplatePurpose.CUSTOM]: readStaticFile(join(__dirname, "custom.hbs")),
//Core wrapper
["branding"]: readStaticFile(join(__dirname, "core.hbs")),
}
export function addBaseTemplates(templates: Template[], type?: string) {

View file

@ -1,6 +1,6 @@
import env from "../environment"
import { EmailTemplatePurpose, TemplateType } from "../constants"
import { getTemplateByPurpose } from "../constants/templates"
import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates"
import { getResetPasswordCode, getInviteCode } from "./redis"
@ -108,30 +108,53 @@ async function buildEmail(
let [base, body] = await Promise.all([
getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE),
getTemplateByPurpose(TYPE, purpose),
//getTemplateByPurpose(TYPE, "branding"), //should generalise to 'branding'
])
if (!base || !body) {
let branding = EmailTemplates["branding"]
if (!base || !body || !branding) {
throw "Unable to build email, missing base components"
}
base = base.contents
body = body.contents
//branding = branding.contents
let name = user ? user.name : undefined
if (user && !name && user.firstName) {
name = user.lastName ? `${user.firstName} ${user.lastName}` : user.firstName
}
context = {
...context,
...context, //enableEmailBranding
contents,
email,
name,
user: user || {},
}
body = await processString(body, context)
// this should now be the complete email HTML
const core = branding + body
body = await processString(core, context)
// Conditional elements
// branding = await processString(branding, {
// ...context,
// body,
// })
// this should now be the core email HTML
return processString(base, {
...context,
body,
body, //: branding, // pass as body as usual
})
// body = await processString(body, context)
// // this should now be the coree email HTML
// return processString(base, {
// ...context,
// body,
// })
}
/**

View file

@ -25,6 +25,11 @@ export async function getSettingsTemplateContext(
[InternalTemplateBinding.CURRENT_DATE]: new Date().toISOString(),
[InternalTemplateBinding.CURRENT_YEAR]: new Date().getFullYear(),
}
// Need to be careful with the binding as it shouldn't be surfacable
// Also default to false if not explicit
context["enableEmailBranding"] = settings.emailBrandingEnabled
// attach purpose specific context
switch (purpose) {
case EmailTemplatePurpose.PASSWORD_RECOVERY: