diff --git a/cypress/integration/identity_creation.spec.js b/cypress/integration/identity_creation.spec.js
deleted file mode 100644
index 170dea0866..0000000000
--- a/cypress/integration/identity_creation.spec.js
+++ /dev/null
@@ -1,140 +0,0 @@
-context("identity creation", () => {
- const validUser = {
- handle: "rafalca",
- };
-
- beforeEach(() => {
- cy.nukeCocoState();
- cy.nukeSessionState();
- cy.visit("./public/index.html");
- });
-
- context("modal", () => {
- it("can't be closed by pressing escape key", () => {
- cy.pick("get-started-button").should("exist");
- cy.get("body").type("{esc}");
- cy.pick("get-started-button").should("exist");
- });
- });
-
- context("navigation", () => {
- it("is possible to step through the identity creation flow", () => {
- // Intro screen
- cy.pick("get-started-button").click();
-
- // Enter details screen
- cy.pick("form", "handle").type(validUser.handle);
- cy.pick("create-id-button").click();
-
- // Confirmation screen
- cy.get(`[data-cy="identity-card"]`).should("exist");
- cy.pick("identity-card").contains(validUser.handle).should("exist");
-
- // Land on profile screen
- cy.pick("go-to-profile-button").click();
- cy.pick("entity-name").contains(validUser.handle);
- });
-
- context(
- "when clicking cancel, close or hitting esc before the identity is created",
- () => {
- it("sends the user back to the intro screen", () => {
- cy.pick("get-started-button").click();
- cy.pick("cancel-button").click();
-
- // We should land back on the intro screen
- cy.pick("get-started-button").click();
-
- // Now try to close the modal via the "x" button
- cy.pick("modal-close-button").click();
-
- // We should land back on the intro screen
- cy.pick("get-started-button").click();
-
- // Now try the escape key
- cy.get("body").type("{esc}");
-
- // We should land back on the intro screen
- cy.pick("get-started-button").should("exist");
- });
- }
- );
-
- context(
- "when clicking the modal close button on the success screen",
- () => {
- it("lands the user on the profile screen", () => {
- cy.pick("get-started-button").click();
-
- cy.pick("form", "handle").type(validUser.handle);
- cy.pick("create-id-button").click();
-
- cy.pick("identity-card").contains(validUser.handle).should("exist");
-
- // Land on profile screen
- cy.pick("modal-close-button").click();
- cy.pick("entity-name").contains(validUser.handle);
- });
- }
- );
-
- context("when pressing escape on the success screen", () => {
- it("lands the user on the profile screen", () => {
- cy.pick("get-started-button").click();
-
- cy.pick("form", "handle").type(validUser.handle);
- cy.pick("create-id-button").click();
-
- cy.pick("identity-card").contains(validUser.handle).should("exist");
-
- // Now try the escape key
- cy.get("body").type("{esc}");
-
- // Land on profile screen
- cy.pick("entity-name").contains(validUser.handle);
- });
- });
- });
-
- context("validations", () => {
- beforeEach(() => {
- cy.pick("get-started-button").click();
- cy.pick("form", "handle").type("_rafalca");
- cy.pick("create-id-button").click();
- });
-
- context("handle", () => {
- const validationError = "Handle should match ^[a-z0-9][a-z0-9_-]+$";
-
- it("prevents the user from submitting an invalid handle", () => {
- // handle is required
- cy.pick("form", "handle").clear();
- cy.pick("form").contains("You must provide a handle");
-
- // no spaces
- cy.pick("form", "handle").type("no spaces");
- cy.pick("form").contains(validationError);
-
- // no special characters
- cy.pick("form", "handle").clear();
- cy.pick("form", "handle").type("$bad");
- cy.pick("form").contains(validationError);
-
- // can't start with an underscore
- cy.pick("form", "handle").clear();
- cy.pick("form", "handle").type("_nein");
- cy.pick("form").contains(validationError);
-
- // can't start with a dash
- cy.pick("form", "handle").clear();
- cy.pick("form", "handle").type("-não");
- cy.pick("form").contains(validationError);
-
- // has to be at least two characters long
- cy.pick("form", "handle").clear();
- cy.pick("form", "handle").type("x");
- cy.pick("form").contains(validationError);
- });
- });
- });
-});
diff --git a/cypress/integration/onboarding.spec.js b/cypress/integration/onboarding.spec.js
new file mode 100644
index 0000000000..3fdd40a9a3
--- /dev/null
+++ b/cypress/integration/onboarding.spec.js
@@ -0,0 +1,190 @@
+context("identity creation", () => {
+ const validUser = {
+ handle: "rafalca",
+ passphrase: "curled",
+ };
+
+ beforeEach(() => {
+ cy.nukeAllState();
+ cy.visit("./public/index.html");
+ cy.pick("welcome-screen").should("exist");
+ });
+
+ context("navigation", () => {
+ it("is not possible to exit the flow by pressing escape key", () => {
+ cy.pick("get-started-button").should("exist");
+ cy.get("body").type("{esc}");
+ cy.pick("get-started-button").should("exist");
+ });
+
+ it("makes sure global hotkeys are disabled during onboarding", () => {
+ cy.pick("get-started-button").should("exist");
+ cy.get("body").type("?");
+ cy.pick("hotkey-modal").should("not.exist");
+ });
+
+ it("is possible to use the keyboard for navigation", () => {
+ // Intro screen.
+ cy.get("body").type("{enter}");
+
+ // Enter name screen.
+ cy.focused().type(validUser.handle);
+ cy.focused().type("{enter}");
+
+ // Enter passphrase screen.
+ cy.pick("enter-name-screen").should("exist");
+ cy.focused().type(validUser.passphrase);
+ cy.focused().type("{enter}");
+ cy.focused().type(validUser.passphrase);
+ cy.focused().type("{enter}");
+
+ // Success screen.
+ cy.pick("urn")
+ .contains(/rafalca@/)
+ .should("exist");
+
+ // Land on profile screen.
+ cy.get("body").type("{enter}");
+ cy.pick("entity-name").contains(validUser.handle);
+ });
+
+ it("is possible to step through the identity creation flow", () => {
+ // Intro screen.
+ cy.pick("get-started-button").click();
+
+ // Enter name screen.
+ cy.pick("handle-input").type(validUser.handle);
+ cy.pick("next-button").click();
+
+ // Enter passphrase screen.
+ cy.pick("passphrase-input").type(validUser.passphrase);
+ cy.pick("repeat-passphrase-input").type(validUser.passphrase);
+ cy.pick("set-passphrase-button").click();
+
+ // Success screen.
+ cy.pick("urn")
+ .contains(/rafalca@/)
+ .should("exist");
+
+ // Land on profile screen.
+ cy.pick("go-to-profile-button").click();
+ cy.pick("entity-name").contains(validUser.handle);
+
+ // Clear session to restart onboarding.
+ cy.pick("sidebar", "settings").click();
+ cy.pick("clear-session-button").click();
+ cy.contains("A free and open-source way to host").should("exist");
+
+ // When creating the same identity again without nuking all data, it
+ // should show an error and return to the name entry screen.
+ cy.pick("get-started-button").click();
+
+ cy.pick("handle-input").type(validUser.handle);
+ cy.pick("next-button").click();
+
+ cy.pick("passphrase-input").type(validUser.passphrase);
+ cy.pick("repeat-passphrase-input").type(validUser.passphrase);
+ cy.pick("set-passphrase-button").click();
+ cy.pick("notification")
+ .contains(
+ /Could not create identity: the identity 'rad:git:[\w]{3}…[\w]{3}' already exits/
+ )
+ .should("exist");
+ cy.pick("notification").contains("Close").click();
+ cy.contains("what should we call you?").should("exist");
+
+ // We can create a different identity with a new handle.
+ cy.pick("handle-input").clear();
+ cy.pick("handle-input").type("cloudhead");
+ cy.pick("next-button").click();
+ cy.pick("passphrase-input").type("1234");
+ cy.pick("repeat-passphrase-input").type("1234");
+ cy.pick("set-passphrase-button").click();
+ cy.pick("urn")
+ .contains(/cloudhead@/)
+ .should("exist");
+ cy.pick("go-to-profile-button").click();
+ cy.pick("entity-name").contains("cloudhead");
+ });
+
+ context("when clicking the back button on the passphrase screen", () => {
+ it("sends the user back to the previous screen", () => {
+ cy.pick("get-started-button").click();
+ cy.pick("handle-input").type(validUser.handle);
+ cy.pick("next-button").click();
+ cy.pick("enter-passphrase-screen").should("exist");
+ cy.pick("back-button").click();
+ cy.contains("what should we call you?").should("exist");
+ cy.pick("handle-input").should("have.value", validUser.handle);
+ });
+ });
+ });
+
+ context("validations", () => {
+ beforeEach(() => {
+ cy.pick("get-started-button").click();
+ });
+
+ context("handle", () => {
+ it("prevents the user from submitting an invalid handle", () => {
+ const validationError = "Handle should match ^[a-z0-9][a-z0-9_-]+$";
+
+ cy.pick("handle-input").type("_rafalca");
+ cy.pick("next-button").click();
+
+ // Handle is required.
+ cy.pick("handle-input").clear();
+ cy.pick("enter-name-screen").contains("You must provide a handle");
+
+ // No spaces.
+ cy.pick("handle-input").type("no spaces");
+ cy.pick("enter-name-screen").contains(validationError);
+
+ // No special characters.
+ cy.pick("handle-input").clear();
+ cy.pick("handle-input").type("$bad");
+ cy.pick("enter-name-screen").contains(validationError);
+
+ // Can't start with an underscore.
+ cy.pick("handle-input").clear();
+ cy.pick("handle-input").type("_nein");
+ cy.pick("enter-name-screen").contains(validationError);
+
+ // Can't start with a dash.
+ cy.pick("handle-input").clear();
+ cy.pick("handle-input").type("-não");
+ cy.pick("enter-name-screen").contains(validationError);
+
+ // Has to be at least two characters long.
+ cy.pick("handle-input").clear();
+ cy.pick("handle-input").type("x");
+ cy.pick("enter-name-screen").contains(validationError);
+ });
+ });
+
+ context("passphrase", () => {
+ it("prevents the user from submitting an invalid passphrase", () => {
+ cy.pick("handle-input").type("cloudhead");
+ cy.pick("next-button").click();
+
+ // Only entered once.
+ cy.pick("passphrase-input").type("123");
+ cy.pick("set-passphrase-button").should("be.disabled");
+
+ // Too short.
+ cy.pick("repeat-passphrase-input").type("123");
+ cy.contains("Passphrase must be at least 4 characters").should("exist");
+
+ // Does not match.
+ cy.pick("passphrase-input").type("4");
+ cy.pick("repeat-passphrase-input").type("5");
+ cy.contains("Passphrases should match").should("exist");
+
+ // Valid passphrase.
+ cy.pick("passphrase-input").clear().type("abcd");
+ cy.pick("repeat-passphrase-input").clear().type("abcd");
+ cy.pick("set-passphrase-button").should("not.be.disabled");
+ });
+ });
+ });
+});
diff --git a/cypress/integration/project_checkout.spec.js b/cypress/integration/project_checkout.spec.js
index d547c253fa..b080cc040e 100644
--- a/cypress/integration/project_checkout.spec.js
+++ b/cypress/integration/project_checkout.spec.js
@@ -35,7 +35,7 @@ const withWorkspaceStub = callback => {
beforeEach(() => {
cy.nukeAllState();
- cy.createIdentity();
+ cy.onboardUser();
cy.createProjectWithFixture();
cy.visit("./public/index.html");
});
diff --git a/cypress/integration/project_creation.spec.js b/cypress/integration/project_creation.spec.js
index 822cb3db36..ff5ab8238a 100644
--- a/cypress/integration/project_creation.spec.js
+++ b/cypress/integration/project_creation.spec.js
@@ -61,7 +61,7 @@ const withPlatinumStub = callback => {
beforeEach(() => {
cy.nukeAllState();
- cy.createIdentity();
+ cy.onboardUser();
cy.visit("./public/index.html");
});
diff --git a/cypress/integration/project_source_browsing.spec.js b/cypress/integration/project_source_browsing.spec.js
index 47f9efbcbc..00140ba0fb 100644
--- a/cypress/integration/project_source_browsing.spec.js
+++ b/cypress/integration/project_source_browsing.spec.js
@@ -1,6 +1,6 @@
before(() => {
cy.nukeAllState();
- cy.createIdentity("cloudhead");
+ cy.onboardUser("cloudhead");
cy.createProjectWithFixture("platinum", "Best project ever.", "master", [
"ele",
"abbey",
diff --git a/cypress/integration/routing.spec.js b/cypress/integration/routing.spec.js
index 18a4fc5414..5b589640e5 100644
--- a/cypress/integration/routing.spec.js
+++ b/cypress/integration/routing.spec.js
@@ -14,7 +14,7 @@ context("routing", () => {
"when there is no additional routing information stored in the browser location",
() => {
it("opens the app on the profile screen", () => {
- cy.createIdentity();
+ cy.onboardUser();
cy.visit("./public/index.html");
cy.location().should(loc => {
@@ -28,7 +28,7 @@ context("routing", () => {
"when there is additional routing information stored in the browser location",
() => {
it("resumes the app from the browser location", () => {
- cy.createIdentity();
+ cy.onboardUser();
cy.visit("./public/index.html");
cy.pick("sidebar", "settings").click();
diff --git a/cypress/integration/settings_specs.js b/cypress/integration/settings_specs.js
index eb1951b8f9..7f03ab69b2 100644
--- a/cypress/integration/settings_specs.js
+++ b/cypress/integration/settings_specs.js
@@ -2,7 +2,7 @@ context("settings", () => {
beforeEach(() => {
cy.nukeCocoState();
cy.nukeSessionState();
- cy.createIdentity();
+ cy.onboardUser();
cy.visit("public/index.html");
cy.pick("sidebar", "settings").click();
diff --git a/cypress/integration/user_profile.spec.js b/cypress/integration/user_profile.spec.js
index 66aa54ccda..69de57f408 100644
--- a/cypress/integration/user_profile.spec.js
+++ b/cypress/integration/user_profile.spec.js
@@ -1,6 +1,6 @@
before(() => {
cy.nukeAllState();
- cy.createIdentity("cloudhead");
+ cy.onboardUser("cloudhead");
cy.createProjectWithFixture("platinum", "Best project ever.", "master", [
"ele",
"abbey",
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 7a3ff9e1b0..801ca5d4da 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -46,7 +46,7 @@ Cypress.Commands.add(
);
Cypress.Commands.add(
- "createIdentity",
+ "onboardUser",
async (handle = "secretariat") =>
await fetch("http://localhost:8080/v1/identities", {
method: "POST",
diff --git a/package.json b/package.json
index bfbb4b10f9..e847eb46a4 100644
--- a/package.json
+++ b/package.json
@@ -86,7 +86,7 @@
"npm-run-all": "^4.1.5",
"patch-package": "^6.2.2",
"prettier": "^2.0.5",
- "prettier-plugin-svelte": "^1.1.0",
+ "prettier-plugin-svelte": "^1.2.0",
"rollup": "^2.26.9",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-livereload": "^1.3.0",
diff --git a/ui/App.svelte b/ui/App.svelte
index 6f6a2db8b2..9780f3d07e 100644
--- a/ui/App.svelte
+++ b/ui/App.svelte
@@ -4,6 +4,8 @@
import * as notification from "./src/notification.ts";
import * as path from "./src/path.ts";
import * as remote from "./src/remote.ts";
+ import * as hotkeys from "./src/hotkeys.ts";
+
import { clear, fetch, session as store } from "./src/session.ts";
import {
@@ -17,7 +19,7 @@
import Theme from "./Theme.svelte";
import Blank from "./Screen/Blank.svelte";
- import IdentityCreation from "./Screen/IdentityCreation.svelte";
+ import Onboarding from "./Screen/Onboarding.svelte";
import DesignSystemGuide from "./Screen/DesignSystemGuide.svelte";
import Discovery from "./Screen/Discovery.svelte";
import Modal from "./Modal";
@@ -37,7 +39,7 @@
const routes = {
"/": Blank,
- "/identity/new": IdentityCreation,
+ "/onboarding": Onboarding,
"/settings": Settings,
"/discovery": Discovery,
"/profile/*": Profile,
@@ -71,9 +73,11 @@
case remote.Status.Success:
if ($store.data.identity === null) {
- push(path.createIdentity());
+ hotkeys.disable();
+ push(path.onboarding());
} else {
- if ($location === "/" || $location === "/identity/new") {
+ hotkeys.enable();
+ if ($location === path.blank() || $location === path.onboarding()) {
push(path.profileProjects());
}
}
diff --git a/ui/DesignSystem/Component/HorizontalMenu.svelte b/ui/DesignSystem/Component/HorizontalMenu.svelte
index c8f73b63f0..60615a0862 100644
--- a/ui/DesignSystem/Component/HorizontalMenu.svelte
+++ b/ui/DesignSystem/Component/HorizontalMenu.svelte
@@ -46,6 +46,5 @@
active={path.active(item.href, $location, item.looseActiveStateMatching)} />
{/each}
-
diff --git a/ui/DesignSystem/Component/RemoteHelperHint.svelte b/ui/DesignSystem/Component/RemoteHelperHint.svelte
index b31eb0a637..5759ac2d61 100644
--- a/ui/DesignSystem/Component/RemoteHelperHint.svelte
+++ b/ui/DesignSystem/Component/RemoteHelperHint.svelte
@@ -42,10 +42,8 @@
- Before you get started, you need to
-
- add this to your shell configuration file. Not sure how?
-
diff --git a/ui/DesignSystem/Component/Wallet/TxListItem.svelte b/ui/DesignSystem/Component/Wallet/TxListItem.svelte
index c5274854d7..0105f556cb 100644
--- a/ui/DesignSystem/Component/Wallet/TxListItem.svelte
+++ b/ui/DesignSystem/Component/Wallet/TxListItem.svelte
@@ -144,6 +144,5 @@
rad={summary.total.rad}
usd="{summary.total.usd}}" />
{/if}
-
diff --git a/ui/DesignSystem/Primitive/Input/Password.svelte b/ui/DesignSystem/Primitive/Input/Password.svelte
new file mode 100644
index 0000000000..c6171e5098
--- /dev/null
+++ b/ui/DesignSystem/Primitive/Input/Password.svelte
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+ {#if validation && validation.status === ValidationStatus.Error}
+
+
+ {/if}
+
diff --git a/ui/DesignSystem/Primitive/Input/Text.svelte b/ui/DesignSystem/Primitive/Input/Text.svelte
index fb642a1c0c..e0d88a1766 100644
--- a/ui/DesignSystem/Primitive/Input/Text.svelte
+++ b/ui/DesignSystem/Primitive/Input/Text.svelte
@@ -1,4 +1,6 @@
-
+
diff --git a/ui/Screen/DesignSystemGuide.svelte b/ui/Screen/DesignSystemGuide.svelte
index a94cad9123..3a7a07c890 100644
--- a/ui/Screen/DesignSystemGuide.svelte
+++ b/ui/Screen/DesignSystemGuide.svelte
@@ -350,7 +350,6 @@
Primitives
-
{#each colors as color}
-
Radicle Upstream
@@ -419,7 +417,6 @@
`}>
Radicle Upstream
-
@@ -441,7 +438,6 @@
-
@@ -644,6 +638,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
How about a checkbox?
@@ -672,7 +683,6 @@
-
@@ -1071,7 +1079,6 @@
text="Hey, I'm a tent."
illustration={IllustrationVariant.Tent} />
-
diff --git a/ui/Screen/Discovery/Project.svelte b/ui/Screen/Discovery/Project.svelte
index 37335345ba..cf98a9e2b0 100644
--- a/ui/Screen/Discovery/Project.svelte
+++ b/ui/Screen/Discovery/Project.svelte
@@ -92,7 +92,6 @@
{#if showTrackButton}
{/if}
-
- import { replace } from "svelte-spa-router";
-
- import * as notification from "../src/notification.ts";
- import { State, store } from "../src/onboard.ts";
- import * as path from "../src/path.ts";
- import * as session from "../src/session.ts";
-
- import { ModalLayout, RadicleLogo } from "../DesignSystem/Component";
- import { Button } from "../DesignSystem/Primitive";
-
- import Form from "./IdentityCreation/Form.svelte";
- import Success from "./IdentityCreation/Success.svelte";
-
- const returnToWelcomeStep = () => {
- store.set(State.Welcome);
- };
-
- const truncateUrn = message => {
- const urn = message.match(/(rad:git:\w{59})/)[1];
-
- if (urn) {
- return message.replace(/(rad:git:\w{59})/, urn.substr(-5));
- } else {
- return message;
- }
- };
-
- const onError = event => {
- notification.error(
- `Could not create identity: ${truncateUrn(event.detail.message)}`
- );
- };
-
- const complete = redirectPath => {
- session.fetch();
- store.set(State.Complete);
- replace(redirectPath);
- };
-
- const onClose = () => {
- switch ($store) {
- case State.Welcome:
- return;
- case State.Form:
- returnToWelcomeStep();
- return;
- case State.SuccessView:
- complete(path.profileProjects());
- return;
- }
- };
-
- const onRegister = () => {
- replace(path.profileProjects());
- complete(path.registerUser());
- };
-
-
-
-
-
- {#if $store === State.Welcome}
-
-
-
- A free and open-source way to host, share, and build software together.
-
-
-
- {:else if $store === State.Form}
-
diff --git a/ui/Screen/IdentityCreation/Form.svelte b/ui/Screen/IdentityCreation/Form.svelte
deleted file mode 100644
index 1846ea8d19..0000000000
--- a/ui/Screen/IdentityCreation/Form.svelte
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
-
-
-
-
-
Create an identity
-
- An identity is required to interact on the radicle network. Multiple
- devices can be linked to a single identity.
-
-
-
-
-
-
-
-
diff --git a/ui/Screen/IdentityCreation/Success.svelte b/ui/Screen/IdentityCreation/Success.svelte
deleted file mode 100644
index 49bbdc20a5..0000000000
--- a/ui/Screen/IdentityCreation/Success.svelte
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-
-
Identity created ✨
-
- This is your peer-to-peer identity. Even though your radicleID is unique,
- your handle isn't. To get a unique handle, you have to
- dispatch('register')}>
- register it.
-
-
-
-
-
-
-
{identity.metadata.handle}
-
-
-
- {identity.shareableEntityIdentifier}
-
-
-
-
-
-
-
-
-
-
diff --git a/ui/Screen/Onboarding.svelte b/ui/Screen/Onboarding.svelte
new file mode 100644
index 0000000000..4e0cfa1200
--- /dev/null
+++ b/ui/Screen/Onboarding.svelte
@@ -0,0 +1,116 @@
+
+
+
+
+
+ {#if state === State.Welcome}
+
+
+ {
+ state = State.EnterName;
+ }} />
+
+
+ {:else if state === State.EnterName}
+
+
+ {
+ animateForward();
+ handle = event.detail;
+ state = State.EnterPassphrase;
+ }} />
+
+
+ {:else if state === State.EnterPassphrase}
+
+
+ {
+ animateBackward();
+ state = State.EnterName;
+ }}
+ on:next={event => {
+ onCreateIdentity(handle, event.detail);
+ }} />
+
+
+ {:else if state === State.SuccessView}
+
+ {/if}
+
diff --git a/ui/Screen/Onboarding/EnterName.svelte b/ui/Screen/Onboarding/EnterName.svelte
new file mode 100644
index 0000000000..dfd0d01be8
--- /dev/null
+++ b/ui/Screen/Onboarding/EnterName.svelte
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+ Hey
+
+ what should we call you?
+
+
+ You’ll need a name to interact on Radicle. This isn’t unique across the
+ platform, but it helps others recognize you a little easier. You can
+ change it in your profile at any time.
+
+
+
+
+
+
+
diff --git a/ui/Screen/Onboarding/EnterPassphrase.svelte b/ui/Screen/Onboarding/EnterPassphrase.svelte
new file mode 100644
index 0000000000..fbde4f7ace
--- /dev/null
+++ b/ui/Screen/Onboarding/EnterPassphrase.svelte
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
Next, you'll need a passphrase.
+
+
+ This is used to protect your account on this computer. Think of it like a
+ computer’s password. You can’t recover your account with it, but it
+ prevents someone from accessing your account if this computer is stolen or
+ hacked.
+
+
+
{
+ repeatedPassphraseInput.focus();
+ }}
+ autofocus={true}
+ dataCy="passphrase-input"
+ placeholder="Enter a secure passphrase"
+ style="margin-top: 1.5rem;"
+ validation={passphraseValidation}
+ bind:value={passphrase} />
+
+
+
And enter it again, just to be safe.
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/Screen/Onboarding/Success.svelte b/ui/Screen/Onboarding/Success.svelte
new file mode 100644
index 0000000000..de0a4a11fe
--- /dev/null
+++ b/ui/Screen/Onboarding/Success.svelte
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
All set!
+
+
+
+
+ This is your Radicle ID. Click to copy
+ it and share it with others so that they can find you.
+
+
+
+
+
diff --git a/ui/Screen/Onboarding/Welcome.svelte b/ui/Screen/Onboarding/Welcome.svelte
new file mode 100644
index 0000000000..4560465e95
--- /dev/null
+++ b/ui/Screen/Onboarding/Welcome.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
A free and open-source way to host, share, and build software together.
+
+
+
diff --git a/ui/Screen/Profile.svelte b/ui/Screen/Profile.svelte
index c7ab985f07..0cd16a6b80 100644
--- a/ui/Screen/Profile.svelte
+++ b/ui/Screen/Profile.svelte
@@ -50,7 +50,6 @@
-
diff --git a/ui/Screen/Project/Commit.svelte b/ui/Screen/Project/Commit.svelte
index 273fcc4c25..03d83e2a2c 100644
--- a/ui/Screen/Project/Commit.svelte
+++ b/ui/Screen/Project/Commit.svelte
@@ -127,28 +127,26 @@
-
+
{commit.header.summary}
-
+
{commit.header.description}
- Authored by
-
- {commit.header.author.name}
-
+ Authored by {commit.header.author.name}
<{commit.header.author.email}>
{#if commit.header.committer.email != commit.header.author.email}
- Committed by
-
- {commit.header.committer.name}
-
+ Committed by {commit.header.committer.name}
<{commit.header.committer.email}>
@@ -158,8 +156,7 @@
- Commit
- {commit.header.sha1}
+ Commit {commit.header.sha1}
@@ -170,13 +167,9 @@
{#if commit.diff.modified.length > 0}
{commit.diff.modified.length} file(s) changed
-
- with
-
+ with
{commit.stats.additions} additions
-
- and
-
+ and
{commit.stats.deletions} deletions
{/if}
diff --git a/ui/Screen/Project/Issue.svelte b/ui/Screen/Project/Issue.svelte
index e2041a8985..969510b5a9 100644
--- a/ui/Screen/Project/Issue.svelte
+++ b/ui/Screen/Project/Issue.svelte
@@ -159,8 +159,8 @@ Part of #277
{issue.open ? 'Open' : 'Closed'}
- {issue.open ? 'Opened' : 'Closed'} {issue.created_at} by
-
{issue.author.handle}
diff --git a/ui/Screen/Project/Source.svelte b/ui/Screen/Project/Source.svelte
index f39b60c522..581192ee5b 100644
--- a/ui/Screen/Project/Source.svelte
+++ b/ui/Screen/Project/Source.svelte
@@ -309,7 +309,6 @@
-
-
{#if object.info.objectType === ObjectType.Blob}
diff --git a/ui/Screen/ProjectRegistration.svelte b/ui/Screen/ProjectRegistration.svelte
index 9a2438d047..0a6624f7eb 100644
--- a/ui/Screen/ProjectRegistration.svelte
+++ b/ui/Screen/ProjectRegistration.svelte
@@ -90,7 +90,6 @@
-
{#if showRegistrationDetails === true}
Project registration
diff --git a/ui/Screen/Settings.svelte b/ui/Screen/Settings.svelte
index 11dc403163..c7c71aba36 100644
--- a/ui/Screen/Settings.svelte
+++ b/ui/Screen/Settings.svelte
@@ -200,6 +200,5 @@
-
diff --git a/ui/Screen/TransactionDetails.svelte b/ui/Screen/TransactionDetails.svelte
index 4d5e2ff0ed..1c81b810f4 100644
--- a/ui/Screen/TransactionDetails.svelte
+++ b/ui/Screen/TransactionDetails.svelte
@@ -30,6 +30,5 @@
-
diff --git a/ui/src/hotkeys.ts b/ui/src/hotkeys.ts
new file mode 100644
index 0000000000..e7d4a34c1b
--- /dev/null
+++ b/ui/src/hotkeys.ts
@@ -0,0 +1,15 @@
+import { get, writable } from "svelte/store";
+
+const state = writable(true);
+
+export const areEnabled = (): boolean => {
+ return get(state);
+};
+
+export const enable = (): void => {
+ state.set(true);
+};
+
+export const disable = (): void => {
+ state.set(false);
+};
diff --git a/ui/src/identity.ts b/ui/src/identity.ts
index 820b2fdc35..a71fa778de 100644
--- a/ui/src/identity.ts
+++ b/ui/src/identity.ts
@@ -30,41 +30,25 @@ export const identity = identityStore.readable;
// EVENTS
enum Kind {
- Create = "CREATE",
Fetch = "FETCH",
}
-interface Create extends event.Event
{
- kind: Kind.Create;
- handle: string;
-}
-
interface Fetch extends event.Event {
kind: Kind.Fetch;
urn: string;
}
-type Msg = Create | Fetch;
+type Msg = Fetch;
interface CreateInput {
handle: string;
+ passphrase: string;
}
export const fetch = event.create(Kind.Fetch, update);
function update(msg: Msg): void {
switch (msg.kind) {
- case Kind.Create:
- creationStore.loading();
- api
- .post("identities", {
- handle: msg.handle,
- })
- .then(id => {
- creationStore.success(id);
- })
- .catch(creationStore.error);
- break;
case Kind.Fetch:
identityStore.loading();
api
@@ -75,7 +59,9 @@ function update(msg: Msg): void {
}
}
-export const create = event.create(Kind.Create, update);
+export const createIdentity = (input: CreateInput): Promise => {
+ return api.post("identities", input);
+};
// MOCK
export const fallback = {
diff --git a/ui/src/onboard.ts b/ui/src/onboard.ts
deleted file mode 100644
index d2188e63e6..0000000000
--- a/ui/src/onboard.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { writable } from "svelte/store";
-
-export enum State {
- Welcome = "WELCOME",
- Form = "FORM",
- SuccessView = "SUCCESS_VIEW",
- Complete = "COMPLETE",
-}
-
-export const store = writable(State.Welcome);
diff --git a/ui/src/onboarding.ts b/ui/src/onboarding.ts
new file mode 100644
index 0000000000..41aab47a2d
--- /dev/null
+++ b/ui/src/onboarding.ts
@@ -0,0 +1,24 @@
+import * as validation from "./validation";
+
+export enum State {
+ Welcome = "WELCOME",
+ EnterName = "ENTER_NAME",
+ EnterPassphrase = "ENTER_PASSPHRASE",
+ SuccessView = "SUCCESS_VIEW",
+}
+
+const HANDLE_MATCH = "^[a-z0-9][a-z0-9_-]+$";
+
+const handleConstraints = {
+ presence: {
+ message: "You must provide a handle",
+ allowEmpty: false,
+ },
+ format: {
+ pattern: new RegExp(HANDLE_MATCH, "i"),
+ message: `Handle should match ${HANDLE_MATCH}`,
+ },
+};
+
+export const createHandleValidationStore = (): validation.ValidationStore =>
+ validation.createValidationStore(handleConstraints);
diff --git a/ui/src/path.ts b/ui/src/path.ts
index b20b465477..db27ce1dd5 100644
--- a/ui/src/path.ts
+++ b/ui/src/path.ts
@@ -2,6 +2,7 @@ import { parse, stringify, ParsedQs } from "qs";
import regexparam from "regexparam";
import { RevisionQuery } from "./source";
+export const blank = (): string => "/";
export const settings = (): string => "/settings";
export const discovery = (): string => "/discovery";
@@ -12,7 +13,7 @@ export const profileProjects = (): string => "/profile/projects";
export const profileTracking = (): string => "/profile/tracking";
export const profileWallet = (): string => "/profile/wallet";
export const registerUser = (): string => "/user-registration";
-export const createIdentity = (): string => "/identity/new";
+export const onboarding = (): string => "/onboarding";
export const userProfile = (urn: string): string => `/user/${urn}`;
export const userProfileProjects = (urn: string): string =>
diff --git a/ui/src/urn.ts b/ui/src/urn.ts
new file mode 100644
index 0000000000..b77c1e3593
--- /dev/null
+++ b/ui/src/urn.ts
@@ -0,0 +1,3 @@
+export const shorten = (stringWithUrn: string): string => {
+ return stringWithUrn.replace(/(rad:git:[\w]{3})[\w]{53}([\w]{3})/, "$1…$2");
+};
diff --git a/yarn.lock b/yarn.lock
index 20c42c924c..165a1e7d9e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6567,12 +6567,7 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-prettier-plugin-svelte@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.1.0.tgz#e6ec282d8457598b0c02164083b11ad8cb8ab304"
- integrity sha512-wmIggG/ryV0wcmE9D5p+k5TwKDpS2SGKJpF6IV1aYHK7dkBJD+di1w47Ci00DRsI4RrXZRC2Ef37DSyrTb6Zqg==
-
-prettier-plugin-svelte@~1.2.0:
+prettier-plugin-svelte@^1.2.0, prettier-plugin-svelte@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.2.0.tgz#64088163656470e7d5c83106aea804a1c2c71efb"
integrity sha512-D6Yh8WIk/8BDtJ6BN5vFMylCcdbiQbB2px5UPIiBCp1MbEqpEbVfWyOJPKnVc0xkvlr2CoCRXJcNbHtnh0JM9w==