Merge branch 'master' into feature/fix-events-modal

This commit is contained in:
kevmodrome 2020-06-04 09:28:35 +02:00
commit c2f1c792fe
30 changed files with 371 additions and 153 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -3,6 +3,7 @@ const apiCall = method => async (url, body) => {
method: method, method: method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-user-agent": "Budibase Builder",
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
}) })
@ -14,11 +15,11 @@ const apiCall = method => async (url, body) => {
return response return response
} }
const post = apiCall("POST") export const post = apiCall("POST")
const get = apiCall("GET") export const get = apiCall("GET")
const patch = apiCall("PATCH") export const patch = apiCall("PATCH")
const del = apiCall("DELETE") export const del = apiCall("DELETE")
const put = apiCall("PUT") export const put = apiCall("PUT")
export default { export default {
post, post,

View File

@ -1,3 +1,5 @@
import { get } from "builderStore/api"
/** /**
* Fetches the definitions for component library components. This includes * Fetches the definitions for component library components. This includes
* their props and other metadata from components.json. * their props and other metadata from components.json.
@ -6,7 +8,7 @@
export const fetchComponentLibDefinitions = async appId => { export const fetchComponentLibDefinitions = async appId => {
const LIB_DEFINITION_URL = `/${appId}/components/definitions` const LIB_DEFINITION_URL = `/${appId}/components/definitions`
try { try {
const libDefinitionResponse = await fetch(LIB_DEFINITION_URL) const libDefinitionResponse = await get(LIB_DEFINITION_URL)
return await libDefinitionResponse.json() return await libDefinitionResponse.json()
} catch (err) { } catch (err) {
console.error(`Error fetching component definitions for ${appId}`, err) console.error(`Error fetching component definitions for ${appId}`, err)

View File

@ -5,6 +5,7 @@
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/" import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/"
import { getContext } from "svelte" import { getContext } from "svelte"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
import { post } from "builderStore/api"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
@ -33,15 +34,7 @@
const data = { name, description } const data = { name, description }
loading = true loading = true
try { try {
const response = await fetch("/api/applications", { const response = await post("/api/applications", data)
method: "POST", // *GET, POST, PUT, DELETE, etc.
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
const res = await response.json() const res = await response.json()

View File

@ -7,7 +7,7 @@
<div class="uk-margin block-field"> <div class="uk-margin block-field">
<div class="uk-form-controls"> <div class="uk-form-controls">
<select class="budibase__input" on:change {value}> <select class="budibase__input" on:change {value}>
<option value=""></option> <option value="" />
{#each $backendUiStore.models as model} {#each $backendUiStore.models as model}
<option value={model._id}>{model.name}</option> <option value={model._id}>{model.name}</option>
{/each} {/each}

View File

@ -16,7 +16,7 @@
} }
</script> </script>
{#if panelDefinition.length > 0} {#if panelDefinition && panelDefinition.length > 0}
{#each panelDefinition as definition} {#each panelDefinition as definition}
{#if propExistsOnComponentDef(definition.key)} {#if propExistsOnComponentDef(definition.key)}
<PropertyControl <PropertyControl

View File

@ -267,7 +267,21 @@ export default {
"A component that automatically generates a login screen for your app.", "A component that automatically generates a login screen for your app.",
icon: "ri-login-box-fill", icon: "ri-login-box-fill",
children: [], children: [],
properties: { design: { ...all } }, properties: {
design: { ...all },
settings: [
{
label: "Name",
key: "name",
control: Input,
},
{
label: "Logo",
key: "logo",
control: Input,
},
],
},
}, },
{ {
name: "Table", name: "Table",
@ -303,7 +317,28 @@ export default {
icon: "ri-bar-chart-fill", icon: "ri-bar-chart-fill",
properties: { properties: {
design: { ...all }, design: { ...all },
settings: [{ label: "Model", key: "model", control: ModelSelect }], settings: [
{ label: "Model", key: "model", control: ModelSelect },
{
label: "Chart Type",
key: "type",
control: OptionSelect,
options: [
"column2d",
"column3d",
"line",
"area2d",
"bar2d",
"bar3d",
"pie2d",
"pie3d",
"doughnut2d",
"doughnut3d",
"pareto2d",
"pareto3d",
],
},
],
}, },
children: [], children: [],
}, },

View File

@ -7,7 +7,7 @@
<div class="uk-margin block-field"> <div class="uk-margin block-field">
<div class="uk-form-controls"> <div class="uk-form-controls">
<select class="budibase__input" bind:value> <select class="budibase__input" bind:value>
<option value=""></option> <option value="" />
{#each $backendUiStore.models as model} {#each $backendUiStore.models as model}
<option value={model}>{model.name}</option> <option value={model}>{model.name}</option>
{/each} {/each}

View File

@ -1,6 +1,7 @@
<script> <script>
import Modal from "svelte-simple-modal" import Modal from "svelte-simple-modal"
import { store } from "builderStore" import { store } from "builderStore"
import { get } from "builderStore/api"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
import { isActive, goto, layout } from "@sveltech/routify" import { isActive, goto, layout } from "@sveltech/routify"
@ -14,7 +15,7 @@
let promise = getPackage() let promise = getPackage()
async function getPackage() { async function getPackage() {
const res = await fetch(`/api/${application}/appPackage`) const res = await get(`/api/${application}/appPackage`)
const pkg = await res.json() const pkg = await res.json()
if (res.ok) { if (res.ok) {

View File

@ -5,14 +5,14 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import { get } from "builderStore/api"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte"
let promise = getApps() let promise = getApps()
async function getApps() { async function getApps() {
const res = await fetch(`/api/applications`) const res = await get("/api/applications")
const json = await res.json() const json = await res.json()
if (res.ok) { if (res.ok) {

View File

@ -56,8 +56,30 @@ export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => {
} }
} }
function click(e) {
const x = e.target.closest("a")
const y = x && x.getAttribute("href")
if (
e.ctrlKey ||
e.metaKey ||
e.altKey ||
e.shiftKey ||
e.button ||
e.defaultPrevented
)
return
const target = x.target || "_self"
if (!y || target !== "_self" || x.host !== location.host) return
e.preventDefault()
route(y)
}
addEventListener("popstate", route) addEventListener("popstate", route)
addEventListener("pushstate", route) addEventListener("pushstate", route)
addEventListener("click", click)
return route return route
} }

View File

@ -23,10 +23,11 @@ export const bbFactory = ({
} }
const apiCall = method => (url, body) => const apiCall = method => (url, body) =>
fetch(relativeUrl(url), { fetch(url, {
method: method, method: method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-user-agent": "Budibase Builder",
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
}) })

View File

@ -16,6 +16,3 @@ PORT=4001
# error level for koa-pino # error level for koa-pino
LOG_LEVEL=error LOG_LEVEL=error
# Budibase app directory
BUDIBASE_DIR=~/.budibase

View File

@ -5,8 +5,10 @@ const newid = require("../../db/newid")
const env = require("../../environment") const env = require("../../environment")
const instanceController = require("./instance") const instanceController = require("./instance")
const { resolve, join } = require("path") const { resolve, join } = require("path")
const { copy, readJSON, writeJSON, exists } = require("fs-extra") const { copy, exists, readFile, writeFile } = require("fs-extra")
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
const { exec } = require("child_process") const { exec } = require("child_process")
const sqrl = require("squirrelly")
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const db = new CouchDB(ClientDb.name(env.CLIENT_ID))
@ -72,7 +74,7 @@ const createEmptyAppPackage = async (ctx, app) => {
"appDirectoryTemplate" "appDirectoryTemplate"
) )
const appsFolder = env.BUDIBASE_DIR const appsFolder = budibaseAppsDir()
const newAppFolder = resolve(appsFolder, app._id) const newAppFolder = resolve(appsFolder, app._id)
if (await exists(newAppFolder)) { if (await exists(newAppFolder)) {
@ -82,16 +84,27 @@ const createEmptyAppPackage = async (ctx, app) => {
await copy(templateFolder, newAppFolder) await copy(templateFolder, newAppFolder)
const packageJsonPath = join(appsFolder, app._id, "package.json") await updateJsonFile(join(appsFolder, app._id, "package.json"), {
const packageJson = await readJSON(packageJsonPath) name: npmFriendlyAppName(app.name),
})
packageJson.name = npmFriendlyAppName(app.name) await updateJsonFile(
join(appsFolder, app._id, "pages", "main", "page.json"),
await writeJSON(packageJsonPath, packageJson) app
)
await updateJsonFile(
join(appsFolder, app._id, "pages", "unauthenticated", "page.json"),
app
)
return newAppFolder return newAppFolder
} }
const updateJsonFile = async (filePath, app) => {
const json = await readFile(filePath, "utf8")
const newJson = sqrl.Render(json, app)
await writeFile(filePath, newJson, "utf8")
}
const runNpmInstall = async newAppFolder => { const runNpmInstall = async newAppFolder => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const cmd = `cd ${newAppFolder} && npm install` const cmd = `cd ${newAppFolder} && npm install`

View File

@ -10,12 +10,9 @@ exports.authenticate = async ctx => {
if (!username) ctx.throw(400, "Username Required.") if (!username) ctx.throw(400, "Username Required.")
if (!password) ctx.throw(400, "Password Required") if (!password) ctx.throw(400, "Password Required")
// TODO: Don't use this. It can't be relied on
const referer = ctx.request.headers.referer.split("/")
const appId = referer[3]
// find the instance that the user is associated with // find the instance that the user is associated with
const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const db = new CouchDB(ClientDb.name(env.CLIENT_ID))
const appId = ctx.params.appId
const app = await db.get(appId) const app = await db.get(appId)
const instanceId = app.userInstanceMap[username] const instanceId = app.userInstanceMap[username]

View File

@ -10,6 +10,12 @@ exports.fetch = async function(ctx) {
ctx.body = body.rows.map(row => row.doc) ctx.body = body.rows.map(row => row.doc)
} }
exports.find = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const model = await db.get(ctx.params.id)
ctx.body = model
}
exports.create = async function(ctx) { exports.create = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const newModel = { const newModel = {

View File

@ -13,7 +13,6 @@ exports.serveBuilder = async function(ctx) {
} }
exports.serveApp = async function(ctx) { exports.serveApp = async function(ctx) {
// TODO: update homedir stuff to wherever budi is run
// default to homedir // default to homedir
const appPath = resolve( const appPath = resolve(
budibaseAppsDir(), budibaseAppsDir(),
@ -26,7 +25,6 @@ exports.serveApp = async function(ctx) {
} }
exports.serveComponentLibrary = async function(ctx) { exports.serveComponentLibrary = async function(ctx) {
// TODO: update homedir stuff to wherever budi is run
// default to homedir // default to homedir
let componentLibraryPath = resolve( let componentLibraryPath = resolve(
budibaseAppsDir(), budibaseAppsDir(),

View File

@ -3,6 +3,6 @@ const controller = require("../controllers/auth")
const router = Router() const router = Router()
router.post("/api/authenticate", controller.authenticate) router.post("/:appId/api/authenticate", controller.authenticate)
module.exports = router module.exports = router

View File

@ -43,6 +43,7 @@ router
router router
.get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch) .get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch)
.get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find)
.post("/api/:instanceId/models", authorized(BUILDER), modelController.create) .post("/api/:instanceId/models", authorized(BUILDER), modelController.create)
// .patch("/api/:instanceId/models", controller.update) // .patch("/api/:instanceId/models", controller.update)
.delete( .delete(

View File

@ -22,6 +22,7 @@ exports.supertest = async () => {
exports.defaultHeaders = { exports.defaultHeaders = {
Accept: "application/json", Accept: "application/json",
Cookie: ["builder:token=test-admin-secret"], Cookie: ["builder:token=test-admin-secret"],
"x-user-agent": "Budibase Builder",
} }
exports.createModel = async (request, instanceId, model) => { exports.createModel = async (request, instanceId, model) => {
@ -175,8 +176,7 @@ const createUserWithPermissions = async (
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const loginResult = await request const loginResult = await request
.post(`/api/authenticate`) .post(`/${designDoc.metadata.applicationId}/api/authenticate`)
.set("Referer", `http://localhost:4001/${designDoc.metadata.applicationId}`)
.send({ username, password }) .send({ username, password })
// returning necessary request headers // returning necessary request headers

View File

@ -13,23 +13,31 @@ module.exports = async (ctx, next) => {
return return
} }
if (ctx.cookies.get("builder:token") === env.ADMIN_SECRET) { const appToken = ctx.cookies.get("budibase:token")
ctx.isAuthenticated = true const builderToken = ctx.cookies.get("builder:token")
ctx.isBuilder = true const isBuilderAgent = ctx.headers["x-user-agent"] === "Budibase Builder"
// all admin api access should auth with buildertoken and 'Budibase Builder user agent
const shouldAuthAsBuilder = isBuilderAgent && builderToken
if (shouldAuthAsBuilder) {
const builderTokenValid = builderToken === env.ADMIN_SECRET
ctx.isAuthenticated = builderTokenValid
ctx.isBuilder = builderTokenValid
await next() await next()
return return
} }
const token = ctx.cookies.get("budibase:token") if (!appToken) {
if (!token) {
ctx.isAuthenticated = false ctx.isAuthenticated = false
await next() await next()
return return
} }
try { try {
const jwtPayload = jwt.verify(token, ctx.config.jwtSecret) const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
ctx.user = { ctx.user = {
...jwtPayload, ...jwtPayload,

View File

@ -1,5 +1,5 @@
{ {
"name": "name", "name": "{{ name }}",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"author": "", "author": "",

View File

@ -1,5 +1,5 @@
{ {
"title": "Test App", "title": "{{ name }}",
"favicon": "./_shared/favicon.png", "favicon": "./_shared/favicon.png",
"stylesheets": [], "stylesheets": [],
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"], "componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],

View File

@ -1,20 +1,44 @@
{ {
"title": "Test App", "componentLibraries": [
"@budibase/standard-components",
"@budibase/materialdesign-components"
],
"title": "{{ name }}",
"favicon": "./_shared/favicon.png", "favicon": "./_shared/favicon.png",
"stylesheets": [], "stylesheets": [],
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"], "props": {
"props" : {
"_component": "@budibase/standard-components/container", "_component": "@budibase/standard-components/container",
"_children": [
{
"_id": "686c252d-dbf2-4e28-9078-414ba4719759",
"_component": "@budibase/standard-components/login",
"_styles": {
"normal": {},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"loginRedirect": "",
"usernameLabel": "Username",
"passwordLabel": "Password",
"loginButtonLabel": "Login",
"buttonClass": "",
"inputClass": "",
"_children": [], "_children": [],
"name": "{{ name }}",
"logo": ""
}
],
"_id": 1, "_id": 1,
"type": "div", "type": "div",
"_styles": { "_styles": {
"active": {}, "layout": {},
"hover": {}, "position": {}
"normal": {},
"selected": {}
}, },
"_code": "" "_code": "",
"className": "",
"onLoad": []
}, },
"_css": "", "_css": "",
"uiFunctions": "" "uiFunctions": ""

View File

@ -45,7 +45,7 @@ const copyClientLib = async (appPath, pageName) => {
const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => { const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pageName) const appPublicPath = publicPath(appPath, pageName)
const appRootPath = appId const appRootPath = rootPath(config, appId)
const stylesheetUrl = s => const stylesheetUrl = s =>
s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}` s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}`

View File

@ -59,6 +59,7 @@
"props": { "props": {
"logo": "asset", "logo": "asset",
"loginRedirect": "string", "loginRedirect": "string",
"name": "string",
"usernameLabel": { "usernameLabel": {
"type": "string", "type": "string",
"default": "Username" "default": "Username"

View File

@ -17,7 +17,7 @@
<button <button
bind:this={theButton} bind:this={theButton}
class={className} class="default"
disabled={disabled || false} disabled={disabled || false}
on:click={clickHandler}> on:click={clickHandler}>
{#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if} {#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if}
@ -25,23 +25,21 @@
<style> <style>
.default { .default {
font-family: inherit; align-items: center;
font-size: inherit; font-family: Inter;
padding: 0.4em; font-size: 16px;
margin: 0 0 0.5em 0; padding: 0px 16px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ccc; border-radius: 4px;
border-radius: 2px;
color: #000333;
outline: none; outline: none;
} height: 40px;
cursor: pointer;
.default:active { transition: all 0.2s ease 0s;
background-color: #f9f9f9; overflow: hidden;
} outline: none;
user-select: none;
.default:focus { white-space: nowrap;
border-color: #666; text-align: center;
} }
.border { .border {

View File

@ -8,19 +8,32 @@
let username let username
let password let password
let newModel = { let newModel = {
modelId: model._id, modelId: model,
} }
let store = _bb.store let store = _bb.store
let schema = {}
let modelDef = {}
$: fields = Object.keys(model.schema) $: if (model && model.length !== 0) {
fetchModel()
}
$: fields = Object.keys(schema)
async function fetchModel() {
const FETCH_MODEL_URL = `/api/${_instanceId}/models/${model}`
const response = await _bb.api.get(FETCH_MODEL_URL)
modelDef = await response.json()
schema = modelDef.schema
}
async function save() { async function save() {
const SAVE_RECORD_URL = `/api/${_instanceId}/records` const SAVE_RECORD_URL = `/api/${_instanceId}/${model}/records`
const response = await _bb.api.post(SAVE_RECORD_URL, newModel) const response = await _bb.api.post(SAVE_RECORD_URL, newModel)
const json = await response.json() const json = await response.json()
store.update(state => { store.update(state => {
state[model._id] = [...state[model._id], json] state[model._id] = [...state[model], json]
return state return state
}) })
} }
@ -45,24 +58,95 @@
} }
</script> </script>
<form class="uk-form" on:submit|preventDefault> <form class="form" on:submit|preventDefault>
<h4>{model.name}</h4> <div class="form-content">
<div>
{#each fields as field} {#each fields as field}
<div class="uk-margin"> <div class="form-item">
<label class="form-label" for="form-stacked-text">{field}</label> <label class="form-label" for="form-stacked-text">{field}</label>
<input <input
class="uk-input" class="input"
type={model.schema[field].type === 'string' ? 'text' : model.schema[field].type} placeholder={field}
type={schema[field].type === 'string' ? 'text' : schema[field].type}
on:change={handleInput(field)} /> on:change={handleInput(field)} />
</div> </div>
<hr />
{/each} {/each}
<div class="button-block">
<button on:click={save}>Submit Form</button>
</div>
</div> </div>
<button on:click={save}>SAVE</button>
</form> </form>
<style> <style>
.form {
align-items: center;
width: 100%;
}
.form-content {
margin-bottom: 20px;
justify-content: space-between;
align-items: baseline;
}
.input {
width: 600px;
height: 40px;
border-radius: 5px;
border: 1px solid #e6e6e6;
padding: 6px 12px 6px 12px;
font-size: 16px;
}
.form-item {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
}
.form-label { .form-label {
font-weight: bold; font-weight: bold;
margin-bottom: 8px;
}
hr {
border: 1px solid #fafafa;
margin: 20px 0px;
}
hr:nth-last-child(2) {
border: 1px solid #fff;
margin: 20px 0px;
}
.button-block {
display: flex;
justify-content: flex-end;
}
button {
font-family: Inter;
font-size: 16px;
padding: 0.4em;
box-sizing: border-box;
border-radius: 4px;
color: white;
background-color: #393c44;
outline: none;
width: 300px;
height: 40px;
cursor: pointer;
transition: all 0.2s ease 0s;
overflow: hidden;
outline: none;
user-select: none;
white-space: nowrap;
text-align: center;
}
button:hover {
background-color: white;
border-color: #393c44;
color: #393c44;
} }
</style> </style>

View File

@ -13,7 +13,7 @@
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
</script> </script>
<a href={_bb.relativeUrl(url)} bind:this={anchorElement} {target}>{text}</a> <a href={url} bind:this={anchorElement} {target}>{text}</a>
<style> <style>
.textDecoration { .textDecoration {

View File

@ -1,10 +1,9 @@
<script> <script>
import Button from "./Button.svelte" import Button from "./Button.svelte"
export let usernameLabel = "Username"
export let passwordLabel = "Password"
export let loginButtonLabel = "Login" export let loginButtonLabel = "Login"
export let logo = "" export let logo = ""
export let name = ""
export let buttonClass = "" export let buttonClass = ""
export let inputClass = "" export let inputClass = ""
@ -26,9 +25,16 @@
const login = async () => { const login = async () => {
loading = true loading = true
const response = await _bb.api.post("/api/authenticate", { const response = await fetch(_bb.relativeUrl("/api/authenticate"), {
body: JSON.stringify({
username, username,
password, password,
}),
headers: {
"Content-Type": "application/json",
"x-user-agent": "Budibase Builder",
},
method: "POST",
}) })
if (response.status === 200) { if (response.status === 200) {
@ -51,14 +57,23 @@
</div> </div>
{/if} {/if}
<h1 class="header-content">Log in to {name}</h1>
<div class="form-root"> <div class="form-root">
<div class="label">{usernameLabel}</div>
<div class="control"> <div class="control">
<input bind:value={username} type="text" class={_inputClass} /> <input
bind:value={username}
type="text"
placeholder="Username"
class={_inputClass} />
</div> </div>
<div class="label">{passwordLabel}</div>
<div class="control"> <div class="control">
<input bind:value={password} type="password" class={_inputClass} /> <input
bind:value={password}
type="password"
placeholder="Password"
class={_inputClass} />
</div> </div>
</div> </div>
@ -77,28 +92,42 @@
<style> <style>
.root { .root {
height: 100%; height: 100%;
display: grid; display: flex;
grid-template-columns: [left] 1fr [middle] auto [right] 1fr; flex-direction: column;
grid-template-rows: [top] 1fr [center] auto [bottom] 1fr; align-items: center;
justify-content: center;
} }
.content { .content {
grid-column-start: middle; display: flex;
grid-row-start: center; flex-direction: column;
width: 400px; align-items: center;
justify-content: center;
} }
.logo-container { .logo-container {
margin-bottom: 20px; margin-bottom: 10px;
} }
.logo-container > img { .logo-container > img {
max-width: 100%; max-width: 200px;
} }
.login-button-container { .login-button-container {
text-align: right; margin-top: 6px;
margin-top: 20px; max-width: 100%;
}
.header-content {
font-family: Inter;
font-weight: 700;
color: #1f1f1f;
font-size: 48px;
line-height: 72px;
margin-bottom: 30px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-feature-settings: "case" "rlig" "calt" 0;
} }
.incorrect-details-panel { .incorrect-details-panel {
@ -114,48 +143,55 @@
} }
.form-root { .form-root {
display: grid; display: flex;
grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/ flex-direction: column;
align-items: center;
width: 300px;
} }
.label {
grid-column-start: label;
padding: 5px 10px;
vertical-align: middle;
}
.control { .control {
grid-column-start: control; padding: 6px 0px;
padding: 5px 10px;
}
.default-input {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
width: 100%; width: 100%;
} }
.default-button { .default-input {
font-family: inherit; font-family: Inter;
font-size: inherit; font-size: 14px;
padding: 0.4em; color: #393c44;
padding: 2px 6px 2px 12px;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ccc; border: 0.5px solid #d8d8d8;
border-radius: 2px; border-radius: 4px;
color: #000333; width: 100%;
height: 40px;
transition: border-color 100ms ease-in 0s;
outline-color: #797979;
}
.default-button {
font-family: Inter;
font-size: 16px;
padding: 0.4em;
box-sizing: border-box;
border-radius: 4px;
color: white;
background-color: #393c44;
outline: none; outline: none;
width: 300px;
height: 40px;
cursor: pointer;
transition: all 0.2s ease 0s;
overflow: hidden;
outline: none;
user-select: none;
white-space: nowrap;
text-align: center;
} }
.default-button:active { .default-button:hover {
background-color: #f9f9f9; background-color: white;
} border-color: #393c44;
color: #393c44;
.default-button:focus {
border-color: #f9f9f9;
} }
</style> </style>