Skip to content

Commit

Permalink
Merge pull request #155 from IFRCGo/feature/login-page
Browse files Browse the repository at this point in the history
Add login page
  • Loading branch information
barshathakuri authored Nov 11, 2024
2 parents d27010d + 728628e commit b9aca34
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 8 deletions.
4 changes: 2 additions & 2 deletions env.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defineConfig, Schema } from '@julr/vite-plugin-validate-env';

// TODO: Integrate .env for CI and remove optional() call on required fields
export default defineConfig({
APP_TITLE: Schema.string.optional(),
APP_MAPBOX_ACCESS_TOKEN: Schema.string(),
APP_GRAPHQL_ENDPOINT: Schema.string.optional(),
APP_GOOGLE_ANALYTICS_ID: Schema.string.optional(),
})
APP_HCAPTCHA_SITEKEY: Schema.string.optional(),
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
"@apollo/client": "^3.9.9",
"@graphql-codegen/introspection": "^4.0.3",
"@graphql-codegen/typescript-operations": "^4.2.0",
"@hcaptcha/react-hcaptcha": "^1.11.0",
"@ifrc-go/icons": "^1.3.3",
"@ifrc-go/ui": "^1.1.2",
"@mapbox/mapbox-gl-draw": "^1.4.3",
"@placemarkio/geo-viewport": "^1.0.2",
"@sentry/react": "^7.81.1",
"@togglecorp/fujs": "^2.1.1",
"@togglecorp/re-map": "^0.2.0-beta-6",
"@togglecorp/toggle-form": "^2.0.4",
"@turf/bbox": "^6.5.0",
"@turf/circle": "^6.5.0",
"graphql": "^16.8.1",
Expand Down
14 changes: 14 additions & 0 deletions src/App/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ const pageNotFound = customWrapRoute({
},
});

const login = customWrapRoute({
parent: rootLayout,
path: 'login',
component: {
render: () => import('#views/Login'),
props: {},
},
context: {
title: 'Login',
visibility: 'is-not-authenticated',
},
});

const wrappedRoutes = {
rootLayout,
homeLayout,
Expand All @@ -190,6 +203,7 @@ const wrappedRoutes = {
allSourcesFeeds,
about,
pageNotFound,
login,
};

export const unwrappedRoutes = unwrapRoute(Object.values(wrappedRoutes));
Expand Down
85 changes: 85 additions & 0 deletions src/components/Captcha/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useCallback } from 'react';
import HCaptcha from '@hcaptcha/react-hcaptcha';
import {
InputContainer,
InputContainerProps,
} from '@ifrc-go/ui';

import { hCaptchaKey } from '#config';

export type HCaptchaProps<T> = Omit<InputContainerProps, 'input'> & {
name: T,
onChange: (value: string | undefined, name: T) => void;
elementRef?: React.RefObject<HCaptcha>;
};

function HCaptchaInput<T extends string>(props: HCaptchaProps<T>) {
const {
actions,
actionsContainerClassName,
className,
disabled,
error,
errorContainerClassName,
hint,
hintContainerClassName,
icons,
iconsContainerClassName,
inputSectionClassName,
label,
readOnly,
name,
onChange,
elementRef,
} = props;

const handleVerify = useCallback(
(token: string) => {
onChange(token, name);
},
[onChange, name],
);
const handleError = useCallback(
(err: string) => {
// eslint-disable-next-line no-console
console.error(err);
onChange(undefined, name);
},
[onChange, name],
);
const handleExpire = useCallback(
() => {
onChange(undefined, name);
},
[onChange, name],
);

return (
<InputContainer
actions={actions}
actionsContainerClassName={actionsContainerClassName}
className={className}
disabled={disabled}
error={error}
errorContainerClassName={errorContainerClassName}
hint={hint}
hintContainerClassName={hintContainerClassName}
icons={icons}
iconsContainerClassName={iconsContainerClassName}
inputSectionClassName={inputSectionClassName}
label={label}
readOnly={readOnly}
input={hCaptchaKey && (
<HCaptcha
ref={elementRef}
sitekey={hCaptchaKey}
onVerify={handleVerify}
onError={handleError}
onExpire={handleExpire}
/>
)}
/>
);
}

export default HCaptchaInput;
8 changes: 3 additions & 5 deletions src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
Button,
Heading,
NavigationTabList,
PageContainer,
Expand Down Expand Up @@ -60,13 +59,12 @@ function Navbar(props: Props) {
>
{strings.appResources}
</NavigationTab>
<Button
name={undefined}
<Link
variant="primary"
onClick={undefined}
to="login"
>
{strings.appLogin}
</Button>
</Link>
</NavigationTabList>
</PageContainer>
<PageContainer
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ const {
APP_TITLE,
APP_COMMIT_HASH,
APP_VERSION,
APP_HCAPTCHA_SITEKEY,
} = import.meta.env;

export const environment = APP_ENVIRONMENT;
export const appTitle = APP_TITLE;
export const api = APP_GRAPHQL_API_ENDPOINT;
export const mapboxToken = APP_MAPBOX_ACCESS_TOKEN;
export const hCaptchaKey = APP_HCAPTCHA_SITEKEY;
export const appCommitHash = APP_COMMIT_HASH;
export const appVersion = APP_VERSION;
22 changes: 22 additions & 0 deletions src/views/Login/i18n.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"namespace": "login",
"strings": {
"loginTitle":"IFRC GO - Login",
"loginHeader":"Login",
"loginSubHeader":"If you are staff, member or volunteer of the Red Cross Red Crescent Movement (National Societies, the IFRC and the ICRC) login with you email and password.",
"loginEmailUsername":"Email",
"loginPassword":"Password",
"loginRecoverTitle":"Recover password",
"loginShowUsernameTitle":"Show me my username",
"loginResendValidation":"Re-send validation email",
"loginResendValidationTitle":"I didn't get my validation email",
"loginForgotUserPass":"Forgot your password?",
"loginInvalid":"Invalid username or password",
"loginErrorMessage":"Error: {message}",
"loginButton":"Login",
"loginDontHaveAccount":"Don’t have an account? {signUpLink}",
"loginCreateAccountTitle":"Create new account",
"loginSignUp":"Sign up",
"loginFailureMessage": "Sorry, failed to login!"
}
}
155 changes: 155 additions & 0 deletions src/views/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { useMemo } from 'react';
import {
Button,
PasswordInput,
TextInput,
} from '@ifrc-go/ui';
import { useTranslation } from '@ifrc-go/ui/hooks';
import { resolveToComponent } from '@ifrc-go/ui/utils';
import {
createSubmitHandler,
type ObjectSchema,
requiredStringCondition,
useForm,
} from '@togglecorp/toggle-form';

import HCaptcha from '#components/Captcha';
import Link from '#components/Link';
import Page from '#components/Page';

import i18n from './i18n.json';
import styles from './styles.module.css';

interface FormFields {
email: string;
password: string;
captcha: string;
}
type PartialFormFields = Partial<FormFields>;
type FormSchema = ObjectSchema<PartialFormFields>;
type FormSchemaFields = ReturnType<FormSchema['fields']>;

const defaultFormValue: PartialFormFields = {};

const formSchema: FormSchema = {
fields: (): FormSchemaFields => ({
email: {
required: true,
requiredValidation: requiredStringCondition,
},
password: {
required: true,
requiredValidation: requiredStringCondition,
},
captcha: {
required: true,
requiredValidation: requiredStringCondition,
},
}),
};

// eslint-disable-next-line import/prefer-default-export
export function Component() {
const strings = useTranslation(i18n);
const {
value: formValue,
setFieldValue,
setError,
validate,
} = useForm(formSchema, { value: defaultFormValue });

const fieldError: PartialFormFields = {};

const handleFormSubmit = useMemo(
() => createSubmitHandler(
validate,
setError,
// FIXME: Add form submission logic here
() => {},
),
[validate, setError],
);

const signupInfo = resolveToComponent(
strings.loginDontHaveAccount,
{
signUpLink: (
<Link
to="login" // FIXME :add Register
withUnderline
>
{strings.loginSignUp}
</Link>
),
},
);

return (
<Page
className={styles.login}
title={strings.loginTitle}
heading={strings.loginHeader}
description={strings.loginSubHeader}
mainSectionClassName={styles.mainSection}
>
<form
className={styles.form}
onSubmit={handleFormSubmit}
>
<div className={styles.fields}>
<TextInput
name="email"
label={strings.loginEmailUsername}
value={formValue.email}
error={fieldError?.email}
onChange={setFieldValue}
withAsterisk
autoFocus
/>
<PasswordInput
name="password"
label={strings.loginPassword}
value={formValue.password}
error={fieldError?.password}
onChange={setFieldValue}
withAsterisk
/>
</div>
<div className={styles.utilityLinks}>
<Link
to="login" // FIXME: ForgotPassword
title={strings.loginRecoverTitle}
withUnderline
>
{strings.loginForgotUserPass}
</Link>
<Link
to="login" // FIXME :LoginResendValidation
title={strings.loginResendValidationTitle}
withUnderline
>
{strings.loginResendValidation}
</Link>
</div>
<div className={styles.actions}>
<HCaptcha
name="captcha"
onChange={setFieldValue}
/>
<Button
name={undefined}
type="submit"
onClick={handleFormSubmit}
>
{strings.loginButton}
</Button>
<div className={styles.signUp}>
{signupInfo}
</div>
</div>
</form>
</Page>
);
}

Component.displayName = 'Login';
39 changes: 39 additions & 0 deletions src/views/Login/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.login {
.main-section {
display: flex;
justify-content: center;

.form {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: var(--go-ui-spacing-xl);
max-width: var(--go-ui-width-content-max);

.fields {
display: flex;
flex-direction: column;
gap: var(--go-ui-spacing-xl);
}

.utility-links {
display: flex;
flex-direction: column;
gap: var(--go-ui-spacing-sm);
align-items: flex-end;
}

.actions {
display: flex;
flex-direction: column;
gap: var(--go-ui-spacing-lg);
align-items: center;

.sign-up {
display: flex;
gap: var(--go-ui-spacing-sm);
}
}
}
}
}
Loading

0 comments on commit b9aca34

Please sign in to comment.