Final version of example, has save form and table of sales, supporting relationships and pagination all from Postgres.
This commit is contained in:
parent
4dc9bab2de
commit
c51352c9bf
|
@ -0,0 +1,43 @@
|
||||||
|
import Link from "next/link"
|
||||||
|
import Image from "next/image"
|
||||||
|
import { ReactNotifications } from "react-notifications-component"
|
||||||
|
|
||||||
|
function layout(props: any) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav className="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div id="navbar" className="navbar-menu">
|
||||||
|
<div className="logo">
|
||||||
|
<Image src="/bb-emblem.svg" width="50" height="50" />
|
||||||
|
</div>
|
||||||
|
<div className="navbar-start">
|
||||||
|
<Link href="/">
|
||||||
|
<a className="navbar-item">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<Link href="/save">
|
||||||
|
<a className="navbar-item">
|
||||||
|
Save
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="navbar-end">
|
||||||
|
<div className="navbar-item">
|
||||||
|
<div className="buttons">
|
||||||
|
<a className="button is-primary" href="https://budibase.readme.io/reference">
|
||||||
|
<strong>API Documentation</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<ReactNotifications />
|
||||||
|
{props.children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default layout
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Store } from "react-notifications-component"
|
||||||
|
|
||||||
|
const notifications = {
|
||||||
|
error: (error: string, title: string) => {
|
||||||
|
Store.addNotification({
|
||||||
|
container: "top-right",
|
||||||
|
type: "danger",
|
||||||
|
message: error,
|
||||||
|
title: title,
|
||||||
|
dismiss: {
|
||||||
|
duration: 10000,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
success: (message: string, title: string) => {
|
||||||
|
Store.addNotification({
|
||||||
|
container: "top-right",
|
||||||
|
type: "success",
|
||||||
|
message: message,
|
||||||
|
title: title,
|
||||||
|
dismiss: {
|
||||||
|
duration: 3000,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default notifications
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { App, AppSearch, Table, TableSearch } from "../definitions"
|
||||||
|
import getConfig from "next/config"
|
||||||
|
|
||||||
|
const { serverRuntimeConfig } = getConfig()
|
||||||
|
const apiKey = serverRuntimeConfig["apiKey"]
|
||||||
|
const appName = serverRuntimeConfig["appName"]
|
||||||
|
const host = serverRuntimeConfig["host"]
|
||||||
|
|
||||||
|
let APP: App | null = null
|
||||||
|
let TABLES: { [key: string]: Table } = {}
|
||||||
|
|
||||||
|
export async function makeCall(method: string, url: string, opts?: { body?: any, appId?: string }): Promise<any> {
|
||||||
|
const fetchOpts: any = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
"x-budibase-api-key": apiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts?.appId) {
|
||||||
|
fetchOpts.headers["x-budibase-app-id"] = opts.appId
|
||||||
|
}
|
||||||
|
if (opts?.body) {
|
||||||
|
fetchOpts.body = typeof opts.body !== "string" ? JSON.stringify(opts.body) : opts.body
|
||||||
|
fetchOpts.headers["Content-Type"] = "application/json"
|
||||||
|
}
|
||||||
|
const finalUrl = `${host}/api/public/v1/${url}`
|
||||||
|
const response = await fetch(finalUrl, fetchOpts)
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
const error = await response.text()
|
||||||
|
console.error("Budibase server error - ", error)
|
||||||
|
throw new Error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getApp(): Promise<App> {
|
||||||
|
if (APP) {
|
||||||
|
return APP
|
||||||
|
}
|
||||||
|
const apps: AppSearch = await makeCall("post", "applications/search", {
|
||||||
|
body: {
|
||||||
|
name: appName,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const app = apps.data.find((app: App) => app.name === appName)
|
||||||
|
if (!app) {
|
||||||
|
throw new Error("Could not find app, please make sure app name in config is correct.")
|
||||||
|
}
|
||||||
|
APP = app
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findTable(appId: string, tableName: string): Promise<Table> {
|
||||||
|
if (TABLES[tableName]) {
|
||||||
|
return TABLES[tableName]
|
||||||
|
}
|
||||||
|
const tables: TableSearch = await makeCall("post", "tables/search", {
|
||||||
|
body: {
|
||||||
|
name: tableName,
|
||||||
|
},
|
||||||
|
appId,
|
||||||
|
})
|
||||||
|
const table = tables.data.find((table: Table) => table.name === tableName)
|
||||||
|
if (!table) {
|
||||||
|
throw new Error("Could not find table, please make sure your app is configured with the Postgres datasource correctly.")
|
||||||
|
}
|
||||||
|
TABLES[tableName] = table
|
||||||
|
return table
|
||||||
|
}
|
|
@ -1,23 +1,21 @@
|
||||||
CREATE TABLE IF NOT EXISTS sales_people (
|
CREATE TABLE IF NOT EXISTS sales_people (
|
||||||
person_id INT NOT NULL,
|
person_id SERIAL PRIMARY KEY,
|
||||||
name varchar(200) NOT NULL,
|
name varchar(200) NOT NULL
|
||||||
PRIMARY KEY (person_id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sales (
|
CREATE TABLE IF NOT EXISTS sales (
|
||||||
sale_id INT NOT NULL,
|
sale_id SERIAL PRIMARY KEY,
|
||||||
sale_name varchar(200) NOT NULL,
|
sale_name varchar(200) NOT NULL,
|
||||||
sold_by INT,
|
sold_by INT,
|
||||||
PRIMARY KEY (sale_id),
|
|
||||||
CONSTRAINT sold_by_fk
|
CONSTRAINT sold_by_fk
|
||||||
FOREIGN KEY(sold_by)
|
FOREIGN KEY(sold_by)
|
||||||
REFERENCES sales_people(person_id)
|
REFERENCES sales_people(person_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO sales_people
|
INSERT INTO sales_people (name)
|
||||||
select id, concat('Sales person ', id)
|
select 'Salesperson ' || id
|
||||||
FROM GENERATE_SERIES(1, 50) as id;
|
FROM GENERATE_SERIES(1, 50) as id;
|
||||||
|
|
||||||
INSERT INTO sales
|
INSERT INTO sales (sale_name, sold_by)
|
||||||
select id, concat('Sale ', id), floor(random() * 50 + 1)::int
|
select 'Sale ' || id, floor(random() * 50 + 1)::int
|
||||||
FROM GENERATE_SERIES(1, 200) as id;
|
FROM GENERATE_SERIES(1, 200) as id;
|
|
@ -14,7 +14,8 @@
|
||||||
"node-fetch": "^3.2.2",
|
"node-fetch": "^3.2.2",
|
||||||
"node-sass": "^7.0.1",
|
"node-sass": "^7.0.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2"
|
"react-dom": "17.0.2",
|
||||||
|
"react-notifications-component": "^3.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "17.0.21",
|
"@types/node": "17.0.21",
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import "../styles/global.sass"
|
import "../styles/global.sass"
|
||||||
import type { AppProps } from "next/app"
|
import type { AppProps } from "next/app"
|
||||||
|
import Layout from "../components/layout"
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApp
|
export default MyApp
|
||||||
|
|
|
@ -1,73 +1,4 @@
|
||||||
import getConfig from "next/config"
|
import { getApp, findTable, makeCall } from "../../components/utils"
|
||||||
import {App, AppSearch, Table, TableSearch} from "../../definitions"
|
|
||||||
|
|
||||||
const { serverRuntimeConfig } = getConfig()
|
|
||||||
const apiKey = serverRuntimeConfig["apiKey"]
|
|
||||||
const appName = serverRuntimeConfig["appName"]
|
|
||||||
const host = serverRuntimeConfig["host"]
|
|
||||||
|
|
||||||
let APP: App | null = null
|
|
||||||
let TABLES: { [key: string]: Table } = {}
|
|
||||||
|
|
||||||
async function makeCall(method: string, url: string, opts?: { body?: any, appId?: string }): Promise<any> {
|
|
||||||
const fetchOpts: any = {
|
|
||||||
method,
|
|
||||||
headers: {
|
|
||||||
"x-budibase-api-key": apiKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts?.appId) {
|
|
||||||
fetchOpts.headers["x-budibase-app-id"] = opts.appId
|
|
||||||
}
|
|
||||||
if (opts?.body) {
|
|
||||||
fetchOpts.body = typeof opts.body !== "string" ? JSON.stringify(opts.body) : opts.body
|
|
||||||
fetchOpts.headers["Content-Type"] = "application/json"
|
|
||||||
}
|
|
||||||
const finalUrl = `${host}/api/public/v1/${url}`
|
|
||||||
const response = await fetch(finalUrl, fetchOpts)
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
const error = await response.text()
|
|
||||||
console.error("Budibase server error - ", error)
|
|
||||||
throw new Error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getApp(): Promise<App> {
|
|
||||||
if (APP) {
|
|
||||||
return APP
|
|
||||||
}
|
|
||||||
const apps: AppSearch = await makeCall("post", "applications/search", {
|
|
||||||
body: {
|
|
||||||
name: appName,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const app = apps.data.find((app: App) => app.name === appName)
|
|
||||||
if (!app) {
|
|
||||||
throw new Error("Could not find app, please make sure app name in config is correct.")
|
|
||||||
}
|
|
||||||
APP = app
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findTable(appId: string, tableName: string): Promise<Table> {
|
|
||||||
if (TABLES[tableName]) {
|
|
||||||
return TABLES[tableName]
|
|
||||||
}
|
|
||||||
const tables: TableSearch = await makeCall("post", "tables/search", {
|
|
||||||
body: {
|
|
||||||
name: tableName,
|
|
||||||
},
|
|
||||||
appId,
|
|
||||||
})
|
|
||||||
const table = tables.data.find((table: Table) => table.name === tableName)
|
|
||||||
if (!table) {
|
|
||||||
throw new Error("Could not find table, please make sure your app is configured with the Postgres datasource correctly.")
|
|
||||||
}
|
|
||||||
TABLES[tableName] = table
|
|
||||||
return table
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSales(req: any) {
|
async function getSales(req: any) {
|
||||||
const { page } = req.query
|
const { page } = req.query
|
||||||
|
@ -79,7 +10,7 @@ async function getSales(req: any) {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
sort: {
|
sort: {
|
||||||
type: "string",
|
type: "string",
|
||||||
order: "ascending",
|
order: "descending",
|
||||||
column: "sale_id",
|
column: "sale_id",
|
||||||
},
|
},
|
||||||
paginate: true,
|
paginate: true,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { getApp, findTable, makeCall } from "../../components/utils"
|
||||||
|
|
||||||
|
async function getSalespeople() {
|
||||||
|
const { _id: appId } = await getApp()
|
||||||
|
const table = await findTable(appId, "sales_people")
|
||||||
|
return await makeCall("post", `tables/${table._id}/rows/search`, {
|
||||||
|
appId,
|
||||||
|
body: {
|
||||||
|
sort: {
|
||||||
|
type: "string",
|
||||||
|
order: "ascending",
|
||||||
|
column: "person_id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(req: any, res: any) {
|
||||||
|
let response: any = {}
|
||||||
|
try {
|
||||||
|
if (req.method === "GET") {
|
||||||
|
response = await getSalespeople()
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.status(200).json(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(400).send(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +1,81 @@
|
||||||
import type { NextPage } from "next"
|
import type { NextPage } from "next"
|
||||||
import styles from "../styles/home.module.css"
|
import styles from "../styles/home.module.css"
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect, useCallback } from "react"
|
||||||
|
import Notifications from "../components/notifications"
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const [sales, setSales] = useState([])
|
const [sales, setSales] = useState([])
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
const getSales = async (page: Number = 1) => {
|
const getSales = useCallback(async (page: Number = 1) => {
|
||||||
let url = "/api/sales"
|
let url = "/api/sales"
|
||||||
if (page) {
|
if (page) {
|
||||||
url += `?page=${page}`
|
url += `?page=${page}`
|
||||||
}
|
}
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await response.text())
|
const error = await response.text()
|
||||||
|
Notifications.error(error, "Failed to get sales")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const sales = await response.json()
|
const sales = await response.json()
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setCurrentPage(page)
|
setCurrentPage(page)
|
||||||
return setSales(sales.data)
|
return setSales(sales.data)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const saveSale = async () => {
|
const goToNextPage = useCallback(async () => {
|
||||||
const response = await fetch("/api/sales", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
})
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(await response.text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToNextPage = async () => {
|
|
||||||
await getSales(currentPage + 1)
|
await getSales(currentPage + 1)
|
||||||
}
|
}, [currentPage, getSales])
|
||||||
|
|
||||||
const goToPrevPage = async () => {
|
const goToPrevPage = useCallback(async () => {
|
||||||
if (currentPage > 1) {
|
if (currentPage > 1) {
|
||||||
await getSales(currentPage - 1)
|
await getSales(currentPage - 1)
|
||||||
}
|
}
|
||||||
}
|
}, [currentPage, getSales])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSales().catch(() => {
|
getSales().then(() => {
|
||||||
|
setLoaded(true)
|
||||||
|
}).catch(() => {
|
||||||
setSales([])
|
setSales([])
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
<div className={styles.tableSection}>
|
||||||
<h1 className="subtitle">Sales</h1>
|
<h1 className="subtitle">Sales</h1>
|
||||||
<div>{sales.map((sale: any) => <p key={sale.sale_id}>{sale.sale_id}</p>)}</div>
|
<div className={styles.table}>
|
||||||
<button onClick={goToPrevPage}>Prev Page</button>
|
<table className="table">
|
||||||
<button onClick={goToNextPage}>Next Page</button>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Sale ID</th>
|
||||||
|
<th>name</th>
|
||||||
|
<th>Sold by</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sales.map((sale: any) =>
|
||||||
|
<tr key={sale.sale_id}>
|
||||||
|
<th>{sale.sale_id}</th>
|
||||||
|
<th>{sale.sale_name}</th>
|
||||||
|
<th>{sale.sales_people?.map((person: any) => person.primaryDisplay)[0]}</th>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<button className="button" onClick={goToPrevPage}>Prev Page</button>
|
||||||
|
<button className="button" onClick={goToNextPage}>Next Page</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import type { NextPage } from "next"
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import styles from "../styles/save.module.css"
|
||||||
|
import Notifications from "../components/notifications"
|
||||||
|
|
||||||
|
const Save: NextPage = () => {
|
||||||
|
const [salespeople, setSalespeople] = useState([])
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
|
const saveSale = useCallback(async (event: any) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const sale = {
|
||||||
|
sale_name: event.target.name.value,
|
||||||
|
sales_person: [event.target.soldBy.value],
|
||||||
|
}
|
||||||
|
const response = await fetch("/api/sales", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(sale),
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text()
|
||||||
|
Notifications.error(error, "Failed to save sale")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Notifications.success("Sale saved successfully!", "Sale saved")
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getSalespeople = useCallback(async () => {
|
||||||
|
const response: any = await fetch("/api/salespeople")
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await response.text())
|
||||||
|
}
|
||||||
|
const json = await response.json()
|
||||||
|
setSalespeople(json.data)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSalespeople().then(() => {
|
||||||
|
setLoaded(true)
|
||||||
|
}).catch(() => {
|
||||||
|
setSalespeople([])
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.formSection}>
|
||||||
|
<h1 className="subtitle">New sale</h1>
|
||||||
|
<form onSubmit={saveSale}>
|
||||||
|
<div className="field">
|
||||||
|
<label className="label">Name</label>
|
||||||
|
<div className="control">
|
||||||
|
<input id="name" className="input" type="text" placeholder="Text input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<label className="label">Sold by</label>
|
||||||
|
<div className="control">
|
||||||
|
<div className="select">
|
||||||
|
<select id="soldBy">
|
||||||
|
{salespeople.map((person: any) => <option key={person._id} value={person._id}>{person.name}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="control">
|
||||||
|
<button className="button is-link">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Save
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#393C44;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
.st2{fill:#4285F4;}
|
||||||
|
</style>
|
||||||
|
<rect x="-152.17" y="-24.17" class="st0" width="96.17" height="96.17"/>
|
||||||
|
<path class="st1" d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
||||||
|
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C-93.55,28.92-93.46,28.52-93.46,28.11z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
||||||
|
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
||||||
|
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
||||||
|
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C-108.68,28.92-108.6,28.52-108.6,28.11z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path class="st2" d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
||||||
|
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
|
C34.45,139.92,34.54,139.52,34.54,139.11z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
||||||
|
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
||||||
|
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
||||||
|
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
||||||
|
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M44,48H4c-2.21,0-4-1.79-4-4V4c0-2.21,1.79-4,4-4h40c2.21,0,4,1.79,4,4v40C48,46.21,46.21,48,44,48z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
|
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
||||||
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
|
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
||||||
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
|
@ -1,4 +0,0 @@
|
||||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,12 +1,25 @@
|
||||||
@charset "utf-8"
|
@charset "utf-8"
|
||||||
|
|
||||||
// Import a Google Font
|
|
||||||
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700')
|
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700')
|
||||||
|
|
||||||
$family-sans-serif: "Roboto", sans-serif
|
$family-sans-serif: "Roboto", sans-serif
|
||||||
//$grey-dark: color
|
|
||||||
//$grey-light: color
|
#__next
|
||||||
//$primary: color
|
display: flex
|
||||||
//$link: color
|
flex-direction: column
|
||||||
|
justify-content: flex-start
|
||||||
|
align-items: stretch
|
||||||
|
height: 100vh
|
||||||
|
|
||||||
|
.logo
|
||||||
|
padding: 0.75rem
|
||||||
|
|
||||||
@import "../node_modules/bulma/bulma.sass"
|
@import "../node_modules/bulma/bulma.sass"
|
||||||
|
@import "../node_modules/react-notifications-component/dist/theme.css"
|
||||||
|
|
||||||
|
// applied after bulma styles are enabled
|
||||||
|
html
|
||||||
|
overflow-y: auto
|
||||||
|
|
||||||
|
.navbar
|
||||||
|
background-color: #D3D3D3
|
||||||
|
color: white
|
|
@ -1,116 +1,30 @@
|
||||||
.container {
|
.container {
|
||||||
padding: 0 2rem;
|
width: 100vw;
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 4rem 0;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
padding: 5rem 2rem 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex-direction: row;
|
||||||
padding: 2rem 0;
|
justify-content: space-between;
|
||||||
border-top: 1px solid #eaeaea;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer a {
|
.tableSection {
|
||||||
display: flex;
|
padding: 2rem;
|
||||||
justify-content: center;
|
background: #D3D3D3;
|
||||||
align-items: center;
|
width: 800px;
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title a {
|
|
||||||
color: #0070f3;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title a:hover,
|
|
||||||
.title a:focus,
|
|
||||||
.title a:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.15;
|
|
||||||
font-size: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title,
|
|
||||||
.description {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin: 4rem 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code {
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin: 1rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
text-align: left;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
border: 1px solid #eaeaea;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
transition: color 0.15s ease, border-color 0.15s ease;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover,
|
.table table {
|
||||||
.card:focus,
|
|
||||||
.card:active {
|
|
||||||
color: #0070f3;
|
|
||||||
border-color: #0070f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h2 {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 1em;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.grid {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tableSection h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
}
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
.container {
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 5rem 2rem 0;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formSection {
|
||||||
|
padding: 2rem;
|
||||||
|
background: #D3D3D3;
|
||||||
|
width: 400px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formSection h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
}
|
|
@ -2384,6 +2384,11 @@ react-is@^16.13.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
react-notifications-component@^3.4.1:
|
||||||
|
version "3.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-notifications-component/-/react-notifications-component-3.4.1.tgz#3670aa17210a4e63ba3b4f553853bd99ab6a0150"
|
||||||
|
integrity sha512-vS/RLdz+VlXZz0dbK+LCcdhgUdUPi1BvSo7mVp58AQDpixI9emGwI0uISXhiTSjqFn/cPibPlJOJQ8kcvgmUrQ==
|
||||||
|
|
||||||
react@17.0.2:
|
react@17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||||
|
|
Loading…
Reference in New Issue