Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

Commit

Permalink
feat(ui): project registration flow (#292)
Browse files Browse the repository at this point in the history
* Set up routes for new project registration form
* Match StepCounter colors to the new style
* Implement project registration wizard
* Improve inner element hover state rounded corners
* Add elevation CSS variables
* Add styled dropdown
* Pre-select project and org dropdowns
* Make avatar text style customisable
* Remove unused Svelte store
* Fix project list context menu copy-to-clipboard
* Fix project list context menu button hover background color
* Fix context menu stacking order
* Add validation
* Use <Remote> component for data fetching
* Make sure a project is selected before unlocking button
* Don't show validations if user hasn't touched the form
* Show chosen wallet and transaction subject
* Implement "back" button
* Use NavigationButtons component for project registration
* Extract menu items out of primitive components
* Integrate project name availability check
* Implement profile avatar "registered" badge
* Require user handle registration before project registration
* Add error handling for handle validation
  - don't allow uppercase letters via front-end validations
  - show proxy errors as notifications

Co-authored-by: Alexander Simmerl <a.simmerl@gmail.com>
  • Loading branch information
rudolfs and xla committed May 4, 2020
1 parent 9e62e30 commit b5d4046
Show file tree
Hide file tree
Showing 43 changed files with 809 additions and 412 deletions.
15 changes: 3 additions & 12 deletions cypress/integration/project_creation.spec.js
Expand Up @@ -11,21 +11,15 @@ context("project creation", () => {

it("can be opened via the profile context menu and closed by pressing cancel", () => {
cy.get('[data-cy="profile-context-menu"]').click();
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click({
// TODO(rudolfs): remove this once #246 is fixed
force: true
});
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click();
cy.get('[data-cy="page"] [data-cy="create-project"]').should("exist");
cy.get('[data-cy="create-project"] [data-cy="cancel-button"]').click();
cy.get('[data-cy="profile-screen"]').should("exist");
});

it("can be closed by pressing escape key", () => {
cy.get('[data-cy="profile-context-menu"]').click();
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click({
// TODO(rudolfs): remove this once #246 is fixed
force: true
});
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click();
cy.get('[data-cy="page"] [data-cy="create-project"]').should("exist");
cy.get("body").type("{esc}");
cy.get('[data-cy="profile-screen"]').should("exist");
Expand All @@ -35,10 +29,7 @@ context("project creation", () => {
context("validations", () => {
beforeEach(() => {
cy.get('[data-cy="profile-context-menu"]').click();
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click({
// TODO(rudolfs): remove this once #246 is fixed
force: true
});
cy.get('[data-cy="dropdown-menu"] [data-cy="new-project"]').click();

// Set up minimal form input to show validations
cy.get('[data-cy="page"] [data-cy="name"]').type("this-name-is-valid");
Expand Down
20 changes: 7 additions & 13 deletions cypress/integration/user_registration.spec.js
Expand Up @@ -4,10 +4,7 @@ context("user registration", () => {
cy.createIdentity();
cy.visit("./public/index.html");
cy.get('[data-cy="profile-context-menu"]').click();
cy.get('[data-cy="dropdown-menu"] [data-cy="register-handle"]').click({
// TODO(rudolfs): remove this once #246 is fixed
force: true
});
cy.get('[data-cy="dropdown-menu"] [data-cy="register-handle"]').click();
});

// TODO(merle): Replace opening via hotkey
Expand Down Expand Up @@ -48,10 +45,7 @@ context("user registration", () => {
cy.createIdentity();
cy.visit("./public/index.html");
cy.get('[data-cy="profile-context-menu"]').click();
cy.get('[data-cy="dropdown-menu"] [data-cy="register-handle"]').click({
// TODO(rudolfs): remove this once #246 is fixed
force: true
});
cy.get('[data-cy="dropdown-menu"] [data-cy="register-handle"]').click();
});

context("handle", () => {
Expand All @@ -64,35 +58,35 @@ context("user registration", () => {
// spaces are not allowed
cy.get('[data-cy="page"] [data-cy="handle"]').type("no spaces");
cy.get('[data-cy="page"]').contains(
"Handle should match [a-z0-9][a-z0-9_-]+"
"Handle should match ^[a-z0-9][a-z0-9_-]+$"
);

// special characters are disallowed
cy.get('[data-cy="page"] [data-cy="handle"]').clear();
cy.get('[data-cy="page"] [data-cy="handle"]').type("$bad");
cy.get('[data-cy="page"]').contains(
"Handle should match [a-z0-9][a-z0-9_-]+"
"Handle should match ^[a-z0-9][a-z0-9_-]+$"
);

// can't start with an underscore
cy.get('[data-cy="page"] [data-cy="handle"]').clear();
cy.get('[data-cy="page"] [data-cy="handle"]').type("_nein");
cy.get('[data-cy="page"]').contains(
"Handle should match [a-z0-9][a-z0-9_-]+"
"Handle should match ^[a-z0-9][a-z0-9_-]+$"
);

// can't start with a dash
cy.get('[data-cy="page"] [data-cy="handle"]').clear();
cy.get('[data-cy="page"] [data-cy="handle"]').type("-nope");
cy.get('[data-cy="page"]').contains(
"Handle should match [a-z0-9][a-z0-9_-]+"
"Handle should match ^[a-z0-9][a-z0-9_-]+$"
);

// has to be at least two characters long
cy.get('[data-cy="page"] [data-cy="handle"]').clear();
cy.get('[data-cy="page"] [data-cy="handle"]').type("x");
cy.get('[data-cy="page"]').contains(
"Handle should match [a-z0-9][a-z0-9_-]+"
"Handle should match ^[a-z0-9][a-z0-9_-]+$"
);
});

Expand Down
3 changes: 2 additions & 1 deletion proxy/src/identity.rs
Expand Up @@ -3,6 +3,7 @@
use crate::avatar;
use crate::error;
use crate::registry;
use std::convert::TryFrom;

/// The users personal identifying metadata and keys.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -66,7 +67,7 @@ pub fn get(id: &str) -> Result<Option<Identity>, error::Error> {
display_name: Some("Alexis Sellier".into()),
avatar_url: Some("https://avatars1.githubusercontent.com/u/40774".into()),
},
registered: None,
registered: registry::Id::try_from("cloudhead").ok(),
avatar_fallback: avatar::Avatar::from(id, avatar::Usage::Identity),
}))
}
10 changes: 10 additions & 0 deletions public/elevation.css
@@ -0,0 +1,10 @@
:root {
--elevation-low: 0px 2px 4px rgba(0, 0, 0, 0.12),
0px 0px 1px rgba(0, 0, 0, 0.12);

--elevation-medium: 0px 4px 8px rgba(0, 0, 0, 0.12),
0px 0px 1px rgba(0, 0, 0, 0.12);

--elevation-high: 0px 8px 16px rgba(0, 0, 0, 0.12),
0px 0px 1px rgba(0, 0, 0, 0.12);
}
1 change: 0 additions & 1 deletion public/global.css
Expand Up @@ -21,5 +21,4 @@ a {
--sidebar-width: 68px;
--topbar-height: 61px;
--content-min-width: 700px;
--color-foreground-level-3-opacity-08: rgba(77, 82, 86, 0.08);
}
1 change: 1 addition & 0 deletions public/index.html
Expand Up @@ -83,6 +83,7 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="reset.css" />
<link rel="stylesheet" href="colors.css" />
<link rel="stylesheet" href="elevation.css" />
<link rel="stylesheet" href="global.css" />
<link rel="stylesheet" href="bundle.css" />

Expand Down
5 changes: 3 additions & 2 deletions ui/App.svelte
Expand Up @@ -16,8 +16,8 @@
import Org from "./Screen/Org.svelte";
import Profile from "./Screen/Profile.svelte";
import Project from "./Screen/Project.svelte";
import ProjectRegistration from "./Screen/ProjectRegistration.svelte";
import OrgRegistration from "./Screen/OrgRegistration.svelte";
import RegisterProject from "./Screen/RegisterProject.svelte";
import UserRegistration from "./Screen/UserRegistration.svelte";
import Search from "./Screen/Search.svelte";
import TransactionDetails from "./Screen/TransactionDetails.svelte";
Expand Down Expand Up @@ -60,7 +60,8 @@
"/orgs/:id": Org,
"/orgs/:id/*": Org,
"/projects/new": CreateProject,
"/projects/:id/register": RegisterProject,
"/projects/register/:registrarId": ProjectRegistration,
"/projects/:projectId/register/:registrarId": ProjectRegistration,
"/projects/:id/*": Project,
"/design-system-guide": DesignSystemGuide,
"/help": Help,
Expand Down
24 changes: 14 additions & 10 deletions ui/DesignSystem/Component/AdditionalActionsDropdown.svelte
Expand Up @@ -56,6 +56,7 @@
.additional-actions-dropdown-button :global(svg) {
fill: var(--color-foreground-level-6);
}
.additional-actions-dropdown-button:active :global(svg) {
fill: var(--color-foreground-level-5);
}
Expand All @@ -71,10 +72,13 @@
width: 240px;
margin-top: 15px;
background-color: var(--color-background);
box-shadow: 0px 4px 8px var(--color-foreground-level-3-opacity-08);
box-shadow: var(--elevation-medium);
border-radius: 4px;
cursor: pointer;
border: 1px solid var(--color-foreground-level-3);
overflow: hidden; /* hack to make inner option rounded corners */
z-index: 1;
user-select: none;
}
.header {
Expand All @@ -99,18 +103,16 @@
color: var(--color-foreground-level-6);
}
.menu-item:first-of-type {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
.menu-item:hover {
background-color: var(--color-foreground-level-1);
}
.menu-item:last-of-type {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
.menu-item.disabled {
color: var(--color-foreground-level-4);
}
.menu-item:hover {
background-color: var(--color-foreground-level-1);
.menu-item.disabled :global(svg) {
fill: var(--color-foreground-level-4);
}
</style>

Expand Down Expand Up @@ -142,9 +144,11 @@
<div class="menu" data-cy="dropdown-menu">
{#each menuItems as item, index}
<div
title={item.tooltip}
data-cy={item.dataCy}
class="menu-item"
on:click|stopPropagation={handleItemSelection(item)}>
class:disabled={item.disabled}
on:click|stopPropagation={!item.disabled && handleItemSelection(item)}>
<svelte:component this={item.icon} style="margin-right: 12px" />
<Text>{item.title}</Text>
</div>
Expand Down
2 changes: 1 addition & 1 deletion ui/DesignSystem/Component/Copyable.svelte
Expand Up @@ -18,7 +18,7 @@
}
</style>

<div class="container" on:click={copy}>
<div class="container" on:click|stopPropagation={copy}>
<span bind:this={slotContent}>
<slot />
</span>
Expand Down
137 changes: 137 additions & 0 deletions ui/DesignSystem/Component/Dropdown.svelte
@@ -0,0 +1,137 @@
<script>
import { Icon, Text } from "../Primitive";
import Option from "./Dropdown/Option.svelte";
export let placeholder = null;
export let options = null;
export let style = null;
export let valid = true;
export let validationMessage = null;
export let validationPending = false;
let expanded = false;
// bind to this prop from the outside
export let value = null;
export let disabled = false;
const toggleMenu = () => {
if (disabled) {
return;
}
expanded = !expanded;
};
const hideMenu = () => {
expanded = false;
};
const optionSelectedHandler = event => {
value = event.detail.value;
toggleMenu();
};
const disabledColor = () => {
return disabled
? "var(--color-foreground-level-4)"
: "var(--color-foreground-level-6)";
};
$: optionByValue = options.find(option => option.value === value);
</script>

<style>
.dropdown {
position: relative;
cursor: pointer;
}
.dropdown > * {
width: 100%;
}
.button {
height: 40px;
border: 1px solid var(--color-foreground-level-3);
border-radius: 4px;
display: flex;
align-items: center;
user-select: none;
display: flex;
justify-content: space-between;
overflow: hidden; /* hack to make inner option corners rounded */
}
.button:hover {
box-shadow: 0px 0px 0px 1px var(--color-foreground-level-3);
background-color: var(--color-foreground-level-2);
color: var(--color-foreground);
}
.button[hidden] {
visibility: hidden;
}
.menu {
position: absolute;
top: 0px;
left: 0px;
box-shadow: var(--elevation-medium),
0px 0px 0px 1px var(--color-foreground-level-3);
border: 1px solid var(--color-foreground-level-3);
border-radius: 4px;
user-select: none;
background-color: var(--color-background);
overflow: hidden; /* hack to make inner option corners rounded */
z-index: 1;
}
.validation-row {
display: flex;
align-items: center;
margin-top: 12px;
margin-left: 12px;
}
.button.invalid {
box-shadow: 0 0 0 1px var(--color-negative);
border: 1px solid var(--color-negative);
}
</style>

<svelte:window on:click={hideMenu} />

<div class="dropdown" {style}>
<div
class="button"
class:invalid={!valid}
on:click|stopPropagation={toggleMenu}>
{#if value && optionByValue}
<Option {...optionByValue} {disabled} />
{:else}
<Text style={`margin-left: 12px; color: ${disabledColor()}`}>
{placeholder}
</Text>
{/if}
<Icon.Expand
style={`flex-shrink: 0; margin: 0 8px 0 8px; fill: ${disabledColor()}`} />
</div>

<div class="menu" hidden={!expanded}>
{#each options as option}
<Option
{...option}
on:selected={optionSelectedHandler}
selected={value === option.value} />
{/each}
</div>

{#if !validationPending && !valid && validationMessage}
<div class="validation-row">
<Text style="color: var(--color-negative); text-align: left;">
{validationMessage}
</Text>
</div>
{/if}
</div>

0 comments on commit b5d4046

Please sign in to comment.