Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to use scram implementation from edgedb-js + polish login page design #405

Merged
merged 3 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions shared/common/newui/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,11 @@ export function ModalPanel({
<div className={styles.subheading}>{subheading}</div>
) : null}
</div>
<div
className={styles.closeButton}
onClick={onClose && (() => onClose())}
>
<CrossIcon />
</div>
{onClose ? (
<div className={styles.closeButton} onClick={onClose}>
<CrossIcon />
</div>
) : null}
</div>
) : null}
{children}
Expand Down
1 change: 0 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"@fontsource-variable/roboto-flex": "^5.1.0",
"@fontsource-variable/roboto-mono": "^5.1.0",
"edgedb": "1.6.0-canary.20250120T111308",
"hash.js": "^1.1.7",
"mobx": "^6.5.0",
"mobx-keystone": "^1.11.0",
"mobx-react-lite": "^4.0.0",
Expand Down
112 changes: 73 additions & 39 deletions web/src/components/loginPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import {useEffect, useState} from "react";
import {useForm} from "react-hook-form";

import Button from "@edgedb/common/ui/button";
import {ModalTextField} from "@edgedb/common/ui/modal";
import {getHTTPSCRAMAuth} from "edgedb/dist/httpScram";
import {cryptoUtils} from "edgedb/dist/browserCrypto";

import {
ModalPanel,
ArrowRightIcon,
ModalContent,
TextInput,
SubmitButton,
InfoIcon,
} from "@edgedb/common/newui";

import {serverUrl, setAuthToken} from "../../state/models/app";
import {SCRAMAuth} from "../../utils/scram";
import {Logo} from "../header";

import styles from "./loginPage.module.scss";
import {ArrowRight} from "@edgedb/studio/icons";
import {Logo} from "../header";

const httpSCRAMAuth = getHTTPSCRAMAuth(cryptoUtils);

const isLocalhost = (() => {
try {
return new URL(serverUrl).hostname === "localhost";
} catch {
// ignore
}
return false;
})();

export default function LoginPage() {
const [error, setError] = useState<Error | null>(null);
const [error, setError] = useState<string | null>(null);
const {register, handleSubmit, formState, setFocus} = useForm<{
username: string;
password: string;
Expand All @@ -29,47 +47,63 @@ export default function LoginPage() {
const onSubmit = handleSubmit(async ({username, password}) => {
setError(null);
try {
const authToken = await SCRAMAuth(serverUrl, username, password);
const authToken = await httpSCRAMAuth(serverUrl, username, password);
setAuthToken(username, authToken);
} catch (err) {
setError(err as Error);
console.error(err);
setError("Login failed: username or password may be incorrect");
}
});

return (
<div className={styles.loginPage}>
<Logo className={styles.title} />
<form className={styles.loginForm} onSubmit={onSubmit}>
<ModalTextField
label="Username"
{...register("username", {
required: "Username is required",
})}
error={formState.errors.username?.message}
/>
<ModalTextField
label="Password"
type="password"
{...register("password", {
required: "Password is required",
})}
error={formState.errors.password?.message}
/>
<Button
className={styles.loginButton}
loading={formState.isSubmitting}
disabled={!formState.isValid || formState.isSubmitting}
label={"Login"}
icon={<ArrowRight className={styles.loginButtonIcon} />}
onClick={onSubmit}
/>
{error ? (
<div className={styles.loginError}>
<span>{error.name}:</span> {error.message}
</div>
) : null}
</form>
<Logo className={styles.logo} />

<ModalPanel
className={styles.loginPanel}
title="Welcome to Gel UI"
onSubmit={onSubmit}
formError={error}
footerButtons={
<SubmitButton
kind="primary"
loading={formState.isSubmitting}
disabled={!formState.isValid || formState.isSubmitting}
rightIcon={<ArrowRightIcon />}
>
Login
</SubmitButton>
}
>
<ModalContent className={styles.formContent}>
{isLocalhost ? (
<div className={styles.loginInfo}>
<InfoIcon />
<div>
<span>It looks like you're running Gel locally.</span>
<br /> If you created this instance using the Gel CLI, the
easiest way to login is by running the <code>gel ui</code>{" "}
command from your project directory.
</div>
</div>
) : null}
<TextInput
label="Username"
{...register("username", {
required: "Username is required",
})}
error={formState.errors.username?.message}
/>
<TextInput
label="Password"
type="password"
{...register("password", {
required: "Password is required",
})}
error={formState.errors.password?.message}
/>
</ModalContent>
</ModalPanel>
</div>
);
}
92 changes: 56 additions & 36 deletions web/src/components/loginPage/loginPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,77 @@
flex-grow: 1;
justify-content: center;
align-items: center;
padding: 32px;
}

.title {
.logo {
position: absolute;
top: 16px;
left: 24px;
}

.loginForm {
position: relative;
display: flex;
flex-direction: column;
width: 280px;
.loginPanel {
border-color: var(--Grey85);
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.12),
0px 2px 2px 0px rgba(0, 0, 0, 0.04);

@include darkTheme {
input {
background: #282828;
}
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.4),
0px 2px 4px 0px rgba(0, 0, 0, 0.2);
}
}

.loginButton {
--buttonBg: var(--app-accent-green);
--buttonTextColour: #fff;
align-self: flex-end;
padding: 0;
.formContent {
width: 380px;
padding-bottom: 32px;
}

& > div {
padding: 6px 16px;
border-radius: 18px;
font-weight: 600;
}
.loginInfo {
display: flex;
background: #dce9ef;
padding: 12px 16px 12px 12px;
margin: 0 -8px;
margin-bottom: 8px;
border-radius: 8px;
gap: 8px;
line-height: 20px;
font-weight: 425;
color: var(--secondary_text_color);

.loginButtonIcon {
stroke: currentColor;
height: 14px;
}
svg {
flex-shrink: 0;
color: #1a6e9a;
}
}

.loginError {
color: #db5246;
background: rgba(219, 82, 70, 0.1);
padding: 12px 16px;
border-radius: 6px;
position: absolute;
top: 100%;
margin-top: 16px;
left: 0;
right: 0;

span {
font-weight: 600;
color: #134c6b;
font-weight: 475;
line-height: 24px;
}

code {
background: rgba(0, 0, 0, 0.05);
font-family: "Roboto Mono Variable", monospace;
padding: 2px 4px;
border-radius: 6px;
font-weight: 500;
font-size: 13px;
white-space: nowrap;
}

@include darkTheme {
background: #233945;

svg {
color: #65a8cb;
}

span {
color: #83bbd8;
}

code {
background: rgba(255, 255, 255, 0.1);
}
}
}
Loading