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

Commit

Permalink
feat: user handle registration modal (#216)
Browse files Browse the repository at this point in the history
* Set up register user modal
* Add temporary hotkey access to register user modal
* Use text input handle variant
* Query user handle availability
* Extend user mock endpoint for testing
* Add Avatar placeholder
* Add testing

Co-authored-by: Alexander Simmerl <a.simmerl@gmail.com>
  • Loading branch information
Merle Breitkreuz and xla committed Mar 18, 2020
1 parent 073db36 commit eade724
Show file tree
Hide file tree
Showing 13 changed files with 459 additions and 26 deletions.
100 changes: 100 additions & 0 deletions cypress/integration/user_registration.spec.js
@@ -0,0 +1,100 @@
context("user registration", () => {
context("modal navigation", () => {
beforeEach(() => {
cy.visit("./public/index.html#/projects");
cy.get("body").type("{shift}t");
});

// TODO(merle): Replace opening via hotkey

it("can be closed by pressing cancel", () => {
cy.get('[data-cy="page"] [data-cy="register-user"]').should("exist");
cy.get('[data-cy="register-user"] [data-cy="cancel-button"]').click();
cy.contains("My Projects").should("exist");
});

it("can be closed by pressing escape key", () => {
cy.get('[data-cy="page"] [data-cy="register-user"]').should("exist");
cy.get("body").type("{esc}");
cy.contains("My Projects").should("exist");
});

// navigation between pick handle (1) and submit tx (2) steps
it("moves through the views by pressing navigation buttons", () => {
// 1 -> 2
cy.get('[data-cy="register-user"] [data-cy="handle"]').should("exist");
cy.get('[data-cy="register-user"] [data-cy="next-button"]').click();
cy.get('[data-cy="register-user"] [data-cy="tx-summary"]').should(
"exist"
);
// 2 -> 1
cy.get('[data-cy="register-user"] [data-cy="back-button"]').click();
cy.get('[data-cy="register-user"] [data-cy="handle"]').should("exist");
// 1 -> 2
cy.get('[data-cy="register-user"] [data-cy="next-button"]').click();
cy.get('[data-cy="register-user"] [data-cy="tx-summary"]').should(
"exist"
);
// 2 -> close modal
cy.get('[data-cy="register-user"] [data-cy="submit-button"]').click();
cy.contains("My Projects").should("exist");
});
});

context("validations", () => {
beforeEach(() => {
cy.visit("./public/index.html#/user-registration");
});

context("handle", () => {
it("prevents the user from registering an invalid handle", () => {
// shows a validation message when handle is not present
cy.get('[data-cy="page"] [data-cy="handle"]').clear();
cy.get('[data-cy="page"]').contains("Handle is required");

// shows a validation message when handle contains invalid characters
// 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_-]+"
);

// 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_-]+"
);

// 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_-]+"
);

// 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_-]+"
);

// 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_-]+"
);
});

// TODO(merle): Add test setup, when mocks are replaced
it("prevents the user from registering an unavailable handle", () => {
// shows a validation message when handle is not available
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 already taken");
});
});
});
});
6 changes: 5 additions & 1 deletion proxy/src/graphql/schema.rs
Expand Up @@ -291,7 +291,11 @@ impl Query {
}

fn user(_ctx: &Context, handle: juniper::ID) -> Result<Option<juniper::ID>, error::Error> {
Ok(None)
if handle == juniper::ID::new("cloudhead") {
Ok(None)
} else {
Ok(Some(juniper::ID::new("1234")))
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions ui/App.svelte
Expand Up @@ -17,6 +17,7 @@
import Project from "./Screen/Project.svelte";
import Projects from "./Screen/Projects.svelte";
import RegisterProject from "./Screen/RegisterProject.svelte";
import UserRegistration from "./Screen/UserRegistration.svelte";
import Search from "./Screen/Search.svelte";
initializeHotkeys();
Expand Down Expand Up @@ -46,6 +47,7 @@
"/projects/:id/*": Project,
"/design-system-guide": DesignSystemGuide,
"/help": Help,
"/user-registration": UserRegistration,
"*": NotFound
};
</script>
Expand Down
23 changes: 16 additions & 7 deletions ui/DesignSystem/Component/Transaction.svelte
Expand Up @@ -8,12 +8,15 @@
// stake: "name of stake (optional)",
// subject: {
// name: "name of the transaction target",
// kind: "project" || "user"
// avatar: "avatar url of the target (optional)"
// kind: "project" || "user",
// avatarFallback: "avatar fallback of the target",
// imageUrl: "avatar url of the target (optional)"
// },
// payer: {
// name: "name of the owner of the paying wallet"
// avatar: "avatar of the owner (optional)"
// avatar: "avatar of the owner (optional)",
// avatarFallback: "avatar fallback of the owner",
// imageUrl: "avatar url of the owner (optional)"
// }
// }
export let tx = null;
Expand All @@ -24,16 +27,18 @@
<Caption style="color: var(--color-darkgray); margin-bottom: 16px">
Your transaction
</Caption>
<Row style="margin-bottom: 32px;">
<Row style="margin-bottom: 32px;" dataCy="tx-summary">
<div slot="left">
<Title>{tx.message}</Title>
</div>

<div slot="right">
<Avatar
title={tx.subject.name}
imageUrl={tx.subject.avatar}
variant={tx.subject.kind} />
imageUrl={tx.subject.imageUrl}
avatarFallback={tx.subject.avatarFallback}
variant={tx.subject.kind}
style="color: var(--color-black)" />
</div>
</Row>

Expand Down Expand Up @@ -81,7 +86,11 @@

<Row style="background-color: var(--color-almostwhite)">
<div slot="left">
<Avatar title={tx.payer.name} imageUrl={tx.payer.avatar} />
<Avatar
title={tx.payer.name}
imageUrl={tx.payer.imageUrl}
avatarFallback={tx.payer.avatarFallback}
style="color: var(--color-black)" />
</div>

<div slot="right">
Expand Down
3 changes: 2 additions & 1 deletion ui/DesignSystem/Component/Transaction/Row.svelte
Expand Up @@ -2,6 +2,7 @@
import { Flex } from "../../Primitive";
export let style = null;
export let dataCy = null;
export let variant = "single"; // top || middle || bottom
export let active = false;
Expand Down Expand Up @@ -59,7 +60,7 @@
}
</style>

<div class={rowClass} {style}>
<div class={rowClass} {style} data-cy={dataCy}>
<Flex>
<div slot="left">
<slot name="left" />
Expand Down
15 changes: 5 additions & 10 deletions ui/DesignSystem/Primitive/Avatar.svelte
Expand Up @@ -61,6 +61,7 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--color-darkgray);
}
.avatar {
Expand All @@ -86,19 +87,13 @@
</Title>
</div>
{:else}
<!-- TODO: Remove when all avatars use the new fallback data or add placeholder -->
<img
class={avatarClass}
src="https://avatars.dicebear.com/v2/avataaars/S7oswrhcNJkjzUhNW33S.svg"
alt="user-avatar" />
<div
class={`avatar ${avatarClass}`}
style="background: var(--color-lightgray)" />
{/if}

{#if title && size === 'regular'}
<Title
style="color: var(--color-darkgray); white-space: nowrap; margin-left:
16px">
{title}
</Title>
<Title style="white-space: nowrap; margin-left: 16px">{title}</Title>
{/if}

{#if title && size === 'big'}
Expand Down
10 changes: 6 additions & 4 deletions ui/DesignSystem/Primitive/Input/Text.svelte
Expand Up @@ -12,7 +12,8 @@
export let valid = true;
export let validationMessage = null;
export let variant = "vanilla"; // vanilla | handle
export let avatar = null;
export let imageUrl = null;
export let avatarFallback = null;
export let validationPending = false;
</script>

Expand Down Expand Up @@ -71,12 +72,13 @@
{disabled}
on:change
on:input />
{#if variant === 'handle' && value && avatar}
{#if variant === 'handle'}
<Avatar
avatarFallback={avatar}
{avatarFallback}
{imageUrl}
variant="user"
size="regular"
style="width: 32px; height: 48px; justify-content: flex-start; position:
style="width: 34px; height: 48px; justify-content: flex-start; position:
absolute; top: 0px; left: 10px" />
{/if}

Expand Down
7 changes: 4 additions & 3 deletions ui/Screen/DesignSystemGuide.svelte
Expand Up @@ -466,7 +466,8 @@

<Swatch>
<Input.Text
avatar={avatarFallback1}
avatarFallback={avatarFallback1}
imageUrl="https://avatars1.githubusercontent.com/u/40774"
placeholder="Enter user name"
style="width: 100%"
valid={true}
Expand All @@ -476,7 +477,7 @@

<Swatch>
<Input.Text
avatar={avatarFallback1}
avatarFallback={avatarFallback1}
placeholder="Enter user name"
style="width: 100%"
valid={true}
Expand All @@ -487,7 +488,7 @@

<Swatch>
<Input.Text
avatar={avatarFallback2}
avatarFallback={avatarFallback2}
placeholder="Enter user name."
style="width: 100%"
valid={false}
Expand Down

0 comments on commit eade724

Please sign in to comment.