diff --git a/background.svg b/background.svg new file mode 100644 index 00000000..b6d2a7e4 --- /dev/null +++ b/background.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/branding.svg b/branding.svg new file mode 100644 index 00000000..6299e205 --- /dev/null +++ b/branding.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/images.svg b/images.svg new file mode 100644 index 00000000..4f48d804 --- /dev/null +++ b/images.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index d031354c..3957240e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "react-dom": "18.2.0", "react-router-dom": "5.3.4", "react-table": "7.8.0", - "react-topbar-progress-indicator": "4.1.1", "tweetnacl": "1.0.3", "yup": "1.2.0" }, @@ -7899,17 +7898,6 @@ "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" } }, - "node_modules/react-topbar-progress-indicator": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-topbar-progress-indicator/-/react-topbar-progress-indicator-4.1.1.tgz", - "integrity": "sha512-Oy3ENNKfymt16zoz5SYy/WOepMurB0oeZEyvuHm8JZ3jrTCe1oAUD7fG6HhYt5sg8Wcg5gdkzSWItaFF6c6VhA==", - "dependencies": { - "topbar": "^0.1.3" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -8747,11 +8735,6 @@ "node": ">=8.0" } }, - "node_modules/topbar": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/topbar/-/topbar-0.1.4.tgz", - "integrity": "sha512-P3n4WnN4GFd2mQXDo30rQmsAGe4V1bVkggtTreSbNyL50Fyc+eVkW5oatSLeGQmJoan2TLIgoXUZypN+6nw4MQ==" - }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", diff --git a/package.json b/package.json index 7482d317..b5e03f82 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "react-dom": "18.2.0", "react-router-dom": "5.3.4", "react-table": "7.8.0", - "react-topbar-progress-indicator": "4.1.1", "tweetnacl": "1.0.3", "yup": "1.2.0" }, diff --git a/present.png b/present.png deleted file mode 100644 index 08e0aeea..00000000 Binary files a/present.png and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx index 7aaa5452..4ea76d98 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,9 +16,10 @@ import { AuthVerify } from "auth/views/AuthVerify"; import { Login } from "auth/views/Login"; import { MagicLink } from "auth/views/MagicLink"; import { Register } from "auth/registration/Register"; +import { LandingPage } from "auth/views/LandingPage"; const Protected = () => { - return ; + return ; }; export function App() { @@ -46,7 +47,11 @@ export function App() { - + + diff --git a/src/auth/lib/index.ts b/src/auth/lib/index.ts index da26e9b7..7de5d528 100644 --- a/src/auth/lib/index.ts +++ b/src/auth/lib/index.ts @@ -69,7 +69,7 @@ export async function submitRegistration(form: RegistrationForm) { ...form.address, }, user: { - fullName: `${form.firstName} ${form.lastName}`, + fullName: form.fullName, email: form.email, }, }), diff --git a/src/auth/registration/AddressField.tsx b/src/auth/registration/AddressField.tsx index cf018455..65e6b4e3 100644 --- a/src/auth/registration/AddressField.tsx +++ b/src/auth/registration/AddressField.tsx @@ -1,61 +1,62 @@ -import { Stack, Typography } from "@mui/material"; +import { Box, Divider, Stack, Typography } from "@mui/material"; import { FormikTextField } from "form/FormikHelpers"; import React from "react"; import { CountryPicker } from "components/Country/CountryPicker"; export function AddressField() { return ( - - - We are excited to have the opportunity to work with you! The last bit we - need is some address info for your organization. This will allow us to - set up the necessary financial arrangements. + + + Second (and last!) we'll need info on your business. Once you are + finished here, your account setup is complete. + + - - + - - + - + - + ); } diff --git a/src/auth/registration/AdvertiserField.tsx b/src/auth/registration/AdvertiserField.tsx deleted file mode 100644 index 6923b9d7..00000000 --- a/src/auth/registration/AdvertiserField.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useFormikContext } from "formik"; -import { RegistrationForm } from "auth/registration/types"; -import { Typography } from "@mui/material"; -import React from "react"; -import { FormikTextField } from "form/FormikHelpers"; - -export function AdvertiserField() { - const { values } = useFormikContext(); - - return ( - - - Welcome to Brave Ads - {values.firstName && ( - <> - {", "} - {values.firstName} - - )} - {"! "} - Now we need to know about your organization. Include your organization's - name and be as descriptive as possible. This will help us expedite the - review and approval procedures. - - - - - - - - - ); -} diff --git a/src/auth/registration/AdvertiserRegistered.tsx b/src/auth/registration/AdvertiserRegistered.tsx index 04c019fa..5e3c6bd5 100644 --- a/src/auth/registration/AdvertiserRegistered.tsx +++ b/src/auth/registration/AdvertiserRegistered.tsx @@ -12,14 +12,17 @@ export function AdvertiserRegistered({ error }: Props) { {!!error ? ( - - We encountered a problem creating your account. Please reach out to + + We encountered a problem creating your account. + + + Please reach out to selfserve@brave.com {" "} or try again. - + - Success! Our team will now carefully review the provided - information. + Thanks for your interest in Brave Ads! Our team will now carefully + review the provided information. Once the review process is complete, we will send you an email to - notify you of the approval status and any further steps required. We - look forward to progressing our partnership with you and will be in - touch soon. + notify you of the approval status and any further steps required. + + + In the meantime, check out our{" "} + + advertiser resources + + . If you have any questions, please reach out to{" "} + selfserve@brave.com. - )} diff --git a/src/auth/registration/NameField.tsx b/src/auth/registration/NameField.tsx index c4aadd2b..cc9208e5 100644 --- a/src/auth/registration/NameField.tsx +++ b/src/auth/registration/NameField.tsx @@ -1,41 +1,64 @@ -import { Stack, Typography } from "@mui/material"; +import { Box, Divider, Stack, Typography } from "@mui/material"; import { FormikTextField } from "form/FormikHelpers"; import React from "react"; export function NameField() { return ( - - - Thank you for choosing Brave's Ads Platform! To complete your - registration, we need some information in order to provide you with a - great advertising experience. First, we'll start with the basics. + + + Thank you for choosing Brave's Ads Platform! Let's get you setup with + your account. First, we'll need your info. - - + - - + + + + + + + - + ); } diff --git a/src/auth/registration/Register.tsx b/src/auth/registration/Register.tsx index 3c405917..cd3f8f66 100644 --- a/src/auth/registration/Register.tsx +++ b/src/auth/registration/Register.tsx @@ -1,18 +1,21 @@ import { AuthContainer } from "auth/views/components/AuthContainer"; import { Form, Formik } from "formik"; -import React from "react"; +import React, { useState } from "react"; import { RegistrationSchema } from "validation/RegistrationSchema"; import { initialValues, RegistrationForm } from "auth/registration/types"; -import { Stack } from "@mui/material"; import { NameField } from "auth/registration/NameField"; -import { AdvertiserField } from "auth/registration/AdvertiserField"; import { AddressField } from "auth/registration/AddressField"; import { FormikSubmitButton } from "form/FormikHelpers"; import { useRegister } from "auth/hooks/mutations/useRegister"; import { AdvertiserRegistered } from "auth/registration/AdvertiserRegistered"; -import { StepsButton } from "components/Steps/StepsButton"; +import { NextAndBack } from "components/Steps/NextAndBack"; +import { Box, Toolbar, Typography } from "@mui/material"; +import { Background } from "components/Background/Background"; +import { LandingPageAppBar } from "components/AppBar/LandingPageAppBar"; +import { PaddedCardContainer } from "components/Card/PaddedCardContainer"; export function Register() { + const [activeStep, setActiveStep] = useState(0); const { register, hasRegistered, error } = useRegister(); if (hasRegistered || error) { @@ -23,38 +26,50 @@ export function Register() { ); } + const steps = [ + { label: "Your information", component: }, + { label: "Your business's information", component: }, + ]; + return ( - - { - setSubmitting(true); - register(v); - setSubmitting(false); - }} - validationSchema={RegistrationSchema} - > -
- - }, - { - label: "Organization details", - component: , - }, - { label: "Organization address", component: }, - ]} - finalComponent={ - - } - /> - -
-
-
+ + + + + + + {steps[activeStep].label} + + { + setSubmitting(true); + register(v); + setSubmitting(false); + }} + validationSchema={RegistrationSchema} + > +
+ + {steps[activeStep].component} + + + setActiveStep(activeStep + 1)} + onBack={() => setActiveStep(activeStep - 1)} + final={ + + } + /> + +
+
+
+
); } diff --git a/src/auth/registration/types.ts b/src/auth/registration/types.ts index 136329c9..ec887b50 100644 --- a/src/auth/registration/types.ts +++ b/src/auth/registration/types.ts @@ -1,7 +1,6 @@ export type RegistrationForm = { email: string; - firstName: string; - lastName: string; + fullName: string; advertiser: { name: string; url: string; @@ -19,8 +18,7 @@ export type RegistrationForm = { export const initialValues: RegistrationForm = { email: "", - firstName: "", - lastName: "", + fullName: "", advertiser: { name: "", url: "", diff --git a/src/auth/views/AuthVerify.tsx b/src/auth/views/AuthVerify.tsx index 49a5e97e..0ee82933 100644 --- a/src/auth/views/AuthVerify.tsx +++ b/src/auth/views/AuthVerify.tsx @@ -41,7 +41,7 @@ export function AuthVerify() { variant="h5" component={RouterLink} sx={{ textAlign: "center" }} - to="/" + to="/user/main" replace > Not automatically redirected? Click this link to go to the diff --git a/src/auth/views/LandingPage.tsx b/src/auth/views/LandingPage.tsx new file mode 100644 index 00000000..f46a7311 --- /dev/null +++ b/src/auth/views/LandingPage.tsx @@ -0,0 +1,59 @@ +import { Background } from "components/Background/Background"; +import { LandingPageAppBar } from "components/AppBar/LandingPageAppBar"; +import React from "react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import goals from "../../../images.svg"; +import { useIsAuthenticated } from "auth/hooks/queries/useIsAuthenticated"; + +const GradientText = { + backgroundImage: + "linear-gradient(96.46deg, #FF2869 -4.13%, #930BFE 82.88%), linear-gradient(0deg, #111317, #111317);", + backgroundClip: "text", + color: "transparent", +}; + +export function LandingPage() { + const isAuthenticated = useIsAuthenticated(); + + return ( + + + + + + + Privacy-forward + {" "} + advertising made simple + + + + Reach and convert new customers through premium advertising on the + Brave browser and search engine. + + + + + + + + + + ); +} diff --git a/src/auth/views/Login.tsx b/src/auth/views/Login.tsx index d452536c..450892da 100644 --- a/src/auth/views/Login.tsx +++ b/src/auth/views/Login.tsx @@ -12,14 +12,14 @@ export function Login() { const { signIn, loading, error } = useSignIn({ onSuccess() { - history.replace("/"); + history.replace("/user/main"); }, }); return ( - - Sign into your Brave Ads account + + Log into your Brave Ads account - Sign In + Log in + + Enter your email address to get a secure login link. Use this link to + access your Brave Ads account. + + setEmail(e.target.value)} @@ -56,10 +61,6 @@ export function MagicLink() { error={!!error} helperText={error} /> - - Enter your email address to get a secure login link. Use this link to - access your Brave Ads account. - - + + + + {aboveCard} - - {children} + {belowCard} - + ); } diff --git a/src/components/AppBar/LandingPageAppBar.tsx b/src/components/AppBar/LandingPageAppBar.tsx new file mode 100644 index 00000000..b9446965 --- /dev/null +++ b/src/components/AppBar/LandingPageAppBar.tsx @@ -0,0 +1,107 @@ +import * as React from "react"; + +import { + AppBar, + Box, + Button, + CssBaseline, + Divider, + Link, + LinkProps, + Stack, + Toolbar, +} from "@mui/material"; +import ads from "../../../branding.svg"; +import { Link as RouterLink, useRouteMatch } from "react-router-dom"; +import { useIsAuthenticated } from "auth/hooks/queries/useIsAuthenticated"; +import { useSignOut } from "auth/hooks/mutations/useSignOut"; + +export function LandingPageAppBar() { + const match = useRouteMatch(); + const isAuthenticated = useIsAuthenticated(); + + const links = [ + { + component: isAuthenticated ? null : ( + + ), + }, + { + component: ( + + ), + }, + { + component: ( + + ), + }, + ]; + + return ( + + + + + + + Ads + + + {links.map((l) => l.component)} + +
+ {!match.url.includes("auth") && ( + + )} + + + + ); +} + +interface HelpProps { + label: string; + props: LinkProps; +} + +function HelpLink({ label, props }: HelpProps) { + return ( + + {label} + + ); +} + +function AuthedButton(props: { isAuthenticated?: boolean }) { + const { signOut } = useSignOut(); + + return ( + + ); +} diff --git a/src/components/Background/Background.tsx b/src/components/Background/Background.tsx new file mode 100644 index 00000000..795bb020 --- /dev/null +++ b/src/components/Background/Background.tsx @@ -0,0 +1,24 @@ +import { PropsWithChildren } from "react"; +import gradient from "../../../background.svg"; +import { Container } from "@mui/material"; + +export function Background({ children }: PropsWithChildren) { + return ( + + {children} + + ); +} diff --git a/src/components/Card/PaddedCardContainer.tsx b/src/components/Card/PaddedCardContainer.tsx new file mode 100644 index 00000000..b4db7e6e --- /dev/null +++ b/src/components/Card/PaddedCardContainer.tsx @@ -0,0 +1,24 @@ +import { Card, CardContent } from "@mui/material"; +import React, { PropsWithChildren } from "react"; + +export function PaddedCardContainer({ children }: PropsWithChildren) { + return ( + + + {children} + + + ); +} diff --git a/src/components/Country/CountryPicker.tsx b/src/components/Country/CountryPicker.tsx index 073073dd..d843b594 100644 --- a/src/components/Country/CountryPicker.tsx +++ b/src/components/Country/CountryPicker.tsx @@ -1,4 +1,4 @@ -import { Autocomplete, TextField } from "@mui/material"; +import { Autocomplete, Box, FormLabel, TextField } from "@mui/material"; import { useField } from "formik"; import React from "react"; import { useCountries } from "components/Country/useCountries"; @@ -23,14 +23,16 @@ export const CountryPicker: React.FC = ({ name }) => { options={data} getOptionLabel={(option) => option.name} renderInput={(params) => ( - + + Country + + )} isOptionEqualToValue={(option, value) => option.code === value.code} value={value} diff --git a/src/components/Navigation/Navbar.tsx b/src/components/Navigation/Navbar.tsx index 82365928..e2dc17c7 100644 --- a/src/components/Navigation/Navbar.tsx +++ b/src/components/Navigation/Navbar.tsx @@ -2,8 +2,6 @@ import * as React from "react"; import { useHistory, useRouteMatch } from "react-router-dom"; -import TopBarProgress from "react-topbar-progress-indicator"; - import { AppBar, Button, @@ -16,18 +14,9 @@ import { import { UserMenu } from "components/Navigation/UserMenu"; import { DraftMenu } from "components/Navigation/DraftMenu"; import moment from "moment"; -import ads from "../../../brave-ads-black.svg"; +import ads from "../../../branding.svg"; import { useAdvertiser } from "auth/hooks/queries/useAdvertiser"; -TopBarProgress.config({ - barColors: { - "0": "#FB7959", - }, - shadowBlur: 0, - shadowColor: undefined, - barThickness: 2, -}); - export function Navbar() { const { advertiser } = useAdvertiser(); const history = useHistory(); @@ -41,7 +30,13 @@ export function Navbar() { return ( theme.zIndex.drawer + 1, bgcolor: "#ffffff" }} + sx={{ + zIndex: (theme) => theme.zIndex.drawer + 1, + bgcolor: "#ffffff", + height: "72px", + justifyContent: "center", + boxShadow: "none", + }} > diff --git a/src/components/Steps/NextAndBack.tsx b/src/components/Steps/NextAndBack.tsx index 69427d67..e63c5d78 100644 --- a/src/components/Steps/NextAndBack.tsx +++ b/src/components/Steps/NextAndBack.tsx @@ -17,37 +17,31 @@ export function NextAndBack({ final, }: Props) { return ( - - - + + {activeStep !== 0 && ( + + )} + {activeStep < steps && ( + + )} {activeStep === steps && <>{final}} ); diff --git a/src/components/Steps/StepsButton.tsx b/src/components/Steps/StepsButton.tsx deleted file mode 100644 index 9c2dea86..00000000 --- a/src/components/Steps/StepsButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Box, Step, StepLabel, Stepper } from "@mui/material"; -import React, { useState } from "react"; -import { NextAndBack } from "components/Steps/NextAndBack"; - -interface Props { - steps: { - label: string; - component: React.ReactNode; - }[]; - finalComponent: React.ReactNode; -} - -export function StepsButton({ steps, finalComponent }: Props) { - const [activeStep, setActiveStep] = useState(0); - - return ( - - {steps[activeStep].component} - - {steps.map((s) => { - const stepProps: { completed?: boolean } = {}; - return ( - - {s.label} - - ); - })} - - setActiveStep(activeStep + 1)} - onBack={() => setActiveStep(activeStep - 1)} - final={finalComponent} - /> - - ); -} diff --git a/src/form/FormikHelpers.tsx b/src/form/FormikHelpers.tsx index 78be82f3..0b92baca 100644 --- a/src/form/FormikHelpers.tsx +++ b/src/form/FormikHelpers.tsx @@ -4,6 +4,8 @@ import React, { useEffect, } from "react"; import { + Box, + Button, FormControl, FormControlLabel, FormHelperText, @@ -29,6 +31,7 @@ type FormikTextFieldProps = TextFieldProps & { small?: boolean; type?: HTMLInputTypeAttribute; disabled?: boolean; + useTopLabel?: boolean; }; export const FormikTextField: React.FC = (props) => { @@ -43,19 +46,36 @@ export const FormikTextField: React.FC = (props) => { helperText = `${len}/${props.maxLengthInstantFeedback} characters`; } + const extraOmit = props.useTopLabel ? ["label"] : []; return ( - + + {props.useTopLabel && ( + {props.label} + )} + + ); }; @@ -198,16 +218,15 @@ export const FormikSubmitButton: React.FC = ({ return (
- {formik.isSubmitting ? inProgressLabel : label} - +
); diff --git a/src/theme.tsx b/src/theme.tsx index 111a866f..1936af79 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -14,10 +14,10 @@ export const theme = createTheme({ }, palette: { primary: { - main: "#F8532BCC", + main: "#423eee", }, secondary: { - main: "#4C54D2", + main: "#fe5907", }, background: { default: "#F1F3F5", @@ -28,4 +28,21 @@ export const theme = createTheme({ secondary: "#A0A1B2", }, }, + components: { + MuiButton: { + styleOverrides: { + root: { + borderRadius: "1000px", + }, + }, + }, + MuiCard: { + styleOverrides: { + root: { + borderRadius: "12px", + boxShadow: "rgba(99, 105, 110, 0.18) 0px 1px 12px 0px", + }, + }, + }, + }, }); diff --git a/src/validation/RegistrationSchema.tsx b/src/validation/RegistrationSchema.tsx index 7fb88fa1..14c8176b 100644 --- a/src/validation/RegistrationSchema.tsx +++ b/src/validation/RegistrationSchema.tsx @@ -12,12 +12,11 @@ export const RegistrationSchema = object().shape({ .label("Email address") .required() .matches(EmailRegex, "Please enter a valid email address"), - firstName: string().label("First name").required(), - lastName: string().label("Last name").required(), + fullName: string().label("Full name").required(), advertiser: object().shape({ - name: string().label("Organization name").required(), + name: string().label("Business name").required(), url: string() - .label("Organization URL") + .label("Business URL") .required() .matches(NoSpacesRegex, `URL must not contain any whitespace`) .matches(HttpsRegex, `URL must start with https://`) @@ -26,11 +25,11 @@ export const RegistrationSchema = object().shape({ `Please enter a valid URL, for example https://brave.com`, ), phone: string() - .label("Organization phone number") + .label("Business phone number") .required() .matches( PhoneRegex, - "Please enter a valid phone number, including country code.", + "Please enter a valid phone number, that has no spaces, and includes country code.", ), }), address: object().shape({