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
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@bunchtogether/vite-plugin-flow": "^1.0.2",
"@clerk/react": "^6.0.1",
"@clerk/react": "^6.3.0",
"@performant-software/geospatial": "^3.1.17",
"@performant-software/semantic-components": "^3.1.17",
"@performant-software/shared-components": "^3.1.17",
Expand Down
42 changes: 21 additions & 21 deletions client/src/components/ClerkAuthenticationContextProvider.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow

import { useAuth } from '@clerk/react';
import React, { useEffect, useMemo, useState } from 'react';
import { AuthenticationContext } from '../context/Authentication';
Expand All @@ -9,10 +10,8 @@ const PROVIDER = import.meta.env.VITE_AUTH_PROVIDER || 'local';
const ClerkAuthenticationContextProvider = (props: any) => {
const clerkAuth = useAuth();
const [user, setUser] = useState(null);
const [ready, setReady] = useState(false);

/**
* Call the /me endpoint to get the current user's data.
*/
useEffect(() => {
if (clerkAuth.isSignedIn && !user) {
UsersService.getMe()
Expand All @@ -21,29 +20,30 @@ const ClerkAuthenticationContextProvider = (props: any) => {
}
}, [clerkAuth.isSignedIn]);

const data = useMemo(() => {
return {
authenticated: clerkAuth.isSignedIn,
logout: clerkAuth.signOut,
user
};
}, [clerkAuth, user]);

/**
* Block the initial render so the useEffect has a chance to set up the axios interceptor.
*/
const ready = useMemo(() => (
clerkAuth.isLoaded && (user || !clerkAuth.isSignedIn)),
[clerkAuth.isLoaded, user, clerkAuth.isSignedIn]
);
const authenticated = useMemo(() => {
if (!clerkAuth.isLoaded) return !!user;
return clerkAuth.isSignedIn;
}, [clerkAuth.isLoaded, clerkAuth.isSignedIn, user]);

const data = useMemo(() => ({
authenticated,
logout: clerkAuth.signOut,
user
}), [authenticated, clerkAuth.signOut, user]);

useEffect(() => {
if (clerkAuth.isLoaded && !!(user || !clerkAuth.isSignedIn)) {
setReady(true);
}
}, [clerkAuth.isSignedIn, user]);

return (
<AuthenticationContext.Provider
value={{ ...data, provider: PROVIDER }}
>
{ ready && props.children }
{ready && props.children}
</AuthenticationContext.Provider>
)
}
);
};

export default ClerkAuthenticationContextProvider;
261 changes: 133 additions & 128 deletions client/src/components/ItemPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { Toaster } from '@performant-software/semantic-components';
import cx from 'classnames';
import React, {
useCallback,
useContext,
useEffect,
useMemo,
Expand Down Expand Up @@ -31,8 +30,8 @@ import Relationships from './Relationships';
import SaveButton from './SaveButton';
import Section from './Section';
import styles from './ItemPage.module.css';
import useReactRouterEditPage from '../hooks/useReactRouterEditPage';
import Validation from '../utils/Validation';
import withReactRouterEditPage from '../hooks/ReactRouterEditPage';

type Props = {
form: Element<any>,
Expand All @@ -44,166 +43,172 @@ type Props = {

type ComponentProps = {
errors?: Array<string>,
form: any,
item: any,
loading: boolean,
onCreateManifests: (item: any) => Promise<any>,
onSave: (item: any) => Promise<any>,
onSaved: (item: any) => void,
saved?: boolean
saved?: boolean,
saving?: boolean
};

const ItemPage = (props: Props) => {
const {
form: Form,
onCreateManifests,
onInitialize,
onSave
} = props;

const Component = (props: ComponentProps) => {
const [saved, setSaved] = useState(false);
const { label, name, url } = initialize(props);
const { projectModel } = useContext(ProjectContext);
const { t } = useTranslation();

const Component = useCallback((props: ComponentProps) => {
const [saved, setSaved] = useState(false);
const { label, name, url } = initialize(props);
const { projectModel } = useContext(ProjectContext);

/**
* Memo-izes the ItemLayoutContext value.
*
* @type {{saved: boolean, setSaved: function(): void}}
*/
const layoutValue = useMemo(() => ({ saved, setSaved }), [saved, setSaved]);
/**
* Memo-izes the ItemLayoutContext value.
*
* @type {{saved: boolean, setSaved: function(): void}}
*/
const layoutValue = useMemo(() => ({ saved, setSaved }), [saved, setSaved]);

/**
* Memo-izes the ItemContext value.
*
* @type {{uuid: *}}
*/
const itemValue = useMemo(() => ({ uuid: props.item.uuid }), [props.item?.uuid]);
/**
* Memo-izes the ItemContext value.
*
* @type {{uuid: *}}
*/
const itemValue = useMemo(() => ({ uuid: props.item.uuid }), [props.item?.uuid]);

/**
* Sets the saved prop on the state when the component is mounted.
*/
useEffect(() => {
if (props.saved) {
setSaved(true);
}
}, []);
/**
* Sets the saved prop on the state when the component is mounted.
*/
useEffect(() => {
if (props.saved) {
setSaved(true);
}
}, [props.saved]);

return (
<ItemLayoutContext.Provider
value={layoutValue}
return (
<ItemLayoutContext.Provider
value={layoutValue}
>
<ItemContext.Provider
value={itemValue}
>
<ItemContext.Provider
value={itemValue}
<ItemLayout
className={styles.itemPage}
>
<ItemLayout
className={styles.itemPage}
<ItemLayout.Toaster
onDismiss={() => setSaved(false)}
type={Toaster.MessageTypes.positive}
visible={saved}
>
<ItemLayout.Toaster
onDismiss={() => setSaved(false)}
type={Toaster.MessageTypes.positive}
visible={saved}
<Message.Header
content={t('Common.messages.save.header')}
/>
<Message.Content
content={t('Common.messages.save.content')}
/>
</ItemLayout.Toaster>
<ItemLayout.Toaster
timeout={0}
type={Toaster.MessageTypes.negative}
visible={!_.isEmpty(props.errors)}
>
<Message.Header
content={t('Common.errors.header')}
/>
<Message.List
items={props.errors}
/>
</ItemLayout.Toaster>
<ItemLayout.Header>
<ItemHeader
back={{
label,
url
}}
name={name}
/>
</ItemLayout.Header>
<ItemLayout.Sidebar>
<ProjectItemMenu />
</ItemLayout.Sidebar>
<ItemLayout.Content>
<Dimmer
active={props.loading}
inverted
>
<Message.Header
content={t('Common.messages.save.header')}
/>
<Message.Content
content={t('Common.messages.save.content')}
/>
</ItemLayout.Toaster>
<ItemLayout.Toaster
timeout={0}
type={Toaster.MessageTypes.negative}
visible={!_.isEmpty(props.errors)}
<Loader />
</Dimmer>
<Section
id='details'
>
<Message.Header
content={t('Common.errors.header')}
<SaveButton
onClick={props.onSave}
saving={props.saving}
/>
<Message.List
items={props.errors}
<Header
className={cx(styles.ui, styles.header)}
content={t('ItemPage.labels.details')}
/>
</ItemLayout.Toaster>
<ItemLayout.Header>
<ItemHeader
back={{
label,
url
}}
name={name}
<props.form
{...props}
/>
</ItemLayout.Header>
<ItemLayout.Sidebar>
<ProjectItemMenu />
</ItemLayout.Sidebar>
<ItemLayout.Content>
<Dimmer
active={props.loading}
inverted
>
<Loader />
</Dimmer>
<Section
id='details'
>
<SaveButton
onClick={props.onSave}
saving={props.saving}
/>
<Header
className={cx(styles.ui, styles.header)}
content={t('ItemPage.labels.details')}
/>
<Form
{...props}
/>
<SaveButton
onClick={props.onSave}
saving={props.saving}
/>
</Section>
<Relationships
onCreateManifests={onCreateManifests}
<SaveButton
onClick={props.onSave}
saving={props.saving}
/>
{ projectModel?.allow_identifiers && props.item.id && (
<Section
id='identifiers'
>
<Divider
section
/>
<Header
content={t('ItemPage.labels.identifiers')}
/>
<RelatedIdentifiers />
</Section>
)}
</Section>
<Relationships
onCreateManifests={props.onCreateManifests}
/>
{ projectModel?.allow_identifiers && props.item.id && (
<Section
id='merges'
id='identifiers'
>
<Divider
section
/>
<Header
content={t('ItemPage.labels.merges')}
content={t('ItemPage.labels.identifiers')}
/>
<RelatedRecordMerges />
<RelatedIdentifiers />
</Section>
</ItemLayout.Content>
</ItemLayout>
</ItemContext.Provider>
</ItemLayoutContext.Provider>
);
}, []);
)}
<Section
id='merges'
>
<Divider
section
/>
<Header
content={t('ItemPage.labels.merges')}
/>
<RelatedRecordMerges />
</Section>
</ItemLayout.Content>
</ItemLayout>
</ItemContext.Provider>
</ItemLayoutContext.Provider>
);
};

const ItemPage = (props: Props) => {
const {
onCreateManifests,
onInitialize,
onSave
} = props;

const Page = withReactRouterEditPage(Component, {
const editPageProps = useReactRouterEditPage({
id: 'itemId',
onCreateManifests,
onSave,
onInitialize,
resolveValidationError: Validation.resolveUpdateError.bind(this)
resolveValidationError: Validation.resolveUpdateError
});

return (
<Page />
<Component
{...props}
{...editPageProps}
/>
);
};

export default ItemPage;
export default ItemPage;
Loading