-
Notifications
You must be signed in to change notification settings - Fork 19
Allow customization on homepage and footer #922
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
base: develop
Are you sure you want to change the base?
Allow customization on homepage and footer #922
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request makes the homepage and footer customizable by introducing runtime configuration options for organization-specific branding. Previously, the application had hard-coded references to "Harvard Dataverse" and Harvard-specific URLs, making it unsuitable for deployment by other organizations. The changes introduce three new optional configuration sections (branding, homepage, and footer) that allow organizations to customize the dataverse name, support URL, copyright holder, and privacy policy URL without rebuilding the application.
Changes:
- Added runtime configuration schema for branding, homepage, and footer customization in
src/config.ts - Updated Footer component to use configurable copyright holder and privacy policy URL
- Updated Usage component to use configurable dataverse name and support URL
- Modified translation strings to use interpolation for dynamic branding values
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/config.ts | Added zod schema definitions for optional branding, homepage, and footer configuration objects |
| src/sections/layout/footer/Footer.tsx | Updated to use configurable copyrightHolder and privacyPolicyUrl from app config with proper fallbacks and optional chaining |
| src/sections/homepage/usage/Usage.tsx | Modified to use configurable dataverseName and supportUrl from app config (has critical issues with null safety) |
| public/config.js | Added default configuration values for branding, homepage, and footer sections with Harvard-specific defaults |
| cypress.config.ts | Added test configuration values for branding, homepage, and footer sections |
| tests/support/bootstrapAppConfig.ts | Extended test config builder to support new branding, homepage, and footer configuration |
| tests/component/sections/layout/footer/Footer.spec.tsx | Added test coverage for privacy policy link and updated repository interface to match expanded API |
| public/locales/en/homepage.json | Changed hard-coded "Harvard Dataverse" strings to use {{dataverseName}} interpolation |
| public/locales/es/homepage.json | Changed hard-coded "Harvard Dataverse" strings to use {{dataverseName}} interpolation |
| public/locales/en/footer.json | Updated copyright translation to use {{copyrightHolder}} interpolation |
| public/locales/es/footer.json | Updated copyright translation to use {{copyrightHolder}} interpolation |
| CHANGELOG.md | Added entry documenting the new runtime configuration options |
Comments suppressed due to low confidence (1)
src/sections/homepage/usage/Usage.tsx:89
- The Usage component now uses configurable branding values (dataverseName, supportUrl) but lacks test coverage. Other homepage components like Metrics and SearchInput have dedicated test files in tests/component/sections/homepage/. Consider adding tests to verify that: 1) the dataverseName interpolation works correctly in the translation strings, 2) the supportUrl is properly used in the link, 3) fallback behavior works when branding config is not provided, and 4) the component handles undefined branding/homepage config objects gracefully.
export const Usage = ({ collectionId }: UsageProps) => {
const { t } = useTranslation('homepage', { keyPrefix: 'usage' })
const appConfig = requireAppConfig() as AppConfig
const dataverseName = appConfig.branding.dataverseName ?? 'Dataverse'
const supportUrl = appConfig.homepage.supportUrl
return (
<Row>
<Col xs={12} lg={4} className={styles.column_card}>
<Card className={styles.card}>
<Card.Body className={styles.card_body}>
<h5>{t('datasets.title')}</h5>
<p className="small text-muted">{t('datasets.content')}</p>
<footer className={styles.footer_wrapper}>
<Link
to={RouteWithParams.CREATE_DATASET(collectionId)}
className="btn btn-secondary btn-sm">
<Stack direction="horizontal" gap={1}>
<span className={styles.cta_link_text}>{t('datasets.text_button')}</span>{' '}
<Plus size={22} />
</Stack>
</Link>
</footer>
</Card.Body>
</Card>
</Col>
<Col xs={12} lg={4} className={styles.column_card}>
<Card className={styles.card}>
<Card.Body className={styles.card_body}>
<h5>{t('collections.title')}</h5>
<p className="small text-muted">{t('collections.content')}</p>
<footer className={styles.footer_wrapper}>
<Link
to={RouteWithParams.CREATE_COLLECTION(collectionId)}
className="btn btn-secondary btn-sm">
<Stack direction="horizontal" gap={1}>
<span className={styles.cta_link_text}>{t('collections.text_button')}</span>{' '}
<Plus size={22} />
</Stack>
</Link>
</footer>
</Card.Body>
</Card>
</Col>
<Col xs={12} lg={4} className={styles.column_card}>
<Card className={styles.card}>
<Card.Body className={styles.card_body}>
<h5>{t('general.title', { dataverseName })}</h5>
<p className="small text-muted">{t('general.content')}</p>
<footer className={styles.footer_wrapper}>
<a
href={supportUrl}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary btn-sm">
<Stack direction="horizontal" gap={2}>
<span className={styles.cta_link_text}>{t('general.text_button')}</span>
<BoxArrowUpRight size={14} />
</Stack>
</a>
</footer>
</Card.Body>
</Card>
</Col>
</Row>
)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <a | ||
| href="https://support.dataverse.harvard.edu/" | ||
| href={supportUrl} |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No fallback value is provided for 'supportUrl'. If 'homepage.supportUrl' is not configured, this will be undefined, and the anchor tag will have an undefined href attribute, which could lead to broken links. Consider providing a fallback value like 'https://dataverse.org/' or handling the case where supportUrl is undefined similar to how privacyPolicyUrl is handled in the Footer component (by conditionally rendering the link only when the URL is available).
| // Optional branding values for homepage/footer text | ||
| branding: { | ||
| // Used in homepage strings such as "{{dataverseName}} is a repository..." | ||
| dataverseName: 'Harvard Dataverse' | ||
| }, | ||
| homepage: { | ||
| supportUrl: 'https://support.dataverse.harvard.edu/' | ||
| }, | ||
| footer: { | ||
| copyrightHolder: 'The President & Fellows of Harvard College', | ||
| privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy' | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default configuration still contains Harvard-specific values (dataverseName: 'Harvard Dataverse', copyrightHolder: 'The President & Fellows of Harvard College', and URLs pointing to support.dataverse.harvard.edu). According to the PR description and issue #791, the goal is to remove Harvard-specific content to make the application deployable for other organizations. While making these values configurable is a good step, the default values should probably be generic (e.g., 'Dataverse' instead of 'Harvard Dataverse') to avoid organizations accidentally deploying with Harvard branding. Organizations can then explicitly configure Harvard branding if needed, rather than having to remember to change it.
| import { requireAppConfig } from '@/config' | ||
| import styles from './Usage.module.scss' | ||
|
|
||
| interface UsageProps { | ||
| collectionId: string | ||
| } | ||
|
|
||
| interface AppConfig { | ||
| branding: { | ||
| dataverseName: string | ||
| } | ||
| homepage: { | ||
| supportUrl: string | ||
| } | ||
| } | ||
|
|
||
| export const Usage = ({ collectionId }: UsageProps) => { | ||
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | ||
| const appConfig = requireAppConfig() as AppConfig | ||
| const dataverseName = appConfig.branding.dataverseName ?? 'Dataverse' | ||
| const supportUrl = appConfig.homepage.supportUrl |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A local AppConfig interface is defined here, but the actual AppConfig type is already imported from '@/config' and should be used instead. This creates unnecessary duplication and could lead to inconsistencies if the config schema changes. The local interface also incorrectly makes 'branding.dataverseName' and 'homepage.supportUrl' required fields when they are actually optional in the schema (defined as z.string().optional() and z.url().optional() in src/config.ts).
| import { requireAppConfig } from '@/config' | |
| import styles from './Usage.module.scss' | |
| interface UsageProps { | |
| collectionId: string | |
| } | |
| interface AppConfig { | |
| branding: { | |
| dataverseName: string | |
| } | |
| homepage: { | |
| supportUrl: string | |
| } | |
| } | |
| export const Usage = ({ collectionId }: UsageProps) => { | |
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | |
| const appConfig = requireAppConfig() as AppConfig | |
| const dataverseName = appConfig.branding.dataverseName ?? 'Dataverse' | |
| const supportUrl = appConfig.homepage.supportUrl | |
| import { requireAppConfig, type AppConfig } from '@/config' | |
| import styles from './Usage.module.scss' | |
| interface UsageProps { | |
| collectionId: string | |
| } | |
| export const Usage = ({ collectionId }: UsageProps) => { | |
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | |
| const appConfig = requireAppConfig() as AppConfig | |
| const dataverseName = appConfig.branding.dataverseName ?? 'Dataverse' | |
| const supportUrl = appConfig.homepage.supportUrl ?? '#' |
| interface AppConfig { | ||
| branding: { | ||
| dataverseName: string | ||
| } | ||
| homepage: { | ||
| supportUrl: string | ||
| } | ||
| } | ||
|
|
||
| export const Usage = ({ collectionId }: UsageProps) => { | ||
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | ||
| const appConfig = requireAppConfig() as AppConfig |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type assertion 'as AppConfig' is used here, but it's casting to the local AppConfig interface rather than the imported type. Since 'requireAppConfig()' already returns the correct AppConfig type from '@/config', this cast is unnecessary and potentially incorrect if the schema differs from the local interface definition.
| interface AppConfig { | |
| branding: { | |
| dataverseName: string | |
| } | |
| homepage: { | |
| supportUrl: string | |
| } | |
| } | |
| export const Usage = ({ collectionId }: UsageProps) => { | |
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | |
| const appConfig = requireAppConfig() as AppConfig | |
| export const Usage = ({ collectionId }: UsageProps) => { | |
| const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) | |
| const appConfig = requireAppConfig() |
| const dataverseName = appConfig.branding.dataverseName ?? 'Dataverse' | ||
| const supportUrl = appConfig.homepage.supportUrl |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null safety check for 'appConfig.branding' and 'appConfig.homepage'. Since these fields are optional in the schema (lines 27-36 and 32-36 in src/config.ts), they could be undefined. While 'dataverseName' has a fallback with '??', accessing 'appConfig.branding.dataverseName' and 'appConfig.homepage.supportUrl' will throw a runtime error if 'branding' or 'homepage' are not configured. Use optional chaining: 'appConfig.branding?.dataverseName' and 'appConfig.homepage?.supportUrl'.
What this PR does / why we need it:
The homepage displays 'Harvard’ as name of the archive and has links to the Harvard support page.
Which issue(s) this PR closes:
Special notes for your reviewer:
Suggestions on how to test this:
Check if there are any "Harvard" related text on the page
Does this PR introduce a user interface change? If mockups are available, please link/include them here:
Is there a release notes or changelog update needed for this change?:
Yes
Additional documentation: