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

UHF-10103: Hakuvahti React Frontend. #992

Merged
merged 26 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b409bab
UHF-9287: Just make eslint happy for now
sundflux May 9, 2024
4e20c71
UHF-9287: Changed the fieldset to accordion wrapper in the search mon…
teroelonen May 15, 2024
6e5d3ee
UHF-9287: Convert elements to components
teroelonen May 15, 2024
51ad7f0
UHF-9287: Make the email address and terms and conditions fields requ…
teroelonen May 15, 2024
fc08203
UHF-10004: Add translations for the react implementation of the hakuv…
teroelonen May 16, 2024
ca2d264
UHF-10004: Remove unnecessary line
teroelonen May 16, 2024
a7771e8
UHF-9287: Remove search description from the react form
teroelonen May 16, 2024
23b3d13
UHF-10004: Merge branch 'UHF-9287-hakuvahti-frontend-react' of https:…
teroelonen May 16, 2024
8c99b5a
UHF-10004: Remove unused translation
teroelonen May 16, 2024
f0d0ee9
UHF-10004: Change the accordion to button and custom wrapper
teroelonen May 16, 2024
d30fc1d
Merge pull request #987 from City-of-Helsinki/UHF-9287-hakuvahti-fron…
teroelonen May 17, 2024
74abd75
UHF-10004: Empty commit to trigger reference image recreation
teroelonen May 21, 2024
04d9a03
Merge pull request #990 from City-of-Helsinki/UHF-10004
teroelonen May 21, 2024
5177713
UHF-10103: Display Hakuvahti only if HAKUVAHTI_URL is set.
sundflux May 22, 2024
b0f31c9
UHF-10103: Fix dot
sundflux May 22, 2024
0c0719c
UHF-10005: Adjust button styling for search monitor form
teroelonen May 22, 2024
214f18c
UHF-10103: Build files
sundflux May 22, 2024
400d10c
Merge pull request #991 from City-of-Helsinki/UHF-10103
sundflux May 22, 2024
053d447
UHF-10005: Modify hds-element styles
teroelonen May 22, 2024
fc4c742
Fix indentation
teroelonen May 22, 2024
25ad248
Merge branch 'hakuvahti' of https://github.com/City-of-Helsinki/drupa…
teroelonen May 22, 2024
125e907
Merge branch 'main' of https://github.com/City-of-Helsinki/drupal-hdb…
teroelonen May 22, 2024
97a1de2
UHF-10005: Merge branch 'hakuvahti' of https://github.com/City-of-Hel…
teroelonen May 22, 2024
bd7b0f6
UHF-10005: Add class for error message notification box
teroelonen May 23, 2024
d6b1436
Merge pull request #993 from City-of-Helsinki/UHF-10005
sundflux May 24, 2024
b096005
Merge branch 'main' of https://github.com/City-of-Helsinki/drupal-hdb…
teroelonen May 29, 2024
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
2 changes: 1 addition & 1 deletion dist/js/job-search.min.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions hdbt.theme
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ function hdbt_preprocess(&$variables): void {
}
}

// Hakuvahti server url, expose to react if it's enabled or not.
$hakuvahtiUrl = getenv('HAKUVAHTI_URL');
$variables['#attached']['drupalSettings']['helfi_react_search']['hakuvahti_url_set'] = !empty($hakuvahtiUrl);

// Required for allowing subtheming for HDBT theme.
$variables['active_theme'] = $active_theme = \Drupal::theme()->getActiveTheme()->getName();
$variables['theme_prefix'] = $active_theme !== 'hdbt' ? $active_theme : '';
Expand Down
8 changes: 6 additions & 2 deletions src/js/react/apps/job-search/containers/ResultsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { useAtomValue } from 'jotai';
import SimpleResultsContainer from './SimpleResultsContainer';
import PromotedResultsContainer from './PromotedResultsContainer';
import { configurationsAtom } from '../store';
import SearchMonitorContainer from './SearchMonitorContainer';

const ResultsContainer = () => {
const { promoted } = useAtomValue(configurationsAtom);

return promoted?.length ?
<PromotedResultsContainer /> :
<SimpleResultsContainer />;
<PromotedResultsContainer /> :
<>
{drupalSettings?.helfi_react_search?.hakuvahti_url_set && <SearchMonitorContainer />}
<SimpleResultsContainer />
</>;
};

export default ResultsContainer;
231 changes: 231 additions & 0 deletions src/js/react/apps/job-search/containers/SearchMonitorContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { atom, useAtom, useAtomValue } from 'jotai';

import { Button, Checkbox, TextInput, Notification, IconAngleUp, IconAngleDown } from 'hds-react';
import React from 'react';
import { Buffer } from 'buffer';
import URLParams from '../types/URLParams';
import useQueryString from '../hooks/useQueryString';
import { urlAtom } from '../store';

const SearchMonitorContainer = () => {
const urlParams: URLParams = useAtomValue(urlAtom);
const query = useQueryString(urlParams);

// Form validation states
const [termsAgreed, setTermsAgreed] = useAtom(receiveNewsletterAtom);
const [email, setEmail] = useAtom(emailAtom);
const [submitted, setSubmitted] = useAtom(submittedAtom);
const [errorMessage, seterrorMessage] = useAtom(errorAtom);
const [isFormVisible, setIsFormVisible] = useAtom(isFormVisibleAtom);

// ElasticSearch query base64 encoded
const queryEncoded = Buffer.from(query).toString('base64');
const searchDescription = '-';
const lang = '';

// Relative url for "query" parameter
const currentPath = window.location.pathname;
const currentParams = window.location.search;
const currentRelativeUrl = currentPath + currentParams;

const requestBody = {
elastic_query: queryEncoded,
query: currentRelativeUrl,
email,
search_description: searchDescription,
// lang is filled by Drupal module
lang
};

const onSubmit = async (
event: React.FormEvent<HTMLFormElement>,
): Promise<void> => {
event.preventDefault();

// Validate stuff to submit form
if (!termsAgreed) {
seterrorMessage(Drupal.t('The choice is mandatory: Terms of service', {}, { context: 'Search monitor error terms' }));
return;
}

if (!email) {
seterrorMessage(Drupal.t('This field is mandatory: Email address', {}, { context: 'Search monitor error email' }));
return;
}

// Make submit button disabled after submitting to prevent double submits
const submitButton = document.getElementById('job-search-form__search-monitor__submit-button');
if (submitButton) {
submitButton.setAttribute('disabled', 'true');
}

// Get csrf token from Drupal
let sessionToken = '';
try {
const response = await fetch('/session/token', {
method: 'GET',
});

if (!response.ok) {
seterrorMessage(
`Error getting session token: ${response.statusText}`,
);
if (submitButton) {
submitButton.removeAttribute('disabled');
}
return;
}

sessionToken = await response.text();
} catch (error) {
seterrorMessage(`Error getting session token: ${error}`);
if (submitButton) {
submitButton.removeAttribute('disabled');
}
return;
}

// Send form to Hakuvahti subscribe service
const body = JSON.stringify(requestBody);
const response = await fetch('/hakuvahti/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'token': sessionToken,
},
body
});

// Oops, error from backend
if (!response.ok) {
seterrorMessage(
`Error submitting form: ${response.statusText}`,
);
if (submitButton) {
submitButton.removeAttribute('disabled');
}
return;
}

// Release submit locks and show success page
setSubmitted(true);
seterrorMessage('');
if (submitButton) {
submitButton.removeAttribute('disabled');
}
};

const formHeader: string = Drupal.t('Receive search results by email', {}, { context: 'Search monitor header' });
const openLabel: string = Drupal.t('Open', {}, { context: 'Search monitor open label' });
const closeLabel: string = Drupal.t('Close', {}, { context: 'Search monitor close label' });
const descriptionHeader: string = Drupal.t('Saved search', {}, { context: 'Search monitor content title' });
const descriptionFirstPart: string = Drupal.t('Save the search you make so that you can receive an email notification of new results matching your search criteria.', {}, { context: 'Search monitor content' });
const descriptionSecondPart: string = Drupal.t('You can save as many searches as you like. You can delete the saved search via the link in the email messages.', {}, { context: 'Search monitor content' });
const emailLabel: string = Drupal.t('Email address', {}, { context: 'Search monitor email label' });
const acceptTermsLabel: string = Drupal.t('Accept the terms and conditions.', {}, { context: 'Search monitor terms label' });
const buttonLabel: string = Drupal.t('Save your search', {}, { context: 'Search monitor submit button label' });
const thankYouHeader: string = Drupal.t('Your search has been saved', {}, { context: 'Search monitor thank you header' });
const thankYouMessage: string = Drupal.t('You will receive a confirmation link by email. You can activate the saved search via the link.', {}, { context: 'Search monitor thank you message' });
const errorLabel: string = Drupal.t('Please check these selections', {}, { context: 'Search monitor error label' });

const customCheckboxStyles = {
'--background-unselected': 'var(--color-white)',
'--background-selected': 'var(--color-black)',
marginTop: 'var(--spacing-m)',
};

return (
<form onSubmit={onSubmit} className="job-search-form__search-monitor">
{!submitted && (
<>
<h3 className='job-search-form__search-monitor__heading'>{formHeader}</h3>
<Button
type="button"
aria-controls='job-search-form__search-monitor__content'
variant="supplementary"
theme="black"
iconLeft={isFormVisible ? <IconAngleUp /> : <IconAngleDown />}
onClick={(event: React.MouseEvent) => {
event.preventDefault();
setIsFormVisible(!isFormVisible);
}}
style={{
backgroundColor: 'transparent',
}}
>
{isFormVisible ? closeLabel : openLabel}
</Button>

<div id='job-search-form__search-monitor__content' className='job-search-form__search-monitor__content' aria-hidden={!isFormVisible}>
<h4 className='job-search-form__search-monitor__content__heading'>{descriptionHeader}</h4>
<p>{descriptionFirstPart}</p>
<p>{descriptionSecondPart}</p>

{errorMessage &&
<Notification
type='error'
size='default'
label={errorLabel}
className='job-search-form__search-monitor__error'
>
{errorMessage}
</Notification>
}

<TextInput
className='job-search-form__search-monitor__email'
id='job-search-form__search_monitor__email'
label={emailLabel}
name='job-search-form__search_monitor__email'
type='email'
onChange={(event) => setEmail(event.target.value)}
value={email}
required
style={{
marginTop: 'var(--spacing-m)',
}}
/>

<Checkbox
className='job-search-form__search-monitor__terms'
label={acceptTermsLabel}
id='job-search-form__search_monitor__terms'
onChange={(event) => setTermsAgreed(event.target.checked)}
checked={termsAgreed}
name='job-search-form__search_monitor__terms'
required
style={customCheckboxStyles}
/>

<Button
className='hdbt-search--react__submit-button job-search-form__search-monitor__submit-button'
type='submit'
id='job-search-form__search-monitor__submit-button'
style={{
marginBottom: '0',
marginTop: 'var(--spacing-l)',
}}
>
{buttonLabel}
</Button>
</div>
</>
)}

{submitted &&
<>
<h3 className='job-search-form__search-monitor__heading'>{thankYouHeader}</h3>
<p>{thankYouMessage}</p>
</>
}
</form>
);
};

const emailAtom = atom('');
const receiveNewsletterAtom = atom(false);
const submittedAtom = atom(false);
const isFormVisibleAtom = atom(false);
const errorAtom = atom('');

export default SearchMonitorContainer;
1 change: 1 addition & 0 deletions src/js/types/drupalSettings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ declare namespace drupalSettings {
cookie_privacy_url: string;
elastic_proxy_url: string;
sentry_dsn_react: string;
hakuvahti_url_set: boolean;
};
const helfi_rekry_job_search: {
results_page_path: string;
Expand Down
56 changes: 56 additions & 0 deletions translations/fi.po
Original file line number Diff line number Diff line change
Expand Up @@ -1217,3 +1217,59 @@ msgstr "Näytä vain"
msgctxt "Label for news card published time"
msgid "Published"
msgstr "Julkaistu"

msgctxt "Search monitor header"
msgid "Receive search results by email"
msgstr "Tilaa hakuvahti sähköpostiisi"

msgctxt "Search monitor open label"
msgid "Open"
msgstr "Avaa"

msgctxt "Search monitor close label"
msgid "Close"
msgstr "Sulje"

msgctxt "Search monitor content title"
msgid "Saved search"
msgstr "Hakuvahti"

msgctxt "Search monitor content"
msgid "Save the search you make so that you can receive an email notification of new results matching your search criteria."
msgstr "Tallenna tekemäsi haku hakuvahdiksi, niin saat sähköpostiisi tiedon uusista osumista."

msgctxt "Search monitor content"
msgid "You can save as many searches as you like. You can delete the saved search via the link in the email messages."
msgstr "Voit luoda niin monta hakuvahtia kuin haluat ja poistaa hakuvahdin sähköpostien mukana tulevasta linkistä."

msgctxt "Search monitor email label"
msgid "Email address"
msgstr "Sähköpostiosoite"

msgctxt "Search monitor terms label"
msgid "Accept the terms and conditions."
msgstr "Hyväksy käyttöehdot."

msgctxt "Search monitor submit button label"
msgid "Save your search"
msgstr "Tilaa hakuvahti"

msgctxt "Search monitor thank you header"
msgid "Your search has been saved"
msgstr "Hakuvahti on luotu"

msgctxt "Search monitor thank you message"
msgid "You will receive a confirmation link by email. You can activate the saved search via the link."
msgstr "Saat vielä sähköpostiisi vahvistuslinkin, jolla voit aktivoida hakuvahdin."

msgctxt "Search monitor error label"
msgid "Please check these selections"
msgstr "Tarkistathan vielä nämä kohdat"

msgctxt "Search monitor error terms"
msgid "The choice is mandatory: Terms of service"
msgstr "Valinta on pakollinen: Käyttöehdot"

msgctxt "Search monitor error email"
msgid "This field is mandatory: Email address"
msgstr "Kenttä on pakollinen: Sähköpostiosoite"
56 changes: 56 additions & 0 deletions translations/sv.po
Original file line number Diff line number Diff line change
Expand Up @@ -1219,3 +1219,59 @@ msgstr "Visa endast"
msgctxt "Label for news card published time"
msgid "Published"
msgstr "Publicerad"

msgctxt "Search monitor header"
msgid "Receive search results by email"
msgstr "Beställ sökvakten till din e-post"

msgctxt "Search monitor open label"
msgid "Open"
msgstr "Öppna"

msgctxt "Search monitor close label"
msgid "Close"
msgstr "Stäng"

msgctxt "Search monitor content title"
msgid "Saved search"
msgstr "Sökvakten"

msgctxt "Search monitor content"
msgid "Save the search you make so that you can receive an email notification of new results matching your search criteria."
msgstr "Spara din sökning som en sökvakt, så får du information om nya träffar till din e-post."

msgctxt "Search monitor content"
msgid "You can save as many searches as you like. You can delete the saved search via the link in the email messages."
msgstr "Du kan skapa så många sökvakter du vill och ta bort sökvakten via länken som finns i e-postmeddelandena. "

msgctxt "Search monitor email label"
msgid "Email address"
msgstr "E-postadress"

msgctxt "Search monitor terms label"
msgid "Accept the terms and conditions."
msgstr "Godkänn användarvillkoren."

msgctxt "Search monitor submit button label"
msgid "Save your search"
msgstr "Beställ sökvakten"

msgctxt "Search monitor thank you header"
msgid "Your search has been saved"
msgstr "Sökvakten har skapats"

msgctxt "Search monitor thank you message"
msgid "You will receive a confirmation link by email. You can activate the saved search via the link."
msgstr "Du får ännu en bekräftelselänk till din e-post genom vilken du kan aktivera sökvakten."

msgctxt "Search monitor error label"
msgid "Please check these selections"
msgstr "Kontrollera ännu dessa punkter"

msgctxt "Search monitor error terms"
msgid "The choice is mandatory: Terms of service"
msgstr "Valet är obligatoriskt: Användningsvillkor"

msgctxt "Search monitor error email"
msgid "This field is mandatory: Email address"
msgstr "Fältet är obligatoriskt: E-postadress"