Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
harishmohanraj committed Sep 3, 2024
1 parent 5b860b3 commit 61fb6fb
Show file tree
Hide file tree
Showing 16 changed files with 556 additions and 13 deletions.
1 change: 1 addition & 0 deletions app/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ app FastAgency {
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
google: { // Guide for setting up Auth via Google https://wasp-lang.dev/docs/auth/social-auth/overview
userSignupFields: import { getGoogleUserFields } from "@src/server/auth/setUsername.js",
configFn: import { getGoogleAuthConfig } from "@src/server/auth/setUsername.js",
Expand Down
4 changes: 3 additions & 1 deletion app/src/client/Main.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ code {
monospace;
}


.toc-marketing-checkbox-wrapper {
margin-top: 24px;
}
.toc-marketing-checkbox-wrapper .checkbox-container {
padding-left: 22px;
display: block;
Expand Down
9 changes: 6 additions & 3 deletions app/src/client/app/AccountPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Link } from 'wasp/client/router';
import { type User } from 'wasp/entities';
import { logout } from 'wasp/client/auth';
import { AuthUser } from 'wasp/auth';
// import { STRIPE_CUSTOMER_PORTAL_LINK } from '../../shared/constants';
import CustomAuthRequiredLayout from '../app/layout/CustomAuthRequiredLayout';
import Button from '../components/Button';
// import FreeTrialButton from '../components/FreeTrialButton';
import { MarketingEmailPreferenceSwitcher } from '../components/MarketingEmailPreferenceSwitcher';

const AccountPage = ({ user }: { user: User }) => {
const AccountPage = ({ user }: { user: AuthUser }) => {
const username = user.username || user.identities.username?.id;

return (
<div className='mt-10 px-6'>
<div className='overflow-hidden border border-airt-primary shadow-lg sm:rounded-lg lg:m-8 dark:border-gray-100/10'>
Expand All @@ -24,11 +27,11 @@ const AccountPage = ({ user }: { user: User }) => {
</dd>
</div>
)}
{!!user.username && (
{!!username && (
<div className='py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6'>
<dt className='text-sm font-medium text-airt-font-base dark:text-white'>Username</dt>
<dd className='mt-1 text-sm text-airt-font-base dark:text-airt-font-base sm:col-span-2 sm:mt-0'>
{user.username}
{username}
</dd>
</div>
)}
Expand Down
12 changes: 9 additions & 3 deletions app/src/client/auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const Container = styled('div', {
flexDirection: 'column',
});

export const MessageError = styled(Message, {
background: '$errorBackground',
color: '$errorText',
fontSize: '0.875rem',
});

// const HeaderText = styled('h2', {
// fontSize: '1.875rem',
// fontWeight: '700',
Expand Down Expand Up @@ -80,18 +86,18 @@ function Auth({
return (
<div className={customTheme}>
<div>
{logo && <img className='mt-10 mx-auto' style={logoStyle} src={logo} alt='Capt’n.ai' />}
{logo && <img className='mt-10 mx-auto' style={logoStyle} src={logo} alt='FastAgency logo' />}
{/* <HeaderText>{title}</HeaderText> */}
<p className='mt-6 text-2xl text-center'>{state === 'signup' ? titles.signup : titles.login}</p>
</div>

{/* {errorMessage && (
{errorMessage && (
<MessageError>
{errorMessage.title}
{errorMessage.description && ': '}
{errorMessage.description}
</MessageError>
)} */}
)}
{successMessage && <MessageSuccess>{successMessage}</MessageSuccess>}
<AuthContext.Provider value={{ isLoading, setIsLoading, setErrorMessage, setSuccessMessage }}>
{(state === 'login' || state === 'signup') && (
Expand Down
108 changes: 108 additions & 0 deletions app/src/client/auth/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { styled } from 'wasp/core/stitches.config';

// Note about using !important with some of the components:
// This is a workaround for CSS generated by Stitches not being specific enough
// and thus being overridden by Tailwind CSS. https://github.com/wasp-lang/wasp/issues/1764
// Long term we want to move away from Stitches and this is an acceptable workaround for now.

// PRIVATE API
export const Form = styled('form', {
marginTop: '1.5rem',
});

// PUBLIC API
export const FormItemGroup = styled('div', {
'& + div': {
marginTop: '1.5rem',
},
});

// PUBLIC API
export const FormLabel = styled('label', {
display: 'block !important',
fontSize: '$sm !important',
fontWeight: '500 !important',
marginBottom: '0.5rem !important',
});

const commonInputStyles = {
display: 'block !important',
lineHeight: '1.5rem !important',
fontSize: '$sm !important',
borderWidth: '1px !important',
borderColor: '$gray600 !important',
backgroundColor: '#f8f4ff !important',
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05) !important',
color: '#003257 !important',
'&:focus': {
borderWidth: '1px !important',
borderColor: '$gray700 !important',
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05) !important',
},
'&:disabled': {
opacity: '0.5 !important',
cursor: 'not-allowed !important',
backgroundColor: '$gray400 !important',
borderColor: '$gray400 !important',
color: '$gray500 !important',
},

borderRadius: '0.375rem !important',
width: '100% !important',

paddingTop: '0.375rem !important',
paddingBottom: '0.375rem !important',
paddingLeft: '0.75rem !important',
paddingRight: '0.75rem !important',
margin: '0 !important',
};

// PUBLIC API
export const FormInput = styled('input', commonInputStyles);

// PUBLIC API
export const FormTextarea = styled('textarea', commonInputStyles);

// PUBLIC API
export const FormError = styled('div', {
display: 'block',
fontSize: '$sm',
fontWeight: '500',
color: '$formErrorText',
marginTop: '0.5rem',
});

// PRIVATE API
export const SubmitButton = styled('button', {
display: 'flex !important',
justifyContent: 'center !important',

width: '100% !important',
borderWidth: '1px !important',
borderColor: '#56b7e1 !important',
backgroundColor: '#56b7e1 !important',
color: '$submitButtonText !important',

padding: '0.5rem 0.75rem !important',
marginBottom: '1.5rem !important',
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05) !important',

fontWeight: '300 !important',
fontSize: '$sm !important',
lineHeight: '1.25rem !important',
borderRadius: '0.375rem !important',

// TODO(matija): extract this into separate BaseButton component and then inherit it.
'&:hover': {
opacity: '0.85 !important',
},
'&:disabled': {
opacity: '0.5 !important',
cursor: 'not-allowed !important',
backgroundColor: '$gray400 !important',
borderColor: '$gray400 !important',
color: '$gray500 !important',
},
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1) !important',
transitionDuration: '100ms !important',
});
156 changes: 155 additions & 1 deletion app/src/client/auth/LoginSignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { useContext, useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useForm, UseFormReturn } from 'react-hook-form';

import { styled } from './configs/stitches.config';
import { AuthContext } from './Auth';
import config from './configs/config';
import TosAndMarketingEmails from '../components/TosAndMarketingEmails';
import { State } from './Auth';
import { Link } from 'wasp/client/router';
import { Form, FormInput, FormItemGroup, FormLabel, FormError, FormTextarea, SubmitButton } from './Form';

import type { AdditionalSignupFields, AdditionalSignupField, AdditionalSignupFieldRenderFn, FormState } from './types';

import { useUsernameAndPassword } from './useUsernameAndPassword';
import { useHistory } from 'react-router-dom';

const SocialAuth = styled('div', {
marginTop: '1.5rem',
Expand Down Expand Up @@ -68,6 +74,10 @@ export const LoginSignupForm = ({
const [tocChecked, setTocChecked] = useState(false);
const [marketingEmailsChecked, setMarketingEmailsChecked] = useState(false);
const [loginFlow, setLoginFlow] = useState(state);

const isLogin = state === 'login';
const cta = isLogin ? 'Sign in' : 'Sign up';

const hookForm = useForm<LoginSignupFormFields>();
const {
register,
Expand Down Expand Up @@ -110,9 +120,32 @@ export const LoginSignupForm = ({
}
}
};
const history = useHistory();
const onErrorHandler = (error: any) => {
setErrorMessage({ title: error.message, description: error.data?.data?.message });
};

const googleBtnText = loginFlow === State.Login ? 'Sign in with Google' : 'Sign up with Google';

const { handleSubmit } = useUsernameAndPassword({
isLogin,
onError: onErrorHandler,
onSuccess() {
history.push('/build');
},
});

async function onSubmit(data: any) {
setIsLoading(true);
setErrorMessage(null);
setSuccessMessage(null);
try {
await handleSubmit(data);
} finally {
setIsLoading(false);
}
}

return (
<>
{loginFlow === State.Signup && (
Expand Down Expand Up @@ -165,6 +198,62 @@ export const LoginSignupForm = ({
</button>
</SocialAuthButtons>
</SocialAuth>
<div className='mt-2 mb-5 relative'>
<div className='absolute inset-0 flex items-center'>
<div className='w-full border-t border-gray-500'></div>
</div>
<div className='relative flex justify-center text-sm'>
<span className='bg-airt-primary px-2'>Or continue with</span>
</div>
</div>
<Form
onSubmit={(e: any) => {
e.preventDefault();
if (loginFlow === State.Signup) {
if (tocChecked) {
updateLocalStorage();
hookFormHandleSubmit(onSubmit)();
} else {
setErrorMessage(checkBoxErrMsg);
}
} else {
hookFormHandleSubmit(onSubmit)();
}
}}
>
<FormItemGroup>
<FormLabel>Username</FormLabel>
<FormInput
{...register('username', {
required: 'Username is required',
})}
type='text'
disabled={isLoading}
/>
{errors.username && <FormError>{errors.username.message}</FormError>}
</FormItemGroup>
<FormItemGroup>
<FormLabel>Password</FormLabel>
<FormInput
{...register('password', {
required: 'Password is required',
})}
type='password'
disabled={isLoading}
/>
{errors.password && <FormError>{errors.password.message}</FormError>}
</FormItemGroup>
<AdditionalFormFields
hookForm={hookForm}
formState={{ isLoading }}
additionalSignupFields={additionalSignupFields}
/>
<FormItemGroup>
<SubmitButton type='submit' disabled={isLoading}>
{cta}
</SubmitButton>
</FormItemGroup>
</Form>
<div className='flex items-center justify-center'>
<span className='text-sm block'>
{loginFlow === State.Login ? "Don't have an account? " : 'Already have an account? '}
Expand All @@ -179,3 +268,68 @@ export const LoginSignupForm = ({
</>
);
};

function AdditionalFormFields({
hookForm,
formState: { isLoading },
additionalSignupFields,
}: {
hookForm: UseFormReturn<LoginSignupFormFields>;
formState: FormState;
additionalSignupFields: AdditionalSignupFields;
}) {
const {
register,
formState: { errors },
} = hookForm;

function renderField<ComponentType extends React.JSXElementConstructor<any>>(
field: AdditionalSignupField,
// Ideally we would use ComponentType here, but it doesn't work with react-hook-form
Component: any,
props?: React.ComponentProps<ComponentType>
) {
return (
<FormItemGroup key={field.name}>
<FormLabel>{field.label}</FormLabel>
<Component {...register(field.name, field.validations)} {...props} disabled={isLoading} />
{errors[field.name] && <FormError>{errors[field.name]!.message}</FormError>}
</FormItemGroup>
);
}

if (areAdditionalFieldsRenderFn(additionalSignupFields)) {
return additionalSignupFields(hookForm, { isLoading });
}

return (
additionalSignupFields &&
additionalSignupFields.map((field) => {
if (isFieldRenderFn(field)) {
return field(hookForm, { isLoading });
}
switch (field.type) {
case 'input':
return renderField<typeof FormInput>(field, FormInput, {
type: 'text',
});
case 'textarea':
return renderField<typeof FormTextarea>(field, FormTextarea);
default:
throw new Error(`Unsupported additional signup field type: ${field.type}`);
}
})
);
}

function isFieldRenderFn(
additionalSignupField: AdditionalSignupField | AdditionalSignupFieldRenderFn
): additionalSignupField is AdditionalSignupFieldRenderFn {
return typeof additionalSignupField === 'function';
}

function areAdditionalFieldsRenderFn(
additionalSignupFields: AdditionalSignupFields
): additionalSignupFields is AdditionalSignupFieldRenderFn {
return typeof additionalSignupFields === 'function';
}
Loading

0 comments on commit 61fb6fb

Please sign in to comment.