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
1 change: 1 addition & 0 deletions components/CustomLink/CustomLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function CustomLink({ Component: _Component, href, children, ...props }) {
const WrappedComponent = React.forwardRef(({ Component, ...props }, ref) => (
<Component ref={ref} {...props} />
));
WrappedComponent.displayName = 'WrappedComponent';

CustomLink.propTypes = {
Component: PropTypes.oneOfType([
Expand Down
1 change: 1 addition & 0 deletions components/EventCallout/EventCallout.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function EventCallout({
height="60px"
width="60px"
src={imageSrc}
alt={title || ''}
rounded="xl"
bg="neutrals.300"
style={{ objectFit: 'cover' }}
Expand Down
4 changes: 2 additions & 2 deletions components/HomeFeed/HomeFeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,15 +320,15 @@ function HomeFeedCTA({ authenticated, personaFeed }) {
title={
<>
<Heading color="neutrals.900" variant="h2" fontWeight="800">
There's Something for Everyone
There&apos;s Something for Everyone
</Heading>
</>
}
textProps={{
mb: { xl: 'xxl' },
mt: { _: 'm', lg: 0, xl: 'l' },
}}
supertitle="Don't miss what God's doing this week!"
supertitle="Don&apos;t miss what God&apos;s doing this week!"
description="From Sunday services to Wednesday night activities, there’s always a way to be a part of what God is doing every week at Long Hollow. Check out our weekly schedule for every member of your family, and mark your calendar for several upcoming events."
actions={[
{
Expand Down
10 changes: 5 additions & 5 deletions components/Modals/AuthModal/AuthDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ function AuthDetails() {
handleAuthIdentity,
} = useAuthIdentity();
const [_, dispatch] = useAuth();
const { values, handleSubmit, handleChange } = useForm(() => {
const age = getAge(values.birthDate);
const { values, handleSubmit, handleChange } = useForm(formValues => {
const age = getAge(formValues.birthDate);
// Make sure they are at least 13 years of age.
if (age < 13) {
setError({
Expand All @@ -28,9 +28,9 @@ function AuthDetails() {
dispatch(
updateAuth({
profile: {
...values,
birthDate: new Date(values.birthDate),
gender: upperFirst(values.gender),
...formValues,
birthDate: new Date(formValues.birthDate),
gender: upperFirst(formValues.gender),
},
})
);
Expand Down
12 changes: 7 additions & 5 deletions components/Modals/AuthModal/AuthIdentity.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ function AuthIdentity() {
setError,
handleAuthIdentity,
} = useAuthIdentity();
const identityRef = React.useRef('');
const [checkIfUserExists] = useUserExists({
fetchPolicy: 'network-only',
onCompleted: async data => {
const identity = values.identity;
const identity = identityRef.current;
const userExists = data?.userExists !== 'NONE';
handleAuthIdentity({
identity,
Expand All @@ -24,14 +25,15 @@ function AuthIdentity() {
});
},
});
const { values, handleSubmit, handleChange } = useForm(() => {
const identity = values.identity;
const { values, handleSubmit, handleChange } = useForm(formValues => {
const identity = formValues.identity;
const validEmail = validateEmail(identity);
const validPhoneNumber = validatePhoneNumber(identity);
const validIdentity = validEmail || validPhoneNumber;
if (validIdentity) {
setStatus('LOADING');
checkIfUserExists({ variables: { identity: values.identity } });
identityRef.current = identity;
checkIfUserExists({ variables: { identity } });
} else {
setStatus('ERROR');
setError({ identity: 'That is not a valid email or phone number.' });
Expand All @@ -43,7 +45,7 @@ function AuthIdentity() {
return (
<>
<Box as="p" color="subdued" mb="l">
Enter your phone number or email address to get started. We'll never
Enter your phone number or email address to get started. We&apos;ll never
share your information or contact you (unless you ask!).
</Box>
<Box as="form" action="" onSubmit={handleSubmit} px={{ _: 0, lg: 'xl' }}>
Expand Down
2 changes: 1 addition & 1 deletion components/Nav/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function Nav({
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart);
};
}, []);
}, [router.events, setHoveredItem]);

return (
<Styled.Nav active={active}>
Expand Down
2 changes: 1 addition & 1 deletion components/SEO/SEO.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function SEO(props = {}) {
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_CODE}`}
/>
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
Expand Down
22 changes: 10 additions & 12 deletions components/SinglePages/UniversalContentItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ export default function Page({
}
`
);
if (authData?.currentUser?.id) {
const token = authData.currentUser.rock.authToken;
const html = data?.htmlContent || '';
const authedHTML = html.replace(
/"([^"]*my\.longhollow\.com[^"]*)"/g,
(match, p1) => `"${p1}?rckipid=${token}"`
);
data.htmlContent = authedHTML;
}
const token = authData?.currentUser?.rock?.authToken;
const htmlContent = token
? (data?.htmlContent || '').replace(
/"([^"]*my\.longhollow\.com[^"]*)"/g,
(match, p1) => `"${p1}?rckipid=${token}"`
)
: data?.htmlContent;

if (data?.loading || router.isFallback) {
return null;
Expand Down Expand Up @@ -179,15 +177,15 @@ export default function Page({
<Section
mb={{
_: 'l',
lg: data.htmlContent ? 'l' : 'xxl',
lg: htmlContent ? 'l' : 'xxl',
}}
>
<MetadataCallout data={data} />
</Section>
) : null}
{data.htmlContent && (
{htmlContent && (
<Section mb={{ _: 'l', lg: 'xxl' }}>
<Longform dangerouslySetInnerHTML={{ __html: data.htmlContent }} />
<Longform dangerouslySetInnerHTML={{ __html: htmlContent }} />
</Section>
)}
{data.ctaLinks?.length ? (
Expand Down
42 changes: 27 additions & 15 deletions components/UserProfile/EditUserProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,46 +56,58 @@ function EditUserProfile(props = {}) {
allowSMS,
} = user;

function getCommunicationPreference(key, variable) {
const _isNil = isNil(values[key]) && isNil(variable);
function getCommunicationPreference(formValues, key, variable) {
const _isNil = isNil(formValues[key]) && isNil(variable);
if (_isNil) return false;
if (!isNil(values[key])) return values[key];
if (!isNil(formValues[key])) return formValues[key];
if (!isNil(variable)) return variable;
return false;
}

function onSubmit() {
function onSubmit(formValues) {
try {
updateCurrentPerson({
variables: {
profileFields: [
{
field: 'Gender',
value: startCase(values.gender) || startCase(gender) || 'Unknown',
value:
startCase(formValues.gender) || startCase(gender) || 'Unknown',
},
{
field: 'BirthDate',
value: formValues.birthdate || birthdate || '',
},
{ field: 'BirthDate', value: values.birthdate || birthdate || '' },
{
field: 'PhoneNumber',
value: values.phone || phone || '',
value: formValues.phone || phone || '',
},
{ field: 'Email', value: values.email || email || '' },
{ field: 'Email', value: formValues.email || email || '' },
],
address: {
street1: values.street || street || '',
street1: formValues.street || street || '',
street2: '',
city: values.city || city || '',
state: values.state || state || '',
postalCode: values.zip || zip || '',
city: formValues.city || city || '',
state: formValues.state || state || '',
postalCode: formValues.zip || zip || '',
},
campusId: values.campus || campusId || '',
campusId: formValues.campus || campusId || '',
communicationPreferences: [
{
type: 'SMS',
allow: getCommunicationPreference('allowSMS', allowSMS),
allow: getCommunicationPreference(
formValues,
'allowSMS',
allowSMS
),
},
{
type: 'Email',
allow: getCommunicationPreference('allowEmail', allowEmail),
allow: getCommunicationPreference(
formValues,
'allowEmail',
allowEmail
),
},
],
},
Expand Down
12 changes: 6 additions & 6 deletions components/UserProfile/UserProfile.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';

import { UserProfileProvider } from 'providers';
Expand Down Expand Up @@ -78,20 +78,20 @@ function UserProfile(props = {}) {
const authDispatch = useAuthDispatch();
const modalDispatch = useModalDispatch();

function handleSignInClick() {
const handleSignInClick = useCallback(() => {
modalDispatch(showModal('Auth'));
}
}, [modalDispatch]);

function handleLogoutClick() {
const handleLogoutClick = useCallback(() => {
authDispatch(logout());
router.reload();
}
}, [authDispatch, router]);

useEffect(() => {
if (!props.loading && props.currentPerson?.length === 0) {
handleSignInClick();
}
}, []);
}, [handleSignInClick, props.currentPerson?.length, props.loading]);

if (props.loading)
return (
Expand Down
29 changes: 16 additions & 13 deletions components/VideoPlayer/VideoJSPlayer.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import styled, { css } from 'styled-components';
import { Box, Heading } from 'ui-kit';
import { themeGet } from '@styled-system/theme-get';

// eslint-disable-next-line import/prefer-default-export
const usePlayer = ({ src, controls, autoplay, fluid, onError }) => {
const options = {
fill: true,
preload: 'auto',
html5: {
hls: {
enableLowInitialPlaylist: true,
smoothQualityChange: true,
overrideNative: true,
const options = useMemo(
() => ({
fill: true,
preload: 'auto',
html5: {
hls: {
enableLowInitialPlaylist: true,
smoothQualityChange: true,
overrideNative: true,
},
},
},
};
}),
[]
);
const videoRef = useRef(null);
const [player, setPlayer] = useState(null);

Expand All @@ -40,7 +43,7 @@ const usePlayer = ({ src, controls, autoplay, fluid, onError }) => {
}

return () => player?.dispose();
}, [src]);
}, [autoplay, controls, fluid, onError, options, player, src]);
Comment on lines 45 to +46

Choose a reason for hiding this comment

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

P1 Badge Video player disposed on every parent rerender

The useEffect now depends on onError and returns a disposer, so any parent rerender that recreates the handler (e.g., onError={() => setPlayerError(true)} in MediaContentItem) triggers the cleanup, disposes the player, and then re-enters the effect with the stale player reference still truthy. Because no new player is created, the subsequent player.src call runs on a disposed instance, causing playback to stop or throw as soon as the parent component updates state (selecting a carousel item, handling errors, etc.).

Useful? React with 👍 / 👎.


return videoRef;
};
Expand Down
6 changes: 0 additions & 6 deletions components/VideoPlayer/VideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ function VideoPlayer({ src, title, details, stopPropagation, ...props } = {}) {
}
}, [playing, videoRef]);

useEffect(() => {
if (videoRef.current) {
setDuration(videoRef.current.duration || 0);
}
}, [videoRef]);

const VideoControl = playing ? PauseCircle : PlayCircle;
return (
<Box
Expand Down
8 changes: 2 additions & 6 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
const { FlatCompat } = require('@eslint/eslintrc');

const compat = new FlatCompat({
baseDirectory: __dirname,
});
const nextCoreWebVitals = require('eslint-config-next/core-web-vitals');

module.exports = [
{
ignores: ['.next/**'],
},
...compat.extends('next/core-web-vitals'),
...nextCoreWebVitals,
{
rules: {
'@next/next/no-html-link-for-pages': 'warn',
Expand Down
14 changes: 13 additions & 1 deletion lib/apolloClient/apolloClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,23 @@ function createApolloClient() {

const link = ApolloLink.from([errorLink, authLink, httpLink]);

const defaultOptions =
typeof window === 'undefined'
? {
watchQuery: {
ssr: false,
},
}
: undefined;

const client = new ApolloClient({
ssrMode: typeof window === 'undefined',
link,
cache,
version: 'web-1.0.0',
clientAwareness: {
version: 'web-1.0.0',
},
defaultOptions,
});

client.onResetStore(() => cache.writeData({ data: {} }));
Expand Down
16 changes: 15 additions & 1 deletion lib/apolloClient/httpLink.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import fetch from 'cross-fetch';
import { HttpLink } from '@apollo/client';

const SERVER_TIMEOUT_MS = 8000;
const CLIENT_TIMEOUT_MS = 15000;

function fetchWithTimeout(url, options) {
const controller = new AbortController();
const timeoutMs =
typeof window === 'undefined' ? SERVER_TIMEOUT_MS : CLIENT_TIMEOUT_MS;
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

return fetch(url, { ...options, signal: controller.signal }).finally(() =>
clearTimeout(timeoutId)
);
}

const httpLink = new HttpLink({
uri:
process.env.NEXT_PUBLIC_APOLLOS_API || 'https://longhollow-cdn.global.ssl.fastly.net', // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
fetch: (url, options) => {
// Strip excess whitespace to help mitigate query length issue
const compressedUrl = url.replace(/(%20)+/g, '%20');
return fetch(compressedUrl, options).then(async response => {
return fetchWithTimeout(compressedUrl, options).then(async response => {
if (process.env.NODE_ENV !== 'production' && !response.ok) {
let bodyText = null;
try {
Expand Down
Loading