Skip to content

Commit

Permalink
merging main
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Grato committed Jul 5, 2022
2 parents 85287f5 + 73e1816 commit dd9715e
Show file tree
Hide file tree
Showing 50 changed files with 824 additions and 415 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ Run `mix dash` and `mix help dash.<task_name>` for more info.
- SECRET_KEY_BASE - Base secret key. This should be a strong cryptographcially generated secret.
- BASIC_AUTH_USERNAME - Username for site-wide basic auth.
- BASIC_AUTH_PASSWORD - Password for site-wide basic auth.
- AUTH_PUBLIC_KEY - Public key for JWT auth provided by auth server. Used in authentication.
- AUTH_SERVER - Server used for login links. e.g. "auth.myhubs.net"
- FXA_SERVER - Firefox Accounts server used for account management links. e.g. "accounts.firefox.com"
36 changes: 35 additions & 1 deletion assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.8",
"react-router-dom": "^6.2.2"
"react-router-dom": "^6.2.2",
"react-toastify": "^9.0.5"
},
"devDependencies": {
"@babel/preset-env": "^7.16.11",
Expand Down
10 changes: 10 additions & 0 deletions assets/src/components/common/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import logOut from "./log-out.svg";
import external from "./external.svg";
import backButton from "./back-button.svg";
import copy from "./copy.svg";
import valid from "./valid.svg";
import invalid from "./invalid.svg";

import "./Icon.css";

Expand Down Expand Up @@ -49,3 +51,11 @@ export function IconBackButton() {
export function IconCopy() {
return <img alt="copy" className="icon" src={copy} />;
}

export function IconValid() {
return <img alt="valid" className="icon" src={valid} />;
}

export function IconInvalid() {
return <img alt="invalid" className="icon" src={invalid} />;
}
5 changes: 5 additions & 0 deletions assets/src/components/common/icons/invalid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/src/components/common/icons/valid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions assets/src/components/containers/HomeContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { useCreateHubMutation } from "../services/hubs";
import { selectAccount } from "../store/account";
import { featureIsEnabled, CREATE_HUBS } from "../utils/feature-flags";
import { HubBuilding } from "../display/HubBuilding";
import { CREATING } from "../utils/hub-constants";
import { STATUS_CREATING } from "../utils/hub-constants";

function HubLoading({ isBuildingHub }) {
return isBuildingHub ? (
<>
<Hub name="Untitled Hub" status={CREATING} />
<Hub name="Untitled Hub" status={STATUS_CREATING} />
<HubBuilding />
</>
) : (
Expand All @@ -32,7 +32,7 @@ export function HomeContainer() {

return (
<div>
{isLoading && <HubLoading isBuildingHub={!account.hasHubs} />}
{isLoading ? <HubLoading isBuildingHub={!account.hasHubs || account.hasCreatingHubs} /> : ""}
{isError && <span>Unable to load Hubs</span>}
{isReady &&
(!hasHubs ? <span>You don&apos;t have any hubs</span> : hubs.map((hub) => <Hub key={hub.hubId} {...hub} />))}
Expand Down
8 changes: 2 additions & 6 deletions assets/src/components/display/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Link } from "react-router-dom";
import { useSelector } from "react-redux";

import "./Header.css";
import { FXA_SERVER } from "../utils/app-config";
import { IconLogOut, IconExternal } from "../common/icons";
import logoBlack from "../../images/logo-black.svg";
import { selectAccount } from "../store/account";
Expand Down Expand Up @@ -43,12 +44,7 @@ export function Header() {
<img alt="profile picture" src={account.profilePicture} />
<span className="account-email">{account.email}</span>
{/* TODO Pull domain from some configuration */}
<a
className="account-manage"
href="https://accounts.stage.mozaws.net/settings"
target="_blank"
rel="noreferrer"
>
<a className="account-manage" href={`https://${FXA_SERVER}/settings`} target="_blank" rel="noreferrer">
Manage your Firefox Account
<IconExternal />
</a>
Expand Down
7 changes: 4 additions & 3 deletions assets/src/components/display/Hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Spinner } from "../common/Spinner";
import { IconDrive, IconUsers, IconExternal } from "../common/icons";
import { CLUSTER_DOMAIN } from "../utils/app-config";
import { formatNumber, formatMegabytes } from "../utils/formatNumber";
import { READY } from "../utils/hub-constants";
import { STATUS_READY, STATUS_CREATING, STATUS_UPDATING } from "../utils/hub-constants";

export function Hub({ tier, name, status, subdomain, currentCcu, currentStorageMb, hubId }) {
const ccu = `${formatNumber(currentCcu)}`;
Expand All @@ -23,7 +23,7 @@ export function Hub({ tier, name, status, subdomain, currentCcu, currentStorageM
<span className="name">{name}</span>
</div>

{status === READY ? (
{status === STATUS_READY ? (
<>
<div>
<a className="domain" href={hubUrl} target="_blank" rel="noreferrer">
Expand All @@ -49,7 +49,8 @@ export function Hub({ tier, name, status, subdomain, currentCcu, currentStorageM
<div>
<span className="domain">
<Spinner isInline />
Building your new hub...
{status === STATUS_CREATING ? "Building your new hub..." : ""}
{status === STATUS_UPDATING ? "Updating your hub..." : ""}
</span>
</div>

Expand Down
113 changes: 90 additions & 23 deletions assets/src/components/display/HubForm.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import PropTypes from "prop-types";

import "./HubForm.css";
import { CLUSTER_DOMAIN } from "../utils/app-config";
import { LinkButton } from "../common/LinkButton";
import { IconDrive, IconUsers } from "../common/icons";
import { IconDrive, IconUsers, IconValid, IconInvalid } from "../common/icons";
import { formatMegabytes } from "../utils/formatNumber";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

function HubNickname({ hub, setHub }) {
const [nameValidity, setNameValidity] = useState({ valid: true });
Expand Down Expand Up @@ -37,20 +39,98 @@ HubNickname.propTypes = {
setHub: PropTypes.func,
};

function HubWebAddress({ hub, setHub }) {
const [subdomainValidity, setSubdomainValidity] = useState({ valid: true });

let validationMessage = "";
if (hub.subdomain.length < 3) {
validationMessage = "Must be at least 3 characters";
} else if (hub.subdomain.startsWith("-") || hub.subdomain.endsWith("-")) {
validationMessage = "Cannot start or end with a hyphen (-)";
} else if (/[^a-zA-Z0-9-]+/.test(hub.subdomain)) {
validationMessage = "Only supports letters (a to z), digits (0 to 9), and hyphens (-)";
}

return (
<div className="web-address">
<div className="web-address-header">
<span className="form-section-title">Web Address (URL)</span>
{subdomainValidity.valid ? (
<span className="form-section-subtitle">
Supports letters (a to z), digits (0 to 9), and hyphens&nbsp;(-)
</span>
) : (
<span className="form-section-subtitle invalid">{validationMessage}</span>
)}
</div>
<div className="web-address-input">
<input
type="text"
value={hub.subdomain}
required
pattern="[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]"
maxLength="63"
onChange={(e) => {
setSubdomainValidity(e.target.validity);
setHub({ ...hub, subdomain: e.target.value });
}}
/>
{subdomainValidity.valid ? <IconValid /> : <IconInvalid />}
<span className="domain">
<span className="subdomain">{hub.subdomain.toLowerCase()}</span>.{CLUSTER_DOMAIN}
</span>
</div>
</div>
);
}
HubWebAddress.propTypes = {
hub: PropTypes.object,
setHub: PropTypes.func,
};

export function HubForm({ hub, setHub, isSubmitting, onSubmit }) {
const onFormSubmit = (e) => {
e.preventDefault();
onSubmit(hub);
};
const [isValid, setIsValid] = useState(false);
const formRef = useRef();

const tierChoices = [
{ tier: "free", disabled: true, ccuLimit: 5, storageLimitMb: 250 },
{ tier: "mvp", disabled: false, ccuLimit: null, storageLimitMb: null },
];

const toastConfig = {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: "colored",
};

const onFormSubmit = (e) => {
e.preventDefault();
onSubmit(hub).then((resp) => {
const errorMessage = "There was an error updating your hub";
const successMessage = "Hub has been updated";

resp.error ? toast.error(errorMessage, toastConfig) : toast.success(successMessage, toastConfig);
});
};

const submitEnabled = !isSubmitting && isValid;

return (
<div className="hub-form-container">
<form className="hub-form" onSubmit={onFormSubmit}>
<form
className="hub-form"
ref={formRef}
onChange={() => {
if (formRef.current) {
setIsValid(formRef.current.checkValidity());
}
}}
onSubmit={onFormSubmit}
>
<HubNickname hub={hub} setHub={setHub} />

<div>
Expand Down Expand Up @@ -85,27 +165,13 @@ export function HubForm({ hub, setHub, isSubmitting, onSubmit }) {
))}
</div>

<div className="web-address">
<div className="web-address-header">
<span className="form-section-title">Web Address (URL)</span>
<span className="form-section-subtitle">
Supports letters (a to z), digits (0 to 9), and hyphens&nbsp;(-)
</span>
</div>
<div className="web-address-input">
<input type="text" value={hub.subdomain} onChange={(e) => setHub({ ...hub, subdomain: e.target.value })} />
&nbsp;
<span className="domain">
<span className="subdomain">{hub.subdomain}</span>.{CLUSTER_DOMAIN}
</span>
</div>
</div>
<HubWebAddress hub={hub} setHub={setHub} />

<hr />

<div className="form-buttons">
<LinkButton to={`/`} text="Back" />
<button className="primary" disabled={isSubmitting}>
<button className="primary" disabled={!submitEnabled}>
{isSubmitting ? "Updating" : "Update"}
</button>
</div>
Expand All @@ -120,6 +186,7 @@ export function HubForm({ hub, setHub, isSubmitting, onSubmit }) {
<span>Capacity</span>
<span className="hub-form-summary-value">-</span>
</div>
<ToastContainer />
</div>
);
}
Expand Down
9 changes: 4 additions & 5 deletions assets/src/components/display/Landing.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from "react";

import "./Landing.css";
import { AUTH_SERVER } from "../utils/app-config";
import { LinkButton } from "../common/LinkButton";
import { Avatar } from "../common/Avatar";
import logoWhite from "../../images/logo-white.svg";

export function Landing() {
const client = location.origin + location.pathname.replace(/\/$/, "");
// TODO Use the CLUSTER_DOMAIN app config value here, assuming the auth server on the dev cluster is functional.
const loginUrl = `https://auth.myhubs.net/login?idp=fxa&client=${client}`;
const loginUrl = `https://${AUTH_SERVER}/login?idp=fxa&client=${client}`;
return (
<div className="landing-container">
<Avatar />
Expand All @@ -26,9 +26,8 @@ export function Landing() {
</div>
</div>
<div className="footer">
{/* TODO Get the proper links here */}
<a href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md">Privacy policy</a>
<a href="https://github.com/mozilla/hubs/blob/master/TERMS.md">Terms and Conditions</a>
<a href="https://www.mozilla.org/privacy/hubs/">Privacy policy</a>
<a href="https://www.mozilla.org/about/legal/terms/hubs/">Terms and Conditions</a>
</div>
</div>
</div>
Expand Down
8 changes: 5 additions & 3 deletions assets/src/components/hooks/hubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ export function useHub(hubId) {

const [submitHub, { isLoading: isSubmitting }] = useUpdateHubMutation();

const updateHub = async (hub) => {
await submitHub(hub);
dispatch(setHubEntity(hub));
const updateHub = (hub) => {
return submitHub(hub).then((resp) => {
if (!resp.error) dispatch(setHubEntity(hub));
return resp;
});
};

const isReady = isSuccess || currentHub;
Expand Down
Loading

0 comments on commit dd9715e

Please sign in to comment.