Skip to content

Commit

Permalink
feat: use new design and add user flows as dummy pages
Browse files Browse the repository at this point in the history
Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd committed Jan 29, 2024
1 parent 18e2ab8 commit ecc0dee
Show file tree
Hide file tree
Showing 37 changed files with 2,416 additions and 1,190 deletions.
2 changes: 1 addition & 1 deletion rockcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: identity-platform-login-ui

base: bare
build-base: ubuntu:22.04
build-base: ubuntu@22.04
version: '0.11.3' # x-release-please-version
summary: Canonical Identity platform login UI
description: |
Expand Down
2 changes: 1 addition & 1 deletion ui/components/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class Flow<T extends Values> extends Component<Props<T>, State<T>> {
[getNodeId(node)]: value,
},
}),
resolve
resolve,
);
})
}
Expand Down
10 changes: 0 additions & 10 deletions ui/components/Logo.tsx

This file was deleted.

68 changes: 56 additions & 12 deletions ui/components/NodeInputSubmit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,59 @@ export const NodeInputSubmit: FC<NodeInputProps> = ({
setValue,
disabled,
dispatchSubmit,
}) => (
<Button
onClick={async (e) => {
// On click, we set this value, and once set, dispatch the submission!
await setValue(attributes.value as string).then(() => dispatchSubmit(e));
}}
disabled={attributes.disabled || disabled}
className="login-button"
>
{getNodeLabel(node)}
</Button>
);
}) => {
const getProviderImage = (value: string) => {
if (value.toLowerCase().startsWith("auth0")) {
return "logos/Auth0.svg";
}
if (value.toLowerCase().startsWith("github")) {
return "logos/Github.svg";
}
if (value.toLowerCase().startsWith("google")) {
return "logos/Google.svg";
}
if (value.toLowerCase().startsWith("microsoft")) {
return "logos/Microsoft.svg";
}
if (value.toLowerCase().startsWith("okta")) {
return "logos/Okta.svg";
}
if (value.toLowerCase().startsWith("ping")) {
return "logos/Ping.svg";
}
return "";
};

const label = getNodeLabel(node);
const isProvider = attributes.name === "provider";
const provider = attributes.value as string;
const image = getProviderImage(provider);

return (
<Button
onClick={async (e) => {
// On click, we set this value, and once set, dispatch the submission!
await setValue(attributes.value as string).then(() =>
dispatchSubmit(e),
);
}}
disabled={attributes.disabled || disabled}
className="login-button"
>
{isProvider ? (
<>
{image && (
<img
src={image}
alt={`${provider} logo`}
style={{ marginRight: "0.5rem" }}
/>
)}
<span>Sign in with {label}</span>
</>
) : (
label
)}
</Button>
);
};
2 changes: 1 addition & 1 deletion ui/components/NodeText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const Content: FC<Props> = ({ attributes }) => {
{/* Used lookup_secret has ID 1050014 */}
<code>{text.id === 1050014 ? "Used" : text.text}</code>
</div>
)
),
);
return (
<div
Expand Down
42 changes: 42 additions & 0 deletions ui/components/PageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Card, Col, Navigation, Row, Theme } from "@canonical/react-components";
import React, { FC, ReactNode, useLayoutEffect } from "react";
import Head from "next/head";

interface Props {
children?: ReactNode;
title: string;
}

const PageLayout: FC<Props> = ({ children, title }) => {
useLayoutEffect(() => {
document.querySelector("body")?.classList.add("is-paper");
});

return (
<>
<Head>
<title>{title}</title>
</Head>
<Row className="p-strip page-row">
<Col emptyLarge={4} size={6}>
<Card className="u-no-padding page-card">
<Navigation
logo={{
src: "https://assets.ubuntu.com/v1/82818827-CoF_white.svg",
title: "Canonical",
url: `/`,
}}
theme={Theme.DARK}
/>
<div className="p-card__inner page-inner">
<h1 className="p-heading--4">{title}</h1>
<div>{children}</div>
</div>
</Card>
</Col>
</Row>
</>
);
};

export default PageLayout;
91 changes: 91 additions & 0 deletions ui/components/Password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { FC } from "react";
import { Input } from "@canonical/react-components";
import PasswordCheck from "./PasswordCheck";

export type PasswordCheckType = "lowercase" | "uppercase" | "number" | "length";

type Props = {
checks: PasswordCheckType[];
password: string;
setPassword: (password: string) => void;
isValid: boolean;
setValid: (isValid: boolean) => void;
label?: string;
};

const Password: FC<Props> = ({
checks,
password,
setPassword,
isValid,
setValid,
label = "Password",
}) => {
const [confirmation, setConfirmation] = React.useState("");
const [hasPassBlur, setPasswordBlurred] = React.useState(false);
const [hasConfirmBlur, setConfirmationBlurred] = React.useState(false);

const getStatus = (check: PasswordCheckType) => {
if (!hasPassBlur) {
return "neutral";
}

switch (check) {
case "lowercase":
return /[a-z]/.test(password) ? "success" : "error";
case "uppercase":
return /[A-Z]/.test(password) ? "success" : "error";
case "number":
return /[0-9]/.test(password) ? "success" : "error";
case "length":
return password.length >= 8 ? "success" : "error";
}
};

const isCheckFailed = checks.some((check) => getStatus(check) === "error");
const isMismatch = hasConfirmBlur && password !== confirmation;

const localValid = hasPassBlur && !isCheckFailed && password === confirmation;
if (isValid !== localValid) {
setValid(localValid);
}

return (
<>
<Input
id="password"
name="password"
type="password"
label={label}
placeholder="Your password"
onBlur={() => setPasswordBlurred(true)}
onChange={(e) => setPassword(e.target.value)}
value={password}
help={checks.length > 0 && "Password must contain"}
/>
{checks.map((check) => {
return (
<PasswordCheck key={check} check={check} status={getStatus(check)} />
);
})}
<Input
id="passwordConfirm"
name="passwordConfirm"
type="password"
label={`Confirm ${label}`}
placeholder="Your password"
onBlur={() => setConfirmationBlurred(true)}
onChange={(e) => setConfirmation(e.target.value)}
error={
isCheckFailed
? "Password does not match requirements"
: isMismatch
? "Passwords do not match"
: undefined
}
/>
</>
);
};

export default Password;
47 changes: 47 additions & 0 deletions ui/components/PasswordCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { FC } from "react";
import { Icon } from "@canonical/react-components";
import { PasswordCheckType } from "./Password";

type Props = {
check: PasswordCheckType;
status: "success" | "error" | "neutral";
};

const PasswordCheck: FC<Props> = ({ check, status }) => {
const getMessage = () => {
switch (check) {
case "lowercase":
return "Lowercase letters (a-z)";
case "uppercase":
return "Uppercase letters (A-Z)";
case "number":
return "Number (0-9)";
case "length":
return "Minimum 8 characters";
}
};

switch (status) {
case "success":
return (
<div className="p-form-validation is-success">
<p className="p-form-validation__message">{getMessage()}</p>
</div>
);
case "error":
return (
<div className="p-form-validation is-error">
<p className="p-form-validation__message">{getMessage()}</p>
</div>
);
case "neutral":
return (
<p className="p-text--small u-text--muted">
<Icon name="information" />
&nbsp;&nbsp;{getMessage()}
</p>
);
}
};

export default PasswordCheck;
4 changes: 2 additions & 2 deletions ui/components/helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { UiNode, UiNodeInputAttributes } from "@ory/client";
import { FormEvent } from "react";

export type ValueSetter = (
value: string | number | boolean | undefined
value: string | number | boolean | undefined,
) => Promise<void>;

export type FormDispatcher = (e: MouseEvent | FormEvent) => Promise<void>;

export interface NodeInputProps {
node: UiNode;
attributes: UiNodeInputAttributes;
value: any;
value: unknown;
disabled: boolean;
dispatchSubmit: FormDispatcher;
setValue: ValueSetter;
Expand Down
Loading

0 comments on commit ecc0dee

Please sign in to comment.