Compare commits
22 Commits
live-prep
...
7fe5858cb3
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fe5858cb3 | |||
|
|
fe07189182 | ||
|
|
19e4f5bc69 | ||
|
|
5b6f58fd2a | ||
|
|
96329d07b1 | ||
|
|
732ee0d64f | ||
|
|
ad2afffdde | ||
|
|
19da66425a | ||
|
|
0c304b84a1 | ||
|
|
edf82b6a28 | ||
|
|
16531a03db | ||
|
|
c5538cf076 | ||
|
|
0789f23c4e | ||
|
|
7812ccdcf8 | ||
|
|
e61b014531 | ||
|
|
5650d2dda0 | ||
|
|
62f771db74 | ||
|
|
bb1ab90aed | ||
|
|
f191382659 | ||
|
|
1e421126cb | ||
|
|
a3dc0be30d | ||
|
|
ff2579adfa |
@@ -16,10 +16,10 @@ If you want to develop websites like it was 1997, feel free to reuse this projec
|
|||||||
|
|
||||||
```
|
```
|
||||||
# clone this repo
|
# clone this repo
|
||||||
git clone https://github.com/encrypted-dev/homepage.git
|
git clone https://github.com/encrypted-dev/userbase-homepage.git
|
||||||
|
|
||||||
# go to the repo directory
|
# go to the repo directory
|
||||||
cd homepage
|
cd userbase-homepage
|
||||||
|
|
||||||
# install all dependencies
|
# install all dependencies
|
||||||
npm install
|
npm install
|
||||||
|
|||||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -2373,6 +2373,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"clipboard": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"good-listener": "^1.2.2",
|
||||||
|
"select": "^1.1.2",
|
||||||
|
"tiny-emitter": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cliui": {
|
"cliui": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||||
@@ -3167,6 +3179,13 @@
|
|||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"delegate": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"delegates": {
|
"delegates": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
@@ -4905,6 +4924,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"good-listener": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||||
|
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"delegate": "^3.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||||
@@ -7947,6 +7976,15 @@
|
|||||||
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
|
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"prismjs": {
|
||||||
|
"version": "1.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz",
|
||||||
|
"integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"clipboard": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"private": {
|
"private": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
||||||
@@ -8636,6 +8674,13 @@
|
|||||||
"ajv-keywords": "^3.1.0"
|
"ajv-keywords": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"select": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"select-hose": {
|
"select-hose": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
@@ -9629,6 +9674,13 @@
|
|||||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tiny-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"tinycolor2": {
|
"tinycolor2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"opn-browser-webpack-plugin": "0.0.7",
|
"opn-browser-webpack-plugin": "0.0.7",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
|
"prismjs": "^1.17.1",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.4",
|
||||||
"tailwindcss": "^1.0.4",
|
"tailwindcss": "^1.0.4",
|
||||||
"terser-webpack-plugin": "^1.3.0",
|
"terser-webpack-plugin": "^1.3.0",
|
||||||
@@ -40,4 +41,4 @@
|
|||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-dev-server": "^3.5.1"
|
"webpack-dev-server": "^3.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/img/logo-horizontal.png
Normal file
BIN
src/img/logo-horizontal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
src/img/logo.png
BIN
src/img/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 40 KiB |
@@ -1,3 +1,6 @@
|
|||||||
|
import Prism from './prism'
|
||||||
|
|
||||||
|
import './prism.css'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
|
||||||
window.displayMailChimpStatus = function (data) {
|
window.displayMailChimpStatus = function (data) {
|
||||||
|
|||||||
895
src/pages/docs.html
Normal file
895
src/pages/docs.html
Normal file
@@ -0,0 +1,895 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2>Docs</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#getting-started" class="font-semibold">Getting started</a></li>
|
||||||
|
<li><a href="#sdk" class="font-semibold">SDK</a></li>
|
||||||
|
<li><a href="#tutorial" class="font-semibold">Tutorial</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<a class="anchor" id="getting-started"></a>
|
||||||
|
<h2 id=>Getting Started</h2>
|
||||||
|
|
||||||
|
<h3>Create a developer account</h3>
|
||||||
|
<p>First, you need a Userbase developer account. You will be able to create a free account when Userbase gets launched. No credit card required.</p>
|
||||||
|
|
||||||
|
<h3>Install the SDK</h3>
|
||||||
|
<p>Then, you need to include the Userbase SDK into your web app.</p>
|
||||||
|
<p>You can either include the SDK with a <code class="language-markup"><script></code> tag:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-markup"><!--
|
||||||
|
<script type="text/javascript" src="https://sdk.userbase.dev/userbase-0.0.5.js"></script>
|
||||||
|
--></code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Or else, you can include the SDK into your build pipeline:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-bash">
|
||||||
|
npm --install --save userbase-js
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3>Set the Application ID</h3>
|
||||||
|
<p>From your developer account, create a new App and get the Application ID. Then, you just need to configure the Userbase SDK to use it:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript"><!--
|
||||||
|
userbase.configure({ appId: 'a43ae910-fc89-43fe-a7a3-a11a53b49325' })
|
||||||
|
--></code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And you're all set.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<a class="anchor" id="sdk"></a>
|
||||||
|
<h2>SDK</h2>
|
||||||
|
|
||||||
|
<p>Userbase is accessible directly from the browser through a very simple JavaScript SDK. The following is the complete set of APIs that let you create user accounts, handle logins, and persist user data.</p>
|
||||||
|
|
||||||
|
<h3 id="sdk-configure">Configure</h3>
|
||||||
|
<p>Use this API to configure your Userbase client.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/docs/sdk/configure" class="font-semibold">Configure</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="sdk-users">Users</h3>
|
||||||
|
<p>Use these APIs to create user accounts, handle logins and logouts, and resume sessions when a user returns to your web app.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/docs/sdk/sign-up" class="font-semibold">SignUp</a></li>
|
||||||
|
<li><a href="/docs/sdk/sign-in" class="font-semibold">SignIn</a></li>
|
||||||
|
<li><a href="/docs/sdk/sign-in-with-session" class="font-semibold">SignInWithSession</a></li>
|
||||||
|
<li><a href="/docs/sdk/sign-out" class="font-semibold">SignOut</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="sdk-data">Data</h3>
|
||||||
|
<p>Use these APIs to store and retrieve user data. All data handled by these APIs is highly-durable, immediately consistent, and end-to-end encrypted.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/docs/sdk/open-database" class="font-semibold">OpenDatabase</a></li>
|
||||||
|
<li><a href="/docs/sdk/insert" class="font-semibold">Insert</a></li>
|
||||||
|
<li><a href="/docs/sdk/update" class="font-semibold">Update</a></li>
|
||||||
|
<li><a href="/docs/sdk/delete" class="font-semibold">Delete</a></li>
|
||||||
|
<li><a href="/docs/sdk/transaction" class="font-semibold">Transaction</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<a class="anchor" id="tutorial"></a>
|
||||||
|
<h2>Tutorial</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In this tutorial we will build a very basic to-do web app. Even if what you're building has nothing to do with to-dos, the techniques we'll cover here can be applied to many other kinds of web apps.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can think of this tutorial as a demonstration of the core functionality of Userbase in the simplest way possible. We are going to focus solely on building a functional web app, and making things pretty is left as an exercise to the reader.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The entire web app we'll be building will fit in a <a href="https://github.com/encrypted-dev/userbase/blob/7746e98c27c7d4fe6b8846592db88cd576a907a6/docs/examples/ugliest-todo-app/index.html" target="_blank">single static HTML file of 185 lines</a>. You can also see a <a href="https://ugliest-todo.netlify.com/" target="_blank">live demo</a> of the final result.</p>
|
||||||
|
|
||||||
|
<h3>Prerequisites</h3>
|
||||||
|
|
||||||
|
<p>The only requirement is basic familiarity of <a href="https://developer.mozilla.org/en-US/docs/Web/HTML" target="_blank">HTML</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript" target="_blank">JavaScript</a>.</p>
|
||||||
|
|
||||||
|
<h3>Setting up</h3>
|
||||||
|
|
||||||
|
<p>Let's get going. Open a new file in your favorite code editor:</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-bash">
|
||||||
|
code ugly-todo.html
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And add some boilerplate HTML:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<pre>
|
||||||
|
<code class="language-markup">
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ugliest To-Do</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now, open this file in a web browser of your choosing. At this point all you'll see is a blank page. As we add functionality throughout the tutorial, you can refresh this page to see the changes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a class="anchor" id="creating-a-developer-account"></a>
|
||||||
|
<h3>Creating a developer account</h3>
|
||||||
|
|
||||||
|
<p>To complete this tutorial, you'll need to create a Userbase developer account. Upon creation, a default application named "Preview" will be created. Take note of the App ID because you'll need it in a second.</p>
|
||||||
|
|
||||||
|
<h3>Installing the SDK</h3>
|
||||||
|
|
||||||
|
We're going to load the Userbase SDK from a CDN with a <code class="language-markup"><script></code> tag in the head of
|
||||||
|
our page:
|
||||||
|
|
||||||
|
<pre data-line="4">
|
||||||
|
<code class="language-markup"><!--
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ugliest To-Do App</title>
|
||||||
|
<script type="text/javascript" src="https://sdk.userbase.dev/userbase-0.0.5.js"></script>
|
||||||
|
</head>
|
||||||
|
--></code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>The Userbase SDK will now be accessible via the <code class="language-javascript">userbase</code> variable. This will be our only dependency.</p>
|
||||||
|
|
||||||
|
<h3>Configuring the SDK</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Before doing anything with the Userbase SDK, we need to let it know our App ID.
|
||||||
|
Simply replace <code class="language-javascript">'YOUR_APP_ID'</code> with the App ID you received when you <a href="#creating-a-developer-account">created your developer account</a>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre data-line="4">
|
||||||
|
<code class="language-markup">
|
||||||
|
<body>
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
userbase.configure({ appId: 'YOUR_APP_ID' })
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3>Letting new users create an account</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Before our users can start creating to-dos, we need to give them a way to create an account with our app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>First, let's add a sign up form:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<pre data-line="2-11">
|
||||||
|
<code class="language-markup">
|
||||||
|
<body>
|
||||||
|
<!-- Auth View -->
|
||||||
|
<div id="auth-view">
|
||||||
|
<h1>Create an account</h1>
|
||||||
|
<form id="signup-form">
|
||||||
|
<input id="signup-username" type="text" required placeholder="Username">
|
||||||
|
<input id="signup-password" type="password" required placeholder="Password">
|
||||||
|
<input type="submit" value="Create an account">
|
||||||
|
</form>
|
||||||
|
<div id="signup-error"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Then, let's add the code to handle the form submission:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<pre data-line="5-16">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
userbase.configure({ appId: 'YOUR_APP_ID' })
|
||||||
|
|
||||||
|
function handleSignUp(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const username = document.getElementById('signup-username').value
|
||||||
|
const password = document.getElementById('signup-password').value
|
||||||
|
|
||||||
|
userbase.signUp(username, password)
|
||||||
|
.then((user) => alert('You signed up!'))
|
||||||
|
.catch((e) => document.getElementById('signup-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now, whenever someone submits the form, the <code class="language-javascript">handleSignUp</code> function will be called.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The first thing we do in <code class="language-javascript">handleSignUp</code> is call <code class="language-javascript">preventDefault()</code> on the
|
||||||
|
submit event. This will prevent the page from submitting to the server. We want to handle this form completely on the client-side.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Next, we get the values of the username and password inputs and call
|
||||||
|
<code class="language-javascript">userbase.signUp(username, password)</code>. This will request a new account to be created with the Userbase service, and returns a Promise that resolves with a <code class="language-javascript">user</code> object.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Go ahead and reload the page in your browser. Enter a username and password in the form and submit. You should get an alert saying that you signed up. And if go to your Userbase developer account, you should also see the new user under your app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now try signing up for another account using the same username and you'll see an error message displayed under the form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We'll come back to this in a bit to change this function to do something more interesting.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Letting users log in</h3>
|
||||||
|
|
||||||
|
<p>Now that our users can create accounts, let's give them the ability to login.</p>
|
||||||
|
|
||||||
|
<p>First, let's add a "Login" form to the page above our "Create Account" form:</p>
|
||||||
|
|
||||||
|
<pre data-line="4-10">
|
||||||
|
<code class="language-markup">
|
||||||
|
<body>
|
||||||
|
<!-- Auth View -->
|
||||||
|
<div id="auth-view">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form id="login-form">
|
||||||
|
<input id="login-username" type="text" required placeholder="Username">
|
||||||
|
<input id="login-password" type="password" required placeholder="Password">
|
||||||
|
<input type="submit" value="Sign in">
|
||||||
|
</form>
|
||||||
|
<div id="login-error"></div>
|
||||||
|
|
||||||
|
<h1>Create an account</h1>
|
||||||
|
<form id="signup-form">
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Then, let's add the code to handle the form submission:</p>
|
||||||
|
|
||||||
|
<pre data-line="5-14">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
userbase.configure({ appId: 'YOUR_APP_ID' })
|
||||||
|
|
||||||
|
function handleLogin(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const username = document.getElementById('login-username').value
|
||||||
|
const password = document.getElementById('login-password').value
|
||||||
|
|
||||||
|
userbase.signIn(username, password)
|
||||||
|
.then((user) => alert('You signed in!'))
|
||||||
|
.catch((e) => document.getElementById('login-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSignUp(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And finally, let's bind our login form within our login handler:</p>
|
||||||
|
|
||||||
|
<pre data-line="9">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
.catch((e) => document.getElementById('signup-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You'll notice that this looks very similar to the sign up code from before. The <code class="language-javascript">handleLogin</code> function prevents the default form behavior, extracts the input values from the DOM, and calls <code class="language-javascript">userbase.signIn(username, password)</code>. This will attempt to sign in the user with the Userbase service, handling a success with an alert and a failure by displaying the error.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and you should see our new login form. Enter the username and
|
||||||
|
password you used to create an account in the previous step, and submit the form.
|
||||||
|
You should get an alert saying that you signed in.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Try submitting the form again with incorrect credentials and you'll see an error
|
||||||
|
message displayed under the form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Showing the to-do view</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
After a user signs in, we'll want to hide the authentication forms, indicate
|
||||||
|
to the user they are logged in, and display their to-do list.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>First, let's add a container for the to-do list under the authentication forms:</p>
|
||||||
|
|
||||||
|
<pre data-line="8-14">
|
||||||
|
<code class="language-markup">
|
||||||
|
|
||||||
|
<!-- Auth View -->
|
||||||
|
<div id="auth-view">
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- To-dos View -->
|
||||||
|
<div id="todo-view">
|
||||||
|
<div id="username"></div>
|
||||||
|
|
||||||
|
<h1>To-Do List</h1>
|
||||||
|
<div id="todos"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
userbase.configure({ appId: 'YOUR_APP_ID' })
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Then, let's add a function to display this view and initially make it hidden:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<pre data-line="9-13,18">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
.catch((e) => document.getElementById('signup-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTodos(username) {
|
||||||
|
document.getElementById('auth-view').style.display = 'none'
|
||||||
|
document.getElementById('todo-view').style.display = 'block'
|
||||||
|
document.getElementById('username').innerHTML = username
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
|
||||||
|
document.getElementById('todo-view').style.display = 'none'
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now that we have a function to show a view for signed in users, let's change
|
||||||
|
<code class="language-javascript">handleLogin</code> to call this function instead of showing an alert:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre data-line="7">
|
||||||
|
<code class="language-javascript">
|
||||||
|
function handleLogin(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const password = document.getElementById('login-password').value
|
||||||
|
|
||||||
|
userbase.signIn(username, password)
|
||||||
|
.then((user) => showTodos(user.username))
|
||||||
|
.catch((e) => document.getElementById('login-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And we do the same thing for <code class="language-javascript">handleSignUp</code>:</p>
|
||||||
|
|
||||||
|
<pre data-line="7">
|
||||||
|
<code class="language-javascript">
|
||||||
|
function handleSignUp(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const password = document.getElementById('signup-password').value
|
||||||
|
|
||||||
|
userbase.signUp(username, password)
|
||||||
|
.then((user) => showTodos(user.username))
|
||||||
|
.catch((e) => document.getElementById('signup-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and login again using your username and password. You should see the
|
||||||
|
authentication view disappear and your username show up along with the to-do list heading.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Using the database</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Each time a user signs in, we need to establish a connection with the database that will hold that user's to-dos.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, let's add a couple of elements for showing a loading indicator and error messages:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre data-line="6-7">
|
||||||
|
<code class="language-markup"><!--
|
||||||
|
<div id="todo-view">
|
||||||
|
<span id="username"></span>
|
||||||
|
|
||||||
|
<h1>To-Do List</h1>
|
||||||
|
<div id="todos"></div>
|
||||||
|
<div id="db-loading">Loading to-dos...</div>
|
||||||
|
<div id="db-error"></div>
|
||||||
|
</div>
|
||||||
|
--></code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>Then, we'll change <code class="language-javascript">showTodos</code> to open a new database with the Userbase service:</p>
|
||||||
|
|
||||||
|
<pre data-line="5,7-12,15-25">
|
||||||
|
<code class="language-javascript">
|
||||||
|
function showTodos(username) {
|
||||||
|
document.getElementById('auth-view').style.display = 'none'
|
||||||
|
document.getElementById('todo-view').style.display = 'block'
|
||||||
|
|
||||||
|
// reset the todos view
|
||||||
|
document.getElementById('username').innerHTML = username
|
||||||
|
document.getElementById('todos').innerText = ''
|
||||||
|
document.getElementById('db-loading').style.display = 'block'
|
||||||
|
document.getElementById('db-error').innerText = ''
|
||||||
|
|
||||||
|
userbase.openDatabase('todos', handleDatabaseChange)
|
||||||
|
.catch((e) => document.getElementById('db-error').innerText = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDatabaseChange(items) {
|
||||||
|
document.getElementById('db-loading').style.display = 'none'
|
||||||
|
|
||||||
|
const todosList = document.getElementById('todos')
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
todosList.innerText = 'Empty'
|
||||||
|
} else {
|
||||||
|
// render to-dos, not yet implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We changed <code class="language-javascript">showTodos</code> to make a call to <code class="language-javascript">userbase.openDatabase('todos',
|
||||||
|
handleDatabaseChange)</code>. The <code class="language-javascript">'todos'</code> parameter is the name of the database we want to open, and <code class="language-javascript">handleDatabaseChange</code> is a callback for receiving changes to data in the database. The Userbase service will attempt to open the user's database by the
|
||||||
|
name of <code class="language-javascript">'todos'</code> (creating one if it doesn't already exist). After the <code class="language-javascript">'todos'</code>
|
||||||
|
database is opened, our callback
|
||||||
|
function <code class="language-javascript">handleDatabaseChanges</code> will be called whenever data changes in the database. When the Promise is resolved, the database is ready for use and we hide the loading indicator.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and sign in again. You'll see the "Loading to-dos..." as a connection
|
||||||
|
to the database is established followed by "Empty", indicating there are
|
||||||
|
currently no to-dos in the database.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Showing the to-dos</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If the database has items in it, we'll want to render those under our to-do list.
|
||||||
|
Let's implement that case in <code class="language-javascript">handleDatabaseChange</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre data-line="9-24">
|
||||||
|
<code class="language-javascript"><!--
|
||||||
|
function handleDatabaseChange(items) {
|
||||||
|
document.getElementById('db-loading').style.display = 'none'
|
||||||
|
|
||||||
|
const todosList = document.getElementById('todos')
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
todosList.innerText = 'Empty'
|
||||||
|
} else {
|
||||||
|
// clear the list
|
||||||
|
todosList.innerHTML = ''
|
||||||
|
|
||||||
|
// render all the to-do items
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
|
||||||
|
// build the todo label
|
||||||
|
const todoLabel = document.createElement('label')
|
||||||
|
todoLabel.innerHTML = items[i].item.todo
|
||||||
|
todoLabel.style.textDecoration = items[i].item.complete ? 'line-through' : 'none'
|
||||||
|
|
||||||
|
// append the todo item to the list
|
||||||
|
const todoItem = document.createElement('div')
|
||||||
|
todoItem.appendChild(todoLabel)
|
||||||
|
todosList.appendChild(todoItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--></code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3>Adding to-dos</h3>
|
||||||
|
|
||||||
|
<p>Now, let's add a form for creating new to-dos:</p>
|
||||||
|
|
||||||
|
<script type="text/plain" data-line="10-14" class="language-markup">
|
||||||
|
<!-- To-dos View -->
|
||||||
|
<div id="todo-view">
|
||||||
|
<span id="username"></span>
|
||||||
|
|
||||||
|
<h1>To-Do List</h1>
|
||||||
|
<div id="todos"></div>
|
||||||
|
<div id="db-loading">Loading to-dos...</div>
|
||||||
|
<div id="db-error"></div>
|
||||||
|
|
||||||
|
<form id="add-todo-form">
|
||||||
|
<input id="add-todo" type="text" required placeholder="To-Do">
|
||||||
|
<input type="submit" value="Add">
|
||||||
|
</form>
|
||||||
|
<div id="add-todo-error"></div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>Then, let's add the code to handle the form submission:</p>
|
||||||
|
|
||||||
|
<pre data-line="5-13,17,20">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
…
|
||||||
|
|
||||||
|
function addTodoHandler(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const todo = document.getElementById('add-todo').value
|
||||||
|
|
||||||
|
userbase.insert('todos', { 'todo': todo })
|
||||||
|
.then(() => document.getElementById('add-todo').value = '')
|
||||||
|
.catch((e) => document.getElementById('add-todo-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)
|
||||||
|
|
||||||
|
document.getElementById('todo-view').style.display = 'none'
|
||||||
|
document.getElementById('add-todo-form').style.display = 'none'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In <code class="language-javascript">addTodoHandler</code> we first call <code class="language-javascript">preventDefault()</code> to stop the default form
|
||||||
|
behavior, pull the to-do text from the input, and then call <code class="language-javascript">userbase.insert</code>
|
||||||
|
with the database name and the object we want the persist. This
|
||||||
|
will return a Promise that resolves when the data is successfully persisted to
|
||||||
|
the database.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We hide the <code class="language-markup">add-todo-form</code> element until
|
||||||
|
the user logs in:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre data-line="8">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
…
|
||||||
|
|
||||||
|
function showTodos(username) {
|
||||||
|
document.getElementById('auth-view').style.display = 'none'
|
||||||
|
document.getElementById('todo-view').style.display = 'block'
|
||||||
|
document.getElementById('add-todo-form').style.display = 'block'
|
||||||
|
|
||||||
|
// reset the todos view
|
||||||
|
document.getElementById('username').innerHTML = username
|
||||||
|
document.getElementById('todos').innerText = ''
|
||||||
|
document.getElementById('db-loading').style.display = 'block'
|
||||||
|
document.getElementById('db-error').innerText = ''
|
||||||
|
|
||||||
|
userbase.openDatabase('todos', handleDatabaseChange)
|
||||||
|
.catch((e) => document.getElementById('db-error').innerText = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and add some to-dos. Then, reload the page again and the to-dos should automatically appear after you login. These to-dos have been successfully persisted in the end-to-end encrypted Userbase database.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Updating to-dos</h3>
|
||||||
|
|
||||||
|
<p>Now, let's add a checkbox to allow to-dos to be marked as completed:</p>
|
||||||
|
|
||||||
|
<pre data-line="4-16,26">
|
||||||
|
<code class="language-javascript">
|
||||||
|
// render all the to-do items
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
|
||||||
|
// build the todo checkbox
|
||||||
|
const todoBox = document.createElement('input')
|
||||||
|
todoBox.type = 'checkbox'
|
||||||
|
todoBox.id = items[i].itemId
|
||||||
|
todoBox.checked = items[i].item.complete ? true : false
|
||||||
|
todoBox.onclick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
userbase.update('todos', {
|
||||||
|
'todo': items[i].item.todo,
|
||||||
|
'complete': !items[i].item.complete
|
||||||
|
}, items[i].itemId)
|
||||||
|
.catch((e) => document.getElementById('add-todo-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the todo label
|
||||||
|
const todoLabel = document.createElement('label')
|
||||||
|
todoLabel.innerHTML = items[i].item.todo
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
// append the todo item to the list
|
||||||
|
const todoItem = document.createElement('div')
|
||||||
|
todoItem.appendChild(todoBox)
|
||||||
|
todoItem.appendChild(todoLabel)
|
||||||
|
todosList.appendChild(todoItem)
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and complete some to-dos. Their state should persist even after you reload the page and login again.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Deleting to-dos</h3>
|
||||||
|
|
||||||
|
<p>And finally, let's create a button for deleting a to-do:</p>
|
||||||
|
|
||||||
|
<pre data-line="4-11">
|
||||||
|
<code class="language-javascript">
|
||||||
|
// render all the to-do items
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
|
||||||
|
// build the todo delete button
|
||||||
|
const todoDelete = document.createElement('button')
|
||||||
|
todoDelete.innerHTML = 'X'
|
||||||
|
todoDelete.style.display = 'inline-block'
|
||||||
|
todoDelete.onclick = () => {
|
||||||
|
userbase.delete('todos', items[i].itemId)
|
||||||
|
.catch((e) => document.getElementById('add-todo-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the todo checkbox
|
||||||
|
const todoBox = document.createElement('input')
|
||||||
|
todoBox.type = 'checkbox'
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And now let's append the delete button to the to-do element:</p>
|
||||||
|
|
||||||
|
<pre data-line="3">
|
||||||
|
<code class="language-javascript">
|
||||||
|
// append the todo item to the list
|
||||||
|
const todoItem = document.createElement('div')
|
||||||
|
todoItem.appendChild(todoDelete)
|
||||||
|
todoItem.appendChild(todoBox)
|
||||||
|
todoItem.appendChild(todoLabel)
|
||||||
|
todosList.appendChild(todoItem)
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Reload the page and delete some to-dos. They should no longer show up even after you reload the page and login again.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Polishing up</h3>
|
||||||
|
|
||||||
|
<p>Before we wrap up, let's add two final pieces of account functionality: user logout and automatic login for returning users.</p>
|
||||||
|
|
||||||
|
<h3>Signing out users</h3>
|
||||||
|
|
||||||
|
<p>First, let's add a logout button along with a container for displaying error messages:</p>
|
||||||
|
|
||||||
|
<script type="text/plain" data-line="4-5" class="language-markup">
|
||||||
|
<!-- To-dos View -->
|
||||||
|
<div id="todo-view">
|
||||||
|
<span id="username"></span>
|
||||||
|
<input type="button" value="Logout" id="logout-button">
|
||||||
|
<div id="logout-error"></div>
|
||||||
|
|
||||||
|
<h1>To-Do List</h1>
|
||||||
|
<div id="todos"></div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>Then, let's add the code to handle the logout:</p>
|
||||||
|
|
||||||
|
<pre data-line="9-13,21-30,40">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
.catch((e) => document.getElementById('signup-error').innerHTML = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
userbase.signOut()
|
||||||
|
.then(() => showAuth())
|
||||||
|
.catch((e) => document.getElementById('logout-error').innerText = e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTodos(username) {
|
||||||
|
document.getElementById('auth-view').style.display = 'none'
|
||||||
|
document.getElementById('todo-view').style.display = 'block'
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
function showAuth() {
|
||||||
|
document.getElementById('todo-view').style.display = 'none'
|
||||||
|
document.getElementById('auth-view').style.display = 'block'
|
||||||
|
document.getElementById('login-username').value = ''
|
||||||
|
document.getElementById('login-password').value = ''
|
||||||
|
document.getElementById('login-error').innerText = ''
|
||||||
|
document.getElementById('signup-username').value = ''
|
||||||
|
document.getElementById('signup-password').value = ''
|
||||||
|
document.getElementById('signup-error').innerText = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDatabaseChange(items) {
|
||||||
|
const todosList = document.getElementById('todos')
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)
|
||||||
|
document.getElementById('logout-button').addEventListener('click', handleLogout)
|
||||||
|
|
||||||
|
document.getElementById('todo-view').style.display = 'none'
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>The <code class="language-javascript">handleLogout</code> function calls <code class="language-javascript">userbase.signOut</code> which sends a request to end the user's session to the Userbase service. A Promise is returned that resolves when the user is signed out, in which case we hide the to-do view and show the authentication view.</p>
|
||||||
|
|
||||||
|
<h3>Automatically resuming a session</h3>
|
||||||
|
|
||||||
|
<p>Whenever a user logs in, the Userbase SDK will store information about the session in the browser to allow it to be resumed when the user returns after having navigating away.</p>
|
||||||
|
|
||||||
|
<p>Let's modify our app to automatically sign in a returning user when the page loads. First, we'll add a view that indicates we are signing in the user:</p>
|
||||||
|
|
||||||
|
<pre data-line="4-5">
|
||||||
|
<code class="language-markup">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Loading View -->
|
||||||
|
<div id="loading-view">Loading...</div>
|
||||||
|
|
||||||
|
<!-- Auth View -->
|
||||||
|
<div id="auth-view">
|
||||||
|
<h1>Login</h1>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>In order to automatically resume a session if one is available, we need to add the following to our application code:</p>
|
||||||
|
|
||||||
|
<pre data-line="12-17">
|
||||||
|
<code class="language-markup">
|
||||||
|
<!-- application code -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
document.getElementById('login-form').addEventListener('submit', handleLogin)
|
||||||
|
document.getElementById('signup-form').addEventListener('submit', handleSignUp)
|
||||||
|
document.getElementById('add-todo-form').addEventListener('submit', addTodoHandler)
|
||||||
|
document.getElementById('logout-button').addEventListener('click', handleLogout)
|
||||||
|
|
||||||
|
document.getElementById('todo-view').style.display = 'none'
|
||||||
|
document.getElementById('auth-view').style.display = 'none'
|
||||||
|
|
||||||
|
userbase.signInWithSession()
|
||||||
|
.then((session) => session.user ? showTodos(session.user.username) : showAuth())
|
||||||
|
.catch(() => showAuth())
|
||||||
|
.finally(() => document.getElementById('loading-view').style.display = 'none')
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We hide the authentication view initially, as we'll now only show it if an existing session can't be resumed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Then, we make a call to <code class="language-javascript">userbase.signInWithSession</code> to attempt to sign in the user
|
||||||
|
using an existing session as soon as our app loads.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code class="language-javascript">userbase.signInWithSession</code> function returns a Promise that resolves when the SDK has determined if it can reuse the previous session. If so, the user gets automatically logged in, and the <code class="language-javascript">session.user</code> object gets set. If there was no previous session, or the session cannot be resumed, the <code class="language-javascript">session.user</code> object will not be set.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If <code class="language-javascript">userbase.signInWithSession</code> fails, we'll just send the user
|
||||||
|
to the sign in page regardless of the reason.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>What's next?</h3>
|
||||||
|
|
||||||
|
<p>And that was it! A fully working (but ugly) web app in just 185 lines of code, including markup and comments.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In a real project you'll likely want to use a more sophisticated approach. For instance, you may want to use React to control the DOM, or a module bundler to package your application from multiple files. At the very
|
||||||
|
least, you'll want to add some styling.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We are working on a collection of new tutorials and sample applications that will
|
||||||
|
show you how to do all these things with Userbase, and more. You can <a href="/mailing-list">subscribe to our mailing list</a> to get updates like these in your inbox.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>If you have any questions, or there's anything we can do to help you with your web app, please get in touch via <a href="mailto:daniel@encrypted.dev">email</a> or <a href="https://twitter.com/UserbaseHQ">Twitter</a>. Thank you!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
31
src/pages/docs_sdk_configure.html
Normal file
31
src/pages/docs_sdk_configure.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : Configure</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">Configure</span> lets you configure your Userbase client.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.configure({ appId, endpoint })</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">settings</span> [object] - Contains your configuration settings.
|
||||||
|
<ul>
|
||||||
|
<li><span class="field">appId</span> [string] - Your App ID you received when you created a Userbase application.</li>
|
||||||
|
<li><span class="field">endpoint</span> [string] - If you're hosting your own Userbase server, you can provide its endpoint here.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>ConfigParametersMissing</li>
|
||||||
|
<li>UserAlreadySignedIn</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
43
src/pages/docs_sdk_delete.html
Normal file
43
src/pages/docs_sdk_delete.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : Delete</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">Delete</span> lets you delete an item from a user's database. This API will return a promise that gets resolved once the item has been durably deleted from the database.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.delete(databaseName, itemId)
|
||||||
|
.then(() => {
|
||||||
|
// item deleted
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">databaseName</span> [string | Len: 1-50] - The database name to use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">itemId</span> [string | Len: 1-100] - The unique identifier of the item to delete.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>DatabaseNotOpen</li>
|
||||||
|
<li>DatabaseNameCannotBeBlank</li>
|
||||||
|
<li>DatabaseNameTooLong</li>
|
||||||
|
<li>DatabaseNameMustBeString</li>
|
||||||
|
<li>ItemIdCannotBeBlank</li>
|
||||||
|
<li>ItemIdTooLong</li>
|
||||||
|
<li>ItemIdMustBeString</li>
|
||||||
|
<li>ItemDoesNotExist</li>
|
||||||
|
<li>ItemUpdateConflict</li>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
46
src/pages/docs_sdk_insert.html
Normal file
46
src/pages/docs_sdk_insert.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : Insert</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">Insert</span> lets you add items to a user's database. This API will return a promise that gets resolved once the item has been durably inserted into the database.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.insert(databaseName, item, itemId)
|
||||||
|
.then(() => {
|
||||||
|
// item inserted
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">databaseName</span> [string | Len: 1-50] - The database name to use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">item</span> [object | Max size: 10 KB] - The item to insert.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">itemId</span> [string | Len: 1-100] - The item's unique identifier.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>DatabaseNotOpen</li>
|
||||||
|
<li>DatabaseNameCannotBeBlank</li>
|
||||||
|
<li>DatabaseNameTooLong</li>
|
||||||
|
<li>DatabaseNameMustBeString</li>
|
||||||
|
<li>ItemIdTooLong</li>
|
||||||
|
<li>ItemIdMustBeString</li>
|
||||||
|
<li>ItemMissing</li>
|
||||||
|
<li>ItemTooLarge</li>
|
||||||
|
<li>ItemAlreadyExists</li>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
50
src/pages/docs_sdk_open-database.html
Normal file
50
src/pages/docs_sdk_open-database.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : OpenDatabase</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">OpenDatabase</span> lets you retrieve data stored by the users of your web app. You also need to have a database open before storing or modifying user data. This API will return a promise that gets resolved once the database becomes available for use.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.openDatabase(databaseName, changeHandler)
|
||||||
|
.then(() => {
|
||||||
|
// the database can now be used
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">databaseName</span> [string | Len: 1-50] - The database name to use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">changeHandler</span> [function] - A callback that gets invoked when the database gets modified.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">items</span> [Array] - The full collection of items in the database, in insertion order.
|
||||||
|
<ul>
|
||||||
|
<li><span class="field">itemId</span> [string] - The item's unique identifier.</li>
|
||||||
|
<li><span class="field">item</span> [object] - The stored item.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>DatabaseAlreadyOpen</li>
|
||||||
|
<li>DatabaseAlreadyOpening</li>
|
||||||
|
<li>DatabaseNameCannotBeBlank</li>
|
||||||
|
<li>DatabaseNameTooLong</li>
|
||||||
|
<li>DatabaseNameMustBeString</li>
|
||||||
|
<li>ChangeHandlerMissing</li>
|
||||||
|
<li>ChangeHandlerMustBeFunction</li>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
47
src/pages/docs_sdk_sign-in-with-session.html
Normal file
47
src/pages/docs_sdk_sign-in-with-session.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : SignInWithSession</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">SignInWithSession</span> lets you automatically log in a user using the last used session. This API will return a promise that gets resolved once the session has been validated. If the session is valid, the user gets automatically logged in.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.signInWithSession()
|
||||||
|
.then((session) => {
|
||||||
|
if (session.user) {
|
||||||
|
// there is a valid active session
|
||||||
|
console.log(session.user.username)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="result">Result</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">session</span> [object] - Contains information about the last used session.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">user</span> [object] - If set, this contains information about the logged in user.
|
||||||
|
<ul>
|
||||||
|
<li><span class="field">username</span> [string] - The user's username.</li>
|
||||||
|
<li><span class="field">key</span> [string] - The user's encryption key.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><span class="field">lastUsedUsername</span> [string] - If set, this is the username of the last user that was logged in.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>AppIdNotSet</li>
|
||||||
|
<li>AppIdNotValid</li>
|
||||||
|
<li>UserAlreadySignedIn</li>
|
||||||
|
<li>UserCanceledSignIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
57
src/pages/docs_sdk_sign-in.html
Normal file
57
src/pages/docs_sdk_sign-in.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : SignIn</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">SignIn</span> lets your users log into your web app. This API will return a promise that gets resolved once the user has been logged in.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.signIn(username, password)
|
||||||
|
.then((user) => {
|
||||||
|
// user logged in
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">username</span> [string | Len: 1-50] - The username for the account to be created.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">password</span> [string | Len: 6-1000] - The password for the account to be created.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="result">Result</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">user</span> [object] - Contains information about the logged in user.
|
||||||
|
<ul>
|
||||||
|
<li><span class="field">username</span> [string] - The user's username.</li>
|
||||||
|
<li><span class="field">key</span> [string] - The user's encryption key.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>UsernameOrPasswordMismatch</li>
|
||||||
|
<li>UsernameCannotBeBlank</li>
|
||||||
|
<li>UsernameTooLong</li>
|
||||||
|
<li>UsernameMustBeString</li>
|
||||||
|
<li>PasswordCannotBeBlank</li>
|
||||||
|
<li>PasswordTooShort</li>
|
||||||
|
<li>PasswordTooLong</li>
|
||||||
|
<li>PasswordMustBeString</li>
|
||||||
|
<li>AppIdNotSet</li>
|
||||||
|
<li>AppIdNotValid</li>
|
||||||
|
<li>UserAlreadySignedIn</li>
|
||||||
|
<li>UserCanceledSignIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
23
src/pages/docs_sdk_sign-out.html
Normal file
23
src/pages/docs_sdk_sign-out.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : SignOut</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">SignOut</span> lets your users log out of your web app. This API will return a promise that gets resolved once the user has been logged out.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.signOut()
|
||||||
|
.then(() => {
|
||||||
|
// user logged out
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
56
src/pages/docs_sdk_sign-up.html
Normal file
56
src/pages/docs_sdk_sign-up.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : SignUp</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">SignUp</span> lets you create user accounts for your web app. This API will return a promise that gets resolved once the user account has been created.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.signUp(username, password)
|
||||||
|
.then((user) => {
|
||||||
|
// user account created
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">username</span> [string | Len: 1-50] - The username for the account to be created.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">password</span> [string | Len: 6-1000] - The password for the account to be created.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="result">Result</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">user</span> [object] - Contains information about the new user.
|
||||||
|
<ul>
|
||||||
|
<li><span class="field">username</span> [string] - The user's username.</li>
|
||||||
|
<li><span class="field">key</span> [string] - The user's encryption key.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>UsernameAlreadyExists</li>
|
||||||
|
<li>UsernameCannotBeBlank</li>
|
||||||
|
<li>UsernameTooLong</li>
|
||||||
|
<li>UsernameMustBeString</li>
|
||||||
|
<li>PasswordCannotBeBlank</li>
|
||||||
|
<li>PasswordTooShort</li>
|
||||||
|
<li>PasswordTooLong</li>
|
||||||
|
<li>PasswordMustBeString</li>
|
||||||
|
<li>AppIdNotSet</li>
|
||||||
|
<li>AppIdNotValid</li>
|
||||||
|
<li>UserAlreadySignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
56
src/pages/docs_sdk_transaction.html
Normal file
56
src/pages/docs_sdk_transaction.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : Transaction</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">Transaction</span> lets you submit multiple database operations as a single atomic batch. Either all operations succeed, or the entire batch fails. You will never see partial updates. This API will return a promise that gets resolved once the transaction has been fully applied and durably persisted in the database.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">
|
||||||
|
const operations = [
|
||||||
|
{ command: 'Update', item: { todo: 'Item A' }, id: '0001' },
|
||||||
|
{ command: 'Insert', item: { todo: 'Item B' }, id: '0002' },
|
||||||
|
{ command: 'Delete', item: { todo: 'Item C' }, id: '0003' }
|
||||||
|
]
|
||||||
|
|
||||||
|
userbase.transaction(databaseName, operations)
|
||||||
|
.then(() => {
|
||||||
|
// transaction applied
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">databaseName</span> [string | Len: 1-50] - The database name to use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">operations</span> [Array] - The Insert, Update, or Delete operations to execute as an atomic transaction.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>DatabaseNotOpen</li>
|
||||||
|
<li>DatabaseNameCannotBeBlank</li>
|
||||||
|
<li>DatabaseNameTooLong</li>
|
||||||
|
<li>DatabaseNameMustBeString</li>
|
||||||
|
<li>OperationsMissing</li>
|
||||||
|
<li>OperationsMustBeArray</li>
|
||||||
|
<li>OperationsConflict</li>
|
||||||
|
<li>ItemIdCannotBeBlank</li>
|
||||||
|
<li>ItemIdTooLong</li>
|
||||||
|
<li>ItemIdMustBeString</li>
|
||||||
|
<li>ItemTooLarge</li>
|
||||||
|
<li>ItemAlreadyExists</li>
|
||||||
|
<li>ItemDoesNotExist</li>
|
||||||
|
<li>ItemUpdateConflict</li>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
48
src/pages/docs_sdk_update.html
Normal file
48
src/pages/docs_sdk_update.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<div class="section">
|
||||||
|
<h2><a href="/docs/#sdk">SDK</a> : Update</h2>
|
||||||
|
|
||||||
|
<p><span class="font-semibold">Update</span> lets you modify an existing item in a user's database. This API will return a promise that gets resolved once the item has been durably updated in the database.</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">userbase.update(databaseName, item, itemId)
|
||||||
|
.then(() => {
|
||||||
|
// item updated
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="params">Parameters</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="field">databaseName</span> [string | Len: 1-50] - The database name to use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">item</span> [object | Max size: 10 KB] - The object to overwrite the existing item with.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="field">itemId</span> [string | Len: 1-100] - The unique identifier of the item to replace.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="errors">Errors</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>DatabaseNotOpen</li>
|
||||||
|
<li>DatabaseNameCannotBeBlank</li>
|
||||||
|
<li>DatabaseNameTooLong</li>
|
||||||
|
<li>DatabaseNameMustBeString</li>
|
||||||
|
<li>ItemIdCannotBeBlank</li>
|
||||||
|
<li>ItemIdMustBeString</li>
|
||||||
|
<li>ItemIdTooLong</li>
|
||||||
|
<li>ItemMissing</li>
|
||||||
|
<li>ItemTooLarge</li>
|
||||||
|
<li>ItemDoesNotExist</li>
|
||||||
|
<li>ItemUpdateConflict</li>
|
||||||
|
<li>UserNotSignedIn</li>
|
||||||
|
<li>ServiceUnavailable</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
<div class="flex justify-center pb-4">
|
||||||
|
<a href="/"><img alt="Userbase" id="logo" src="../img/logo.png"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h1>Create <span class="highlight">secure</span> and <span class="highlight">GDPR-compliant</span> web apps using only static JavaScript, HTML, and CSS.</h1>
|
<h1>Create <span class="highlight">secure</span> and <span class="highlight">GDPR-compliant</span> web apps using only static JavaScript, HTML, and CSS.</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,10 +14,10 @@
|
|||||||
<p>Userbase is a database and backend service, purpose-built for web apps. It is accessible directly from the browser through a very simple JavaScript SDK.</p>
|
<p>Userbase is a database and backend service, purpose-built for web apps. It is accessible directly from the browser through a very simple JavaScript SDK.</p>
|
||||||
|
|
||||||
<h3 id="zero-management-database">Zero-management database</h3>
|
<h3 id="zero-management-database">Zero-management database</h3>
|
||||||
<p>Unlike a regular database, all Userbase queries run in the browser. The server-side is just a dumb data store, and there's no database to manage or worry about.</p>
|
<p>Unlike a regular database, all Userbase queries run in the browser. The server-side is just a dumb data store, and there's nothing on the backend to manage or worry about.</p>
|
||||||
|
|
||||||
<h3 id="user-accounts">Built-in user accounts</h3>
|
<h3 id="user-accounts">Built-in user accounts</h3>
|
||||||
<p>Userbase takes care of your user accounts. It comes with built-in APIs for user signups, logins, password resets, and access control.</p>
|
<p>Userbase takes care of your user accounts. It comes with built-in APIs for user signups, logins, and access control.</p>
|
||||||
|
|
||||||
<h3 id="e2ee">End-to-end encryption</h3>
|
<h3 id="e2ee">End-to-end encryption</h3>
|
||||||
<p>Userbase won't show you what your users store in your web app. It spares you from the liability of handling user data by encrypting everything in the browser, using keys that always stay with the user.</p>
|
<p>Userbase won't show you what your users store in your web app. It spares you from the liability of handling user data by encrypting everything in the browser, using keys that always stay with the user.</p>
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
<form method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" target="_blank" class="validate text-center" action="https://danielvassallo.us20.list-manage.com/subscribe/post?u=e3b0fd293a0e6d7ea0080fafe&id=d0aae9dd3e">
|
<form method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" target="_blank" class="validate text-center" action="https://danielvassallo.us20.list-manage.com/subscribe/post?u=e3b0fd293a0e6d7ea0080fafe&id=d0aae9dd3e">
|
||||||
<input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false" placeholder="Email address" class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded my-4 inline-block">
|
<input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false" placeholder="Email address" class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded my-4 inline-block">
|
||||||
|
|
||||||
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button">
|
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button hover:text-orange-700">
|
||||||
|
|
||||||
<div class="mc-status" class="hidden"></div>
|
<div class="mc-status" class="hidden"></div>
|
||||||
<div style="position: absolute; left: -5000px;" aria-hidden="true">
|
<div style="position: absolute; left: -5000px;" aria-hidden="true">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false" placeholder="Email address" class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded my-4 inline-block">
|
<input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false" placeholder="Email address" class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded my-4 inline-block">
|
||||||
|
|
||||||
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button">
|
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button hover:text-orange-700">
|
||||||
|
|
||||||
<div class="mc-status" class="hidden"></div>
|
<div class="mc-status" class="hidden"></div>
|
||||||
<div style="position: absolute; left: -5000px;" aria-hidden="true">
|
<div style="position: absolute; left: -5000px;" aria-hidden="true">
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<div class="font-sans tracking-tight my-10 text-xs text-center">Copyright © 2019 Encrypted Development LLC</div>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<header class="sm:flex sm:justify-between sm:items-center p-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<a href="/"><img alt="Userbase" class="h-8 sm:h-12" src="../img/logo.png"></a>
|
|
||||||
</div>
|
|
||||||
<div class="sm:hidden">
|
|
||||||
<button onclick="toggleMenu()" type="button" class="block text-blackish hover:text-orange-700 focus:text-orange-700 focus:outline-none">
|
|
||||||
<svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
|
|
||||||
<path class="hidden menu-close" fill-rule="evenodd" d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z" />
|
|
||||||
<path class="menu-open" fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav class="pt-2 pb-4 hidden sm:flex sm:p-0 text-lg text-right menu">
|
|
||||||
<a href="/mailing-list.html" class="block font-extrabold py-2 leading-none sm:px-4">Mailing list</a>
|
|
||||||
<a href="mailto:daniel@encrypted.dev" class="block font-extrabold py-2 leading-none sm:px-4">Contact</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
3
src/partials/logo.html
Normal file
3
src/partials/logo.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<a href="/"><img alt="Userbase" class="h-8" src="../img/logo-horizontal.png"></a>
|
||||||
|
</div>
|
||||||
355
src/prism.css
Normal file
355
src/prism.css
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
/* PrismJS 1.17.1
|
||||||
|
https://prismjs.com/download.html#themes=prism-coy&languages=markup+css+clike+javascript&plugins=line-highlight+toolbar+unescaped-markup+normalize-whitespace+copy-to-clipboard */
|
||||||
|
/**
|
||||||
|
* prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML
|
||||||
|
* Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);
|
||||||
|
* @author Tim Shedor
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
position: relative;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
pre[class*="language-"]>code {
|
||||||
|
position: relative;
|
||||||
|
border-left: 10px solid #358ccb;
|
||||||
|
box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);
|
||||||
|
background-size: 3em 3em;
|
||||||
|
background-origin: content-box;
|
||||||
|
background-attachment: local;
|
||||||
|
}
|
||||||
|
|
||||||
|
code[class*="language"] {
|
||||||
|
max-height: inherit;
|
||||||
|
height: inherit;
|
||||||
|
padding: 0 1em;
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Margin bottom to accommodate shadow */
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
position: relative;
|
||||||
|
padding: .2em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
color: #c92c2c;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
display: inline;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]:before,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
content: '';
|
||||||
|
z-index: -2;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.75em;
|
||||||
|
left: 0.18em;
|
||||||
|
width: 40%;
|
||||||
|
height: 20%;
|
||||||
|
max-height: 13em;
|
||||||
|
box-shadow: 0px 13px 8px #979797;
|
||||||
|
-webkit-transform: rotate(-2deg);
|
||||||
|
-moz-transform: rotate(-2deg);
|
||||||
|
-ms-transform: rotate(-2deg);
|
||||||
|
-o-transform: rotate(-2deg);
|
||||||
|
transform: rotate(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"]:after,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
right: 0.75em;
|
||||||
|
left: auto;
|
||||||
|
-webkit-transform: rotate(2deg);
|
||||||
|
-moz-transform: rotate(2deg);
|
||||||
|
-ms-transform: rotate(2deg);
|
||||||
|
-o-transform: rotate(2deg);
|
||||||
|
transform: rotate(2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.block-comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #7D8B99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #5F6364;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.function-name,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #c92c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.function,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #2f9c0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.token.variable {
|
||||||
|
color: #a67f59;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword,
|
||||||
|
.token.class-name {
|
||||||
|
color: #1990b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #a67f59;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
pre[class*="language-"]:before,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
bottom: 14px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles */
|
||||||
|
.token.tab:not(:empty):before,
|
||||||
|
.token.cr:before,
|
||||||
|
.token.lf:before {
|
||||||
|
color: #e0d7d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles: Line Numbers */
|
||||||
|
pre[class*="language-"].line-numbers.line-numbers {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers.line-numbers code {
|
||||||
|
padding-left: 3.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers.line-numbers .line-numbers-rows {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles: Line Highlight */
|
||||||
|
pre[class*="language-"][data-line] {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
pre[data-line] code {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 4em;
|
||||||
|
}
|
||||||
|
pre .line-highlight {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[data-line] {
|
||||||
|
position: relative;
|
||||||
|
padding: 1em 0 1em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: inherit 0;
|
||||||
|
margin-top: 1em; /* Same as .prism’s padding-top */
|
||||||
|
|
||||||
|
background: hsla(24, 20%, 50%,.08);
|
||||||
|
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
line-height: inherit;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight:before,
|
||||||
|
.line-highlight[data-end]:after {
|
||||||
|
content: attr(data-start);
|
||||||
|
position: absolute;
|
||||||
|
top: .4em;
|
||||||
|
left: .6em;
|
||||||
|
min-width: 1em;
|
||||||
|
padding: 0 .5em;
|
||||||
|
background-color: hsla(24, 20%, 50%,.4);
|
||||||
|
color: hsl(24, 20%, 95%);
|
||||||
|
font: bold 65%/1.5 sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: .3em;
|
||||||
|
border-radius: 999px;
|
||||||
|
text-shadow: none;
|
||||||
|
box-shadow: 0 1px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight[data-end]:after {
|
||||||
|
content: attr(data-end);
|
||||||
|
top: auto;
|
||||||
|
bottom: .4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers .line-highlight:before,
|
||||||
|
.line-numbers .line-highlight:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: .3em;
|
||||||
|
right: .2em;
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar:hover > .toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separate line b/c rules are thrown out if selector is invalid.
|
||||||
|
IE11 and old Edge versions don't support :focus-within. */
|
||||||
|
div.code-toolbar:focus-within > .toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar .toolbar-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar button {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
line-height: normal;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-user-select: none; /* for button */
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar a,
|
||||||
|
div.code-toolbar > .toolbar button,
|
||||||
|
div.code-toolbar > .toolbar span {
|
||||||
|
color: #bbb;
|
||||||
|
font-size: .8em;
|
||||||
|
padding: 0 .5em;
|
||||||
|
background: #f5f2f0;
|
||||||
|
background: rgba(224, 224, 224, 0.2);
|
||||||
|
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar a:hover,
|
||||||
|
div.code-toolbar > .toolbar a:focus,
|
||||||
|
div.code-toolbar > .toolbar button:hover,
|
||||||
|
div.code-toolbar > .toolbar button:focus,
|
||||||
|
div.code-toolbar > .toolbar span:hover,
|
||||||
|
div.code-toolbar > .toolbar span:focus {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback, in case JS does not run, to ensure the code is at least visible */
|
||||||
|
[class*='lang-'] script[type='text/plain'],
|
||||||
|
[class*='language-'] script[type='text/plain'],
|
||||||
|
script[type='text/plain'][class*='lang-'],
|
||||||
|
script[type='text/plain'][class*='language-'] {
|
||||||
|
display: block;
|
||||||
|
font: 100% Consolas, Monaco, monospace;
|
||||||
|
white-space: pre;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
12
src/prism.js
Normal file
12
src/prism.js
Normal file
File diff suppressed because one or more lines are too long
@@ -20,12 +20,32 @@ a:hover {
|
|||||||
@apply text-orange-700;
|
@apply text-orange-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.menu-item {
|
||||||
|
@apply block font-black py-2 leading-none no-underline
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
a.menu-item {
|
||||||
|
@apply px-3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
@apply w-84
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
#logo {
|
||||||
|
width: 441px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@apply mb-4;
|
@apply mb-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-center text-2xl font-black leading-tight tracking-tight;
|
@apply text-center text-2xl font-extrabold leading-tight tracking-tight;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@@ -33,21 +53,35 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@apply font-bold text-xl tracking-tight;
|
@apply font-bold text-xl tracking-tight mt-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@apply font-bold text-xl tracking-tight mt-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@screen sm {
|
@screen sm {
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-4xl;
|
@apply text-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@apply text-5xl;
|
@apply text-4xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@apply text-3xl;
|
@apply text-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
h2 {
|
||||||
|
@apply text-5xl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@@ -80,4 +114,54 @@ ul {
|
|||||||
background-image: linear-gradient(119deg, rgb(255, 255, 255), rgb(255, 246, 151) 10.5%, #fdf59d 85.29%, #ffffff);
|
background-image: linear-gradient(119deg, rgb(255, 255, 255), rgb(255, 246, 151) 10.5%, #fdf59d 85.29%, #ffffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
@apply font-semibold
|
||||||
|
}
|
||||||
|
|
||||||
|
a.anchor {
|
||||||
|
@apply block relative;
|
||||||
|
top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]:before, pre[class*="language-"]:after {
|
||||||
|
content: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"], :not(pre) > code[class*="language-"], pre[class*="language-"] {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code[class*="language"] {
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]>code {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
box-shadow: none;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-highlight:before, .line-highlight[data-end]:after {
|
||||||
|
content: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[data-line] code {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .line-highlight {
|
||||||
|
margin-top: 16px;
|
||||||
|
background-image: none;
|
||||||
|
background-color: rgba(0, 255, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator, .token.entity, .token.url, .token.variable {
|
||||||
|
color: #ff8000;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|||||||
@@ -8,10 +8,41 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<%= require('html-loader!./partials/' + header + '.html') %>
|
<header class="sticky top-0 bg-white z-50">
|
||||||
|
<div class="sm:flex sm:justify-between sm:items-center py-2 px-4">
|
||||||
|
<div class="flex items-center justify-between h-10">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<%= page !== 'index' ? require('html-loader!./partials/logo.html') : null %>
|
||||||
|
</div>
|
||||||
|
<div class="sm:hidden">
|
||||||
|
<button onclick="toggleMenu()" type="button" class="block text-blackish hover:text-orange-700 focus:text-orange-700 focus:outline-none">
|
||||||
|
<svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path class="hidden menu-close" fill-rule="evenodd" d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z" />
|
||||||
|
<path class="menu-open" fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="pt-2 pb-4 hidden sm:flex sm:p-0 text-lg text-center menu">
|
||||||
|
<a href="/docs" class="menu-item">Docs</a>
|
||||||
|
<a href="/mailing-list" class="menu-item">Mailing list</a>
|
||||||
|
<a href="mailto:daniel@encrypted.dev" class="menu-item">Contact</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
<div class="container mx-auto max-w-4xl px-4">
|
<div class="container mx-auto max-w-4xl px-4">
|
||||||
<%= require('html-loader!./pages/' + page + '.html') %>
|
<%= require('html-loader!./pages/' + page + '.html') %>
|
||||||
<%= require('html-loader!./partials/' + footer + '.html') %>
|
<footer class="text-center py-4 tracking-tight text-sm">
|
||||||
|
<div>
|
||||||
|
<nav class="pt-2 pb-4 text-center menu">
|
||||||
|
<a href="http://github.com/encrypted-dev/userbase" class="py-2 leading-none px-2">GitHub</a>
|
||||||
|
<a href="https://twitter.com/UserbaseHQ" class="py-2 leading-none px-2">Twitter</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-500 text-xs">
|
||||||
|
Copyright © 2019 Encrypted Development LLC
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ module.exports = {
|
|||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
yellowish: '#ffe77a',
|
yellowish: '#ffe77a',
|
||||||
blackish: '#1a2a30'
|
blackish: '#1a2a30',
|
||||||
|
blue: '#358ccb'
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
'72': '18rem',
|
'72': '18rem',
|
||||||
'84': '21rem',
|
'84': '21rem',
|
||||||
'96': '24rem'
|
'96': '24rem'
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
'logo': 'Tahoma, Geneva, Verdana, sans-serif'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
'logo': 'Tahoma, Geneva, Verdana, sans-serif'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
variants: {},
|
variants: {},
|
||||||
|
|||||||
@@ -15,10 +15,22 @@ module.exports = (env, argv) => {
|
|||||||
const pages = []
|
const pages = []
|
||||||
|
|
||||||
fs.readdirSync('./src/pages/').forEach(file => {
|
fs.readdirSync('./src/pages/').forEach(file => {
|
||||||
|
const page = file.split('.')[0]
|
||||||
|
let filename = './'
|
||||||
|
|
||||||
|
if (page !== 'index') {
|
||||||
|
const pagePath = page.split('_')
|
||||||
|
for (let i = 0; i < pagePath.length; i++) {
|
||||||
|
filename += pagePath[i] + '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename += 'index.html'
|
||||||
|
|
||||||
pages.push(new HtmlWebPackPlugin({
|
pages.push(new HtmlWebPackPlugin({
|
||||||
template: './src/template.html',
|
template: './src/template.html',
|
||||||
filename: './' + file,
|
filename,
|
||||||
templateParameters() { return { header: 'header', footer: 'footer', page: file.split('.')[0] } }
|
templateParameters() { return { page } }
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -93,10 +105,8 @@ module.exports = (env, argv) => {
|
|||||||
|
|
||||||
config.devServer = {
|
config.devServer = {
|
||||||
watchContentBase: true,
|
watchContentBase: true,
|
||||||
historyApiFallback: true,
|
|
||||||
hot: true,
|
hot: true,
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 3000
|
port: 3000
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user