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} + +
+

{validation.message}

+
+ {/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 @@
- @@ -553,14 +549,12 @@ -
- @@ -644,6 +638,23 @@ + + + + + + + + + + + + How about a checkbox? @@ -672,7 +683,6 @@
- -
@@ -977,7 +986,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} -
store.set(State.SuccessView)} /> - {:else if $store === State.SuccessView} - - {/if} - 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 @@ + + + + + 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} /> + + + +
+ + + +
+
+
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==