Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/app/helpers/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export type Book = {
subjects: string[];
title: string;
content_warning_text: string;
assignable_book?: boolean;
kindle_link?: string;
has_faculty_resources?: boolean;
has_student_resources?: boolean;

};

const statesToInclude = ['live', 'new_edition_available', 'coming_soon'];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
import React, {useState} from 'react';
import buildContext from '~/components/jsx-helpers/build-context';
import fetchBooks from '~/models/books';
import {type Book} from '~/helpers/books';
import {useLocation} from 'react-router-dom';
import {useDataFromPromise, useDataFromSlug} from '~/helpers/page-data-utils';

type ContextValue = {
selectedBook: Book | null;
books: Book[] | null | undefined;
hasError: string | null;
setHasError: (error: string | null) => void;
hideErrors: boolean;
submitting: boolean;
validateBeforeSubmitting: (event: React.FormEvent) => void;
searchParams: URLSearchParams;
title: string | null;
setTitle: (title: string) => void;
};

function useSearchParams() {
const {search} = useLocation();

return React.useMemo(() => new window.URLSearchParams(search), [search]);
}

function useContextValue() {
function useContextValue(): ContextValue {
const searchParams = useSearchParams();
const initialTitle = React.useMemo(() => searchParams.get('book'), [searchParams]);
const [title, setTitle] = useState(initialTitle);
const initialTitle = React.useMemo(
() => searchParams.get('book'),
[searchParams]
);
const [title, setTitle] = useState<string | null>(initialTitle);
const books = useDataFromPromise(fetchBooks);
const selectedBook = React.useMemo(
() => books?.find((b) => b.title === title) || {},
() => books?.find((b) => b.title === title) || null,
[books, title]
);
const bookInfo = useDataFromSlug(selectedBook.slug) || selectedBook;
const [hasError, setHasError] = useState('You have not completed the form');
const bookInfo =
useDataFromSlug<Book>(selectedBook ? selectedBook.slug : null) ||
selectedBook;
const [hasError, setHasError] = useState<string | null>(
'You have not completed the form'
);
const [hideErrors, setHideErrors] = useState(true);
const [submitting, setSubmitting] = useState(false);
const validateBeforeSubmitting = React.useCallback(
(event) => {
(event: React.FormEvent) => {
event.preventDefault();
setHideErrors(false);
if (!hasError) {
Expand All @@ -37,7 +58,8 @@ function useContextValue() {
return {
selectedBook: bookInfo,
books,
hasError, setHasError,
hasError,
setHasError,
hideErrors,
submitting,
validateBeforeSubmitting,
Expand All @@ -49,7 +71,4 @@ function useContextValue() {

const {useContext, ContextProvider} = buildContext({useContextValue});

export {
useContext as default,
ContextProvider as ErrataFormContextProvider
};
export {useContext as default, ContextProvider as ErrataFormContextProvider};
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from 'react';
import Form from './form/form';
import FormSelect from '~/components/form-select/form-select';
import useErrataFormContext, {ErrataFormContextProvider} from './errata-form-context';
import useErrataFormContext, {
ErrataFormContextProvider
} from './errata-form-context';
import useUserContext from '~/contexts/user';
import linkHelper from '~/helpers/link';
import './errata-form.scss';

type Book = {
title: string;
};

function ErrataForm() {
const {title} = useErrataFormContext();

Expand All @@ -26,22 +32,29 @@ function ErrataForm() {
function TitleSelector() {
const {books, setTitle} = useErrataFormContext();
const options = React.useMemo(
() => books?.map((book) => ({label: book.title, value: book.title})),
() =>
books?.map((book: Book) => ({
label: book.title,
value: book.title
})),
[books]
);

return (
<div className="text-content title-selector">
<p>
It looks like you got referred here but they didn&apos;t tell us what
book you were looking at.
It looks like you got referred here but they didn&apos;t tell us
what book you were looking at.
</p>
<FormSelect
name="title-selector"
selectAttributes={{
placeholder: 'Please select one'
}}
onValueUpdate={setTitle}
label="What book were you in, again?" options={options} />
label="What book were you in, again?"
options={options ?? []}
/>
</div>
);
}
Expand All @@ -64,7 +77,13 @@ export default function EnsureLoggedIn() {
<main className="errata-form page">
<div className="boxed">
<div>You need to be logged in to submit errata</div>
<a className="btn primary" href={linkHelper.loginLink()} data-local="true">Log in</a>
<a
className="btn primary"
href={linkHelper.loginLink()}
data-local="true"
>
Log in
</a>
</div>
</main>
);
Expand Down

This file was deleted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier to see the changes in the individual commits.

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, {useState, useRef, useEffect} from 'react';
import useErrataFormContext from '../../errata-form-context';
import managedInvalidMessage from '../InvalidMessage';
import TocSelector from './toc-selector';
import './ErrorLocationSelector.scss';

type AdditionalLocationInputProps = {
value?: string;
readOnly?: boolean;
updateValue?: (value: string) => void;
required?: boolean;
};

type InputComponentProps = {
defaultValue: string | null;
};

function AdditionalLocationInput({
value,
readOnly = false,
updateValue,
required = true
}: AdditionalLocationInputProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [InvalidMessage, updateInvalidMessage] =
managedInvalidMessage(inputRef);
const syncValue = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) =>
updateValue?.(event.target.value),
[updateValue]
);

useEffect(updateInvalidMessage, [required, updateInvalidMessage]);

return (
<React.Fragment>
<label
className="question"
htmlFor="additional_location_information"
>
Additional location information, if applicable
</label>
<InvalidMessage />
<input
id="additional_location_information"
type="text"
name="additional_location_information"
placeholder="Describe where you found the error"
value={value}
onChange={syncValue}
ref={inputRef}
readOnly={readOnly}
required={required}
/>
</React.Fragment>
);
}

function DefaultValue({defaultValue}: InputComponentProps) {
return (
<AdditionalLocationInput
value={defaultValue as string}
readOnly={true}
/>
);
}

function NotDefaultValue({defaultValue}: InputComponentProps) {
const [tocV, updateTocV] = useState<string | null>();
const [addlV, updateAddlV] = useState(defaultValue || '');
const required = () => !tocV && !addlV;

return (
<React.Fragment>
<TocSelector required={required()} updateValue={updateTocV} />
<AdditionalLocationInput
value={addlV}
required={required()}
updateValue={updateAddlV}
/>
</React.Fragment>
);
}

export default function ErrorLocationSelector() {
const {searchParams} = useErrataFormContext();
const defaultValue = searchParams.get('location');
const readOnly = defaultValue && searchParams.get('source');
const Input = readOnly ? DefaultValue : NotDefaultValue;

return <Input defaultValue={defaultValue} />;
}
Loading