From 8d4af5416fdc4279eea4369b19031d1089eb8d6c Mon Sep 17 00:00:00 2001 From: jtmst Date: Tue, 14 May 2024 16:49:05 -0400 Subject: [PATCH 1/9] config wip --- src/app/{ => [jurisdiction]}/[topic]/page.tsx | 2 +- src/app/[jurisdiction]/page.tsx | 29 +++++++++++++++++++ src/config/formSources.config.js | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) rename src/app/{ => [jurisdiction]}/[topic]/page.tsx (84%) create mode 100644 src/app/[jurisdiction]/page.tsx diff --git a/src/app/[topic]/page.tsx b/src/app/[jurisdiction]/[topic]/page.tsx similarity index 84% rename from src/app/[topic]/page.tsx rename to src/app/[jurisdiction]/[topic]/page.tsx index 0a71aee..4b11808 100644 --- a/src/app/[topic]/page.tsx +++ b/src/app/[jurisdiction]/[topic]/page.tsx @@ -1,5 +1,5 @@ import { GetStaticPathsResult } from 'next'; -import { legalTopics, Topic } from '../../config/topics.config'; +import { legalTopics, Topic } from '../../../config/topics.config'; interface PageProps { params: { diff --git a/src/app/[jurisdiction]/page.tsx b/src/app/[jurisdiction]/page.tsx new file mode 100644 index 0000000..fdd3847 --- /dev/null +++ b/src/app/[jurisdiction]/page.tsx @@ -0,0 +1,29 @@ +import { formSources } from '../../config/formSources.config'; + +interface PageProps { + params: { + source: string; + }; +} + +const Page = ({ params }: PageProps) => { + const { source } = params; + return
LIST OF STUFF FOR {source}:
; +}; + +export default Page; + +// export async function generateStaticParams() { +// return formSources.docassembleServers.map((source) => ({ +// source: source.path.toLowerCase(), +// })); +// } + +export async function generateStaticParams() { + return formSources.docassembleServers.map((source) => { + console.log(source.path); + return { + source: source.path.toLowerCase(), + }; + }); +} diff --git a/src/config/formSources.config.js b/src/config/formSources.config.js index 131f802..1db0d2f 100644 --- a/src/config/formSources.config.js +++ b/src/config/formSources.config.js @@ -4,11 +4,13 @@ export const formSources = { key: 'suffolkListLab', url: 'https://apps.suffolklitlab.org', name: 'Suffolk LIT Lab', + path: 'ma', }, { key: 'greaterBostonLegalService', url: 'https://interviews.gbls.org', name: 'Greater Boston Legal Services', + path: 'gb', }, ], }; From 5a3e613fd5e4db491c53d5da589c4893b18bc69e Mon Sep 17 00:00:00 2001 From: jtmst Date: Wed, 15 May 2024 08:32:19 -0400 Subject: [PATCH 2/9] wip --- src/app/[jurisdiction]/page.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/app/[jurisdiction]/page.tsx b/src/app/[jurisdiction]/page.tsx index fdd3847..df8f0b9 100644 --- a/src/app/[jurisdiction]/page.tsx +++ b/src/app/[jurisdiction]/page.tsx @@ -2,28 +2,20 @@ import { formSources } from '../../config/formSources.config'; interface PageProps { params: { - source: string; + jurisdiction: string; }; } const Page = ({ params }: PageProps) => { - const { source } = params; - return
LIST OF STUFF FOR {source}:
; + const { jurisdiction } = params; + + return
LIST OF STUFF FOR {jurisdiction}:
; }; export default Page; -// export async function generateStaticParams() { -// return formSources.docassembleServers.map((source) => ({ -// source: source.path.toLowerCase(), -// })); -// } - export async function generateStaticParams() { - return formSources.docassembleServers.map((source) => { - console.log(source.path); - return { - source: source.path.toLowerCase(), - }; - }); + return formSources.docassembleServers.map((jurisdiction) => ({ + jurisdiction: jurisdiction.path.toLowerCase(), + })); } From edba6f33a5c9f12453ad777f139bca8c46c840f5 Mon Sep 17 00:00:00 2001 From: jtmst Date: Thu, 16 May 2024 17:23:50 -0400 Subject: [PATCH 3/9] routes --- src/app/[jurisdiction]/[topic]/page.tsx | 5 +++-- src/app/[jurisdiction]/layout.tsx | 11 +++++++++++ src/app/[jurisdiction]/page.tsx | 6 ------ 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 src/app/[jurisdiction]/layout.tsx diff --git a/src/app/[jurisdiction]/[topic]/page.tsx b/src/app/[jurisdiction]/[topic]/page.tsx index 4b11808..82db0d6 100644 --- a/src/app/[jurisdiction]/[topic]/page.tsx +++ b/src/app/[jurisdiction]/[topic]/page.tsx @@ -8,13 +8,14 @@ interface PageProps { } const Page = ({ params }: PageProps) => { - const { topic } = params; + const { topic, jurisdiction } = params; return
Topic: {topic}
; }; export default Page; -export async function generateStaticParams() { +export async function generateStaticParams({ params }) { + const { jurisdiction } = params; return legalTopics.map((topic) => ({ topic: topic.name.toLowerCase(), })); diff --git a/src/app/[jurisdiction]/layout.tsx b/src/app/[jurisdiction]/layout.tsx new file mode 100644 index 0000000..fdcc785 --- /dev/null +++ b/src/app/[jurisdiction]/layout.tsx @@ -0,0 +1,11 @@ +import { formSources } from '../../config/formSources.config'; + +export async function generateStaticParams() { + return formSources.docassembleServers.map((jurisdiction) => ({ + jurisdiction: jurisdiction.path.toLowerCase(), + })); +} + +export default function Layout({ children }) { + return <>{children}; +} diff --git a/src/app/[jurisdiction]/page.tsx b/src/app/[jurisdiction]/page.tsx index df8f0b9..4308560 100644 --- a/src/app/[jurisdiction]/page.tsx +++ b/src/app/[jurisdiction]/page.tsx @@ -13,9 +13,3 @@ const Page = ({ params }: PageProps) => { }; export default Page; - -export async function generateStaticParams() { - return formSources.docassembleServers.map((jurisdiction) => ({ - jurisdiction: jurisdiction.path.toLowerCase(), - })); -} From 27151be5c3935580d8e35b663f111763684837ac Mon Sep 17 00:00:00 2001 From: jtmst Date: Thu, 16 May 2024 18:11:28 -0400 Subject: [PATCH 4/9] config setup --- __tests__/index.test.js | 2 +- prefix.ts | 3 ++ src/app/[jurisdiction]/[topic]/page.tsx | 1 + src/app/[jurisdiction]/page.tsx | 12 +++++-- src/app/components/Footer.tsx | 2 +- src/app/components/NavigationBar.tsx | 7 +++- src/app/components/TopicCard.tsx | 45 +++++++++++++---------- src/app/components/TopicsSection.tsx | 48 +++++++++++++++++++++++++ src/app/page.tsx | 47 +++--------------------- src/data/fetchInterviewData.ts | 14 +++++--- 10 files changed, 110 insertions(+), 71 deletions(-) create mode 100644 prefix.ts create mode 100644 src/app/components/TopicsSection.tsx diff --git a/__tests__/index.test.js b/__tests__/index.test.js index d5b82ee..1c45661 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; -import TopicsPage from '../src/app/page'; +import Page from '../src/app/page'; import RootLayout from '../src/app/layout'; import { fetchInterviews } from '../src/data/fetchInterviewData'; diff --git a/prefix.ts b/prefix.ts new file mode 100644 index 0000000..997d890 --- /dev/null +++ b/prefix.ts @@ -0,0 +1,3 @@ +const prefix = process.env.NEXT_PUBLIC_BASE_PATH || ''; + +export { prefix }; diff --git a/src/app/[jurisdiction]/[topic]/page.tsx b/src/app/[jurisdiction]/[topic]/page.tsx index 82db0d6..5844130 100644 --- a/src/app/[jurisdiction]/[topic]/page.tsx +++ b/src/app/[jurisdiction]/[topic]/page.tsx @@ -4,6 +4,7 @@ import { legalTopics, Topic } from '../../../config/topics.config'; interface PageProps { params: { topic: string; + jurisdiction: string; }; } diff --git a/src/app/[jurisdiction]/page.tsx b/src/app/[jurisdiction]/page.tsx index 4308560..d4fe35a 100644 --- a/src/app/[jurisdiction]/page.tsx +++ b/src/app/[jurisdiction]/page.tsx @@ -1,4 +1,6 @@ -import { formSources } from '../../config/formSources.config'; +import HowItWorksSection from '../components/HowItWorksSection'; +import TopicsSection from '../components/TopicsSection'; +import HeroSection from '../components/HeroSection'; interface PageProps { params: { @@ -9,7 +11,13 @@ interface PageProps { const Page = ({ params }: PageProps) => { const { jurisdiction } = params; - return
LIST OF STUFF FOR {jurisdiction}:
; + return ( +
+ + + +
+ ); }; export default Page; diff --git a/src/app/components/Footer.tsx b/src/app/components/Footer.tsx index 42d4ca0..6367ef1 100644 --- a/src/app/components/Footer.tsx +++ b/src/app/components/Footer.tsx @@ -8,7 +8,7 @@ export default function Footer() {
Suffolk University Law School diff --git a/src/app/components/NavigationBar.tsx b/src/app/components/NavigationBar.tsx index 246f28b..5a8d28e 100644 --- a/src/app/components/NavigationBar.tsx +++ b/src/app/components/NavigationBar.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faLanguage } from '@fortawesome/free-solid-svg-icons'; +import { prefix } from '../../../prefix'; export default function NavigationBar() { return ( @@ -10,7 +11,11 @@ export default function NavigationBar() { >
- Logo + Logo Court Forms Online diff --git a/src/app/components/TopicCard.tsx b/src/app/components/TopicCard.tsx index 7e49165..28f43f3 100644 --- a/src/app/components/TopicCard.tsx +++ b/src/app/components/TopicCard.tsx @@ -12,6 +12,7 @@ interface TopicCardProps { interviews: any[]; index: number; serverUrl: string; + jurisdiction: string; } interface IconProps { @@ -28,7 +29,13 @@ const FontAwesomeIcon = ({ return ; }; -const TopicCard = ({ topic, interviews, index, serverUrl }: TopicCardProps) => { +const TopicCard = ({ + topic, + interviews, + index, + serverUrl, + jurisdiction, +}: TopicCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const visibilityClass = index > 8 ? 'hidden' : ''; @@ -54,13 +61,10 @@ const TopicCard = ({ topic, interviews, index, serverUrl }: TopicCardProps) => { key={topic.codes[0]} > -
e.preventDefault()} - > +
{ className="tag-container" style={{ maxHeight: isExpanded ? '800px' : '200px' }} > - {displayInterviews.map((interview, index) => ( - { - e.preventDefault(); - handleNavigation(serverUrl + interview.link); - }} - > - {interview.metadata.title} - - ))} + {displayInterviews.map((interview, index) => { + if (interview.metadata && interview.metadata.title) { + return ( + { + e.preventDefault(); + handleNavigation(serverUrl + interview.link); + }} + > + {interview.metadata.title} + + ); + } + return null; + })}
{interviews.length > 3 && (
diff --git a/src/app/components/TopicsSection.tsx b/src/app/components/TopicsSection.tsx new file mode 100644 index 0000000..9b92aa1 --- /dev/null +++ b/src/app/components/TopicsSection.tsx @@ -0,0 +1,48 @@ +import { legalTopics } from '../../config/topics.config'; +import { formSources } from '../../config/formSources.config'; +import { fetchInterviews } from '../../data/fetchInterviewData'; +import TopicCard from './TopicCard'; +import ShowAllToggle from './ShowAllToggle'; + +const TopicsSection = async ({ jurisdiction }) => { + const { interviewsByTopic, isError } = await fetchInterviews(jurisdiction); + + if (isError) { + return
Error fetching data.
; + } + + const server = + formSources.docassembleServers.find( + (server) => server.path === jurisdiction + ) || formSources.docassembleServers[0]; + const serverUrl = server.url; + + const filteredTopics = legalTopics + .sort((a, b) => b.priority - a.priority) + .filter( + (topic) => + topic.always_visible || interviewsByTopic[topic.name].length > 0 + ); + + return ( +
+
+

Browse court forms by category

+
+ {filteredTopics.map((topic) => ( + + ))} +
+ {filteredTopics.length > 9 && } +
+
+ ); +}; + +export default TopicsSection; diff --git a/src/app/page.tsx b/src/app/page.tsx index 9ac97ae..a59038c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,52 +1,13 @@ -import HeroSection from './components/HeroSection'; import HowItWorksSection from './components/HowItWorksSection'; -import { fetchInterviews } from '../data/fetchInterviewData'; -import TopicCard from './components/TopicCard'; -import { legalTopics } from '../config/topics.config'; -import ShowAllToggle from './components/ShowAllToggle'; -import { formSources } from '../config/formSources.config'; - -export default async function TopicsPage() { - const interviewsResult = await fetchInterviews(); - const { interviewsByTopic } = interviewsResult; - - // Modify this to account for various jurisdictions - const serverKey = 'suffolkListLab'; - const server = formSources.docassembleServers.find( - (server) => server.key === serverKey - ); - const url = server ? server.url : 'https://apps.suffolklitlab.org'; - - const filteredTopics = legalTopics - .sort((a, b) => b.priority - a.priority) - .filter( - (topic, index) => - topic.always_visible || - (interviewsByTopic[topic.name] && - interviewsByTopic[topic.name].length > 0) - ); +import TopicsSection from './components/TopicsSection'; +import HeroSection from './components/HeroSection'; +export default async function Page() { return (
-
-
-

Browse court forms by category

-
- {filteredTopics.map((topic, index) => ( - - ))} -
- {filteredTopics.length > 9 && } -
-
+
); } diff --git a/src/data/fetchInterviewData.ts b/src/data/fetchInterviewData.ts index 2232d49..921a5ee 100644 --- a/src/data/fetchInterviewData.ts +++ b/src/data/fetchInterviewData.ts @@ -2,8 +2,12 @@ import { formSources } from '../config/formSources.config'; import { legalTopics } from '../config/topics.config'; import { findClosestTopic } from './helpers'; -export const fetchInterviews = async () => { - const serverUrl = formSources.docassembleServers[0].url; +export const fetchInterviews = async (jurisdiction) => { + const server = + formSources.docassembleServers.find( + (server) => server.path === jurisdiction + ) || formSources.docassembleServers[0]; + const serverUrl = server.url; const url = new URL(`${serverUrl}/list`); url.search = 'json=1'; @@ -22,14 +26,14 @@ export const fetchInterviews = async () => { data.interviews.forEach((interview) => { const uniqueTopics = new Set(); - // match topics to config by metadata.LIST_Topic, and tags values + // Match topics to config by metadata.LIST_topics and tags values (interview.metadata.LIST_topics || []) .concat(interview.tags || []) .forEach((code) => { const topic = findClosestTopic(code, legalTopics); if (topic && !uniqueTopics.has(topic.name)) { uniqueTopics.add(topic.name); - // check if the title already exists in the topic to avoid duplicated titles + // Avoid duplicated titles within the same topic if (!titlesInTopics[topic.name].has(interview.title)) { interviewsByTopic[topic.name].push(interview); titlesInTopics[topic.name].add(interview.title); @@ -37,7 +41,7 @@ export const fetchInterviews = async () => { } }); - // add to other if no matching topic in config + // Add to 'Other' if no matching topic found if (uniqueTopics.size === 0) { interviewsByTopic['Other'] = interviewsByTopic['Other'] || []; if (!titlesInTopics['Other'].has(interview.title)) { From 7593249cba7e73dec4e3da613633d2763d02c005 Mon Sep 17 00:00:00 2001 From: jtmst Date: Thu, 16 May 2024 18:14:09 -0400 Subject: [PATCH 5/9] type fix --- src/app/components/TopicsSection.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components/TopicsSection.tsx b/src/app/components/TopicsSection.tsx index 9b92aa1..448b7ae 100644 --- a/src/app/components/TopicsSection.tsx +++ b/src/app/components/TopicsSection.tsx @@ -29,13 +29,14 @@ const TopicsSection = async ({ jurisdiction }) => {

Browse court forms by category

- {filteredTopics.map((topic) => ( + {filteredTopics.map((topic, index) => ( ))}
From 17c3e2f3fa8830c90fabe2da9e07be181b5fca42 Mon Sep 17 00:00:00 2001 From: jtmst Date: Thu, 16 May 2024 18:35:49 -0400 Subject: [PATCH 6/9] prettier --- src/app/components/Footer.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/Footer.tsx b/src/app/components/Footer.tsx index 68c45e5..e1478b6 100644 --- a/src/app/components/Footer.tsx +++ b/src/app/components/Footer.tsx @@ -1,6 +1,6 @@ import Link from 'next/link'; -import Image from 'next/image' -import { prefix } from '../../../prefix' +import Image from 'next/image'; +import { prefix } from '../../../prefix'; export default function Footer() { return ( @@ -9,7 +9,7 @@ export default function Footer() {
- Suffolk University Law School Date: Mon, 20 May 2024 15:07:34 -0400 Subject: [PATCH 7/9] modified fetch functions and server config for paths --- src/app/[jurisdiction]/layout.tsx | 11 -- .../[topic]/page.tsx | 6 +- src/app/[path]/layout.tsx | 13 +++ src/app/{[jurisdiction] => [path]}/page.tsx | 10 +- src/app/components/TopicCard.tsx | 9 +- src/app/components/TopicsSection.tsx | 12 +- src/app/globals.css | 8 +- src/app/page.tsx | 5 +- src/config/formSources.config.js | 15 ++- src/data/fetchInterviewData.ts | 104 +++++++++--------- 10 files changed, 102 insertions(+), 91 deletions(-) delete mode 100644 src/app/[jurisdiction]/layout.tsx rename src/app/{[jurisdiction] => [path]}/[topic]/page.tsx (80%) create mode 100644 src/app/[path]/layout.tsx rename src/app/{[jurisdiction] => [path]}/page.tsx (52%) diff --git a/src/app/[jurisdiction]/layout.tsx b/src/app/[jurisdiction]/layout.tsx deleted file mode 100644 index fdcc785..0000000 --- a/src/app/[jurisdiction]/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { formSources } from '../../config/formSources.config'; - -export async function generateStaticParams() { - return formSources.docassembleServers.map((jurisdiction) => ({ - jurisdiction: jurisdiction.path.toLowerCase(), - })); -} - -export default function Layout({ children }) { - return <>{children}; -} diff --git a/src/app/[jurisdiction]/[topic]/page.tsx b/src/app/[path]/[topic]/page.tsx similarity index 80% rename from src/app/[jurisdiction]/[topic]/page.tsx rename to src/app/[path]/[topic]/page.tsx index 5844130..aa5b935 100644 --- a/src/app/[jurisdiction]/[topic]/page.tsx +++ b/src/app/[path]/[topic]/page.tsx @@ -4,19 +4,19 @@ import { legalTopics, Topic } from '../../../config/topics.config'; interface PageProps { params: { topic: string; - jurisdiction: string; + path: string; }; } const Page = ({ params }: PageProps) => { - const { topic, jurisdiction } = params; + const { topic, path } = params; return
Topic: {topic}
; }; export default Page; export async function generateStaticParams({ params }) { - const { jurisdiction } = params; + const { path } = params; return legalTopics.map((topic) => ({ topic: topic.name.toLowerCase(), })); diff --git a/src/app/[path]/layout.tsx b/src/app/[path]/layout.tsx new file mode 100644 index 0000000..d100b4b --- /dev/null +++ b/src/app/[path]/layout.tsx @@ -0,0 +1,13 @@ +import { pathToServerConfig } from '../../config/formSources.config'; + +// This is only necessary because there is a bug within next that doesnt allow static params to be passed from page to page properly. This layout only exists to assist in passing said props + +export async function generateStaticParams() { + return Object.keys(pathToServerConfig).map(key => ({ + path: key.toLowerCase() + })); +} + +export default function Layout({ children }) { + return <>{children}; +} \ No newline at end of file diff --git a/src/app/[jurisdiction]/page.tsx b/src/app/[path]/page.tsx similarity index 52% rename from src/app/[jurisdiction]/page.tsx rename to src/app/[path]/page.tsx index d4fe35a..42f0954 100644 --- a/src/app/[jurisdiction]/page.tsx +++ b/src/app/[path]/page.tsx @@ -1,21 +1,23 @@ import HowItWorksSection from '../components/HowItWorksSection'; import TopicsSection from '../components/TopicsSection'; import HeroSection from '../components/HeroSection'; +import { fetchInterviews } from '../../data/fetchInterviewData'; interface PageProps { params: { - jurisdiction: string; + path: string; }; } -const Page = ({ params }: PageProps) => { - const { jurisdiction } = params; + const Page = async ({ params }: PageProps) => { + const { path } = params; + const { interviewsByTopic, isError } = await fetchInterviews(path); return (
- +
); }; diff --git a/src/app/components/TopicCard.tsx b/src/app/components/TopicCard.tsx index 28f43f3..0a3e3c6 100644 --- a/src/app/components/TopicCard.tsx +++ b/src/app/components/TopicCard.tsx @@ -12,7 +12,7 @@ interface TopicCardProps { interviews: any[]; index: number; serverUrl: string; - jurisdiction: string; + path: string; } interface IconProps { @@ -34,11 +34,10 @@ const TopicCard = ({ interviews, index, serverUrl, - jurisdiction, + path, }: TopicCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const visibilityClass = index > 8 ? 'hidden' : ''; - const displayInterviews = isExpanded ? interviews.slice(0, Math.min(10, interviews.length)) : interviews.slice(0, 3); @@ -61,7 +60,7 @@ const TopicCard = ({ key={topic.codes[0]} >
@@ -87,7 +86,7 @@ const TopicCard = ({ className="form-tag text-decoration-none" onClick={(e) => { e.preventDefault(); - handleNavigation(serverUrl + interview.link); + handleNavigation(interview.serverUrl + interview.link); }} > {interview.metadata.title} diff --git a/src/app/components/TopicsSection.tsx b/src/app/components/TopicsSection.tsx index 448b7ae..72f3351 100644 --- a/src/app/components/TopicsSection.tsx +++ b/src/app/components/TopicsSection.tsx @@ -1,11 +1,9 @@ import { legalTopics } from '../../config/topics.config'; import { formSources } from '../../config/formSources.config'; -import { fetchInterviews } from '../../data/fetchInterviewData'; import TopicCard from './TopicCard'; import ShowAllToggle from './ShowAllToggle'; -const TopicsSection = async ({ jurisdiction }) => { - const { interviewsByTopic, isError } = await fetchInterviews(jurisdiction); +const TopicsSection = async ({ path, interviews, isError }) => { if (isError) { return
Error fetching data.
; @@ -13,7 +11,7 @@ const TopicsSection = async ({ jurisdiction }) => { const server = formSources.docassembleServers.find( - (server) => server.path === jurisdiction + (server) => server.path === path ) || formSources.docassembleServers[0]; const serverUrl = server.url; @@ -21,7 +19,7 @@ const TopicsSection = async ({ jurisdiction }) => { .sort((a, b) => b.priority - a.priority) .filter( (topic) => - topic.always_visible || interviewsByTopic[topic.name].length > 0 + topic.always_visible || interviews[topic.name].length > 0 ); return ( @@ -33,8 +31,8 @@ const TopicsSection = async ({ jurisdiction }) => { diff --git a/src/app/globals.css b/src/app/globals.css index eebb154..7f92f9b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -192,13 +192,7 @@ h5, font-weight: 600; } -h1, -h2, -h3, -h4, -h5 { - margin-top: 2em; -} + body { font-family: 'Inter'; diff --git a/src/app/page.tsx b/src/app/page.tsx index a59038c..1cf49d2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,13 +1,16 @@ import HowItWorksSection from './components/HowItWorksSection'; import TopicsSection from './components/TopicsSection'; import HeroSection from './components/HeroSection'; +import { fetchInterviews } from '../data/fetchInterviewData'; export default async function Page() { + const { interviewsByTopic, isError } = await fetchInterviews('ma'); + return (
- +
); } diff --git a/src/config/formSources.config.js b/src/config/formSources.config.js index 1db0d2f..d229c5b 100644 --- a/src/config/formSources.config.js +++ b/src/config/formSources.config.js @@ -1,16 +1,27 @@ +export const pathToServerConfig = { + ma: { + path: 'ma', + servers: ['Suffolk LIT Lab', 'Greater Boston Legal Services'] + }, + gb: { + path: 'gb', + servers: ['Greater Boston Legal Services'] + } +}; + export const formSources = { docassembleServers: [ { key: 'suffolkListLab', url: 'https://apps.suffolklitlab.org', name: 'Suffolk LIT Lab', - path: 'ma', + }, { key: 'greaterBostonLegalService', url: 'https://interviews.gbls.org', name: 'Greater Boston Legal Services', - path: 'gb', + }, ], }; diff --git a/src/data/fetchInterviewData.ts b/src/data/fetchInterviewData.ts index 921a5ee..5067997 100644 --- a/src/data/fetchInterviewData.ts +++ b/src/data/fetchInterviewData.ts @@ -1,60 +1,62 @@ -import { formSources } from '../config/formSources.config'; +import { formSources, pathToServerConfig } from '../config/formSources.config'; import { legalTopics } from '../config/topics.config'; import { findClosestTopic } from './helpers'; -export const fetchInterviews = async (jurisdiction) => { - const server = - formSources.docassembleServers.find( - (server) => server.path === jurisdiction - ) || formSources.docassembleServers[0]; - const serverUrl = server.url; - const url = new URL(`${serverUrl}/list`); - url.search = 'json=1'; - - try { - const response = await fetch(url.toString()); - const data = await response.json(); - const interviewsByTopic = {}; - const titlesInTopics = {}; - - legalTopics.forEach((topic) => { - interviewsByTopic[topic.name] = []; - titlesInTopics[topic.name] = new Set(); - }); - - if (data && data.interviews) { - data.interviews.forEach((interview) => { - const uniqueTopics = new Set(); - - // Match topics to config by metadata.LIST_topics and tags values - (interview.metadata.LIST_topics || []) - .concat(interview.tags || []) - .forEach((code) => { - const topic = findClosestTopic(code, legalTopics); - if (topic && !uniqueTopics.has(topic.name)) { - uniqueTopics.add(topic.name); - // Avoid duplicated titles within the same topic - if (!titlesInTopics[topic.name].has(interview.title)) { - interviewsByTopic[topic.name].push(interview); - titlesInTopics[topic.name].add(interview.title); - } - } - }); - - // Add to 'Other' if no matching topic found - if (uniqueTopics.size === 0) { - interviewsByTopic['Other'] = interviewsByTopic['Other'] || []; - if (!titlesInTopics['Other'].has(interview.title)) { - interviewsByTopic['Other'].push(interview); - titlesInTopics['Other'].add(interview.title); +export const fetchInterviews = async (path) => { + const config = pathToServerConfig[path]; + const serverNames = config ? config.servers : [formSources.docassembleServers[0].name]; + const servers = formSources.docassembleServers.filter(server => serverNames.includes(server.name)); + + let allInterviews = []; + for (const server of servers) { + const url = new URL(`${server.url}/list`); + url.search = 'json=1'; + + try { + const response = await fetch(url.toString()); + const data = await response.json(); + if (data && data.interviews) { + const taggedInterviews = data.interviews.map(interview => ({ + ...interview, + serverUrl: server.url + })); + allInterviews = allInterviews.concat(taggedInterviews); + } + } catch (error) { + console.error('Failed to fetch interviews from server:', server.name, error); + } + } + + const interviewsByTopic = {}; + const titlesInTopics = {}; + legalTopics.forEach((topic) => { + interviewsByTopic[topic.name] = []; + titlesInTopics[topic.name] = new Set(); + }); + + allInterviews.forEach((interview) => { + const uniqueTopics = new Set(); + + // Match topics by metadata.LIST_topics and tags + (interview.metadata.LIST_topics || []) + .concat(interview.tags || []) + .forEach((code) => { + const topic = findClosestTopic(code, legalTopics); + if (topic && !uniqueTopics.has(topic.name)) { + uniqueTopics.add(topic.name); + if (!titlesInTopics[topic.name].has(interview.title)) { + interviewsByTopic[topic.name].push(interview); + titlesInTopics[topic.name].add(interview.title); } } }); + + if (uniqueTopics.size === 0 && !titlesInTopics['Other'].has(interview.title)) { + interviewsByTopic['Other'] = interviewsByTopic['Other'] || []; + interviewsByTopic['Other'].push(interview); + titlesInTopics['Other'].add(interview.title); } + }); - return { interviewsByTopic, isError: false }; - } catch (error) { - console.error('Failed to fetch interviews:', error); - return { interviewsByTopic: {}, isError: true }; - } + return { interviewsByTopic, isError: false }; }; From 3cb1c2079efed9ec31425c45e374ed985d0693c8 Mon Sep 17 00:00:00 2001 From: jtmst Date: Mon, 20 May 2024 15:09:31 -0400 Subject: [PATCH 8/9] lint --- src/app/[path]/layout.tsx | 6 +++--- src/app/[path]/page.tsx | 8 ++++++-- src/app/components/TopicsSection.tsx | 9 +++------ src/app/globals.css | 2 -- src/app/page.tsx | 6 +++++- src/config/formSources.config.js | 12 +++++------- src/data/fetchInterviewData.ts | 23 +++++++++++++++++------ 7 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/app/[path]/layout.tsx b/src/app/[path]/layout.tsx index d100b4b..8271883 100644 --- a/src/app/[path]/layout.tsx +++ b/src/app/[path]/layout.tsx @@ -3,11 +3,11 @@ import { pathToServerConfig } from '../../config/formSources.config'; // This is only necessary because there is a bug within next that doesnt allow static params to be passed from page to page properly. This layout only exists to assist in passing said props export async function generateStaticParams() { - return Object.keys(pathToServerConfig).map(key => ({ - path: key.toLowerCase() + return Object.keys(pathToServerConfig).map((key) => ({ + path: key.toLowerCase(), })); } export default function Layout({ children }) { return <>{children}; -} \ No newline at end of file +} diff --git a/src/app/[path]/page.tsx b/src/app/[path]/page.tsx index 42f0954..f206c44 100644 --- a/src/app/[path]/page.tsx +++ b/src/app/[path]/page.tsx @@ -9,7 +9,7 @@ interface PageProps { }; } - const Page = async ({ params }: PageProps) => { +const Page = async ({ params }: PageProps) => { const { path } = params; const { interviewsByTopic, isError } = await fetchInterviews(path); @@ -17,7 +17,11 @@ interface PageProps {
- +
); }; diff --git a/src/app/components/TopicsSection.tsx b/src/app/components/TopicsSection.tsx index 72f3351..522d83f 100644 --- a/src/app/components/TopicsSection.tsx +++ b/src/app/components/TopicsSection.tsx @@ -4,22 +4,19 @@ import TopicCard from './TopicCard'; import ShowAllToggle from './ShowAllToggle'; const TopicsSection = async ({ path, interviews, isError }) => { - if (isError) { return
Error fetching data.
; } const server = - formSources.docassembleServers.find( - (server) => server.path === path - ) || formSources.docassembleServers[0]; + formSources.docassembleServers.find((server) => server.path === path) || + formSources.docassembleServers[0]; const serverUrl = server.url; const filteredTopics = legalTopics .sort((a, b) => b.priority - a.priority) .filter( - (topic) => - topic.always_visible || interviews[topic.name].length > 0 + (topic) => topic.always_visible || interviews[topic.name].length > 0 ); return ( diff --git a/src/app/globals.css b/src/app/globals.css index 7f92f9b..1668a8b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -192,8 +192,6 @@ h5, font-weight: 600; } - - body { font-family: 'Inter'; } diff --git a/src/app/page.tsx b/src/app/page.tsx index 1cf49d2..a8ab847 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,7 +10,11 @@ export default async function Page() {
- +
); } diff --git a/src/config/formSources.config.js b/src/config/formSources.config.js index d229c5b..5e68617 100644 --- a/src/config/formSources.config.js +++ b/src/config/formSources.config.js @@ -1,12 +1,12 @@ export const pathToServerConfig = { - ma: { + ma: { path: 'ma', - servers: ['Suffolk LIT Lab', 'Greater Boston Legal Services'] + servers: ['Suffolk LIT Lab', 'Greater Boston Legal Services'], }, - gb: { + gb: { path: 'gb', - servers: ['Greater Boston Legal Services'] - } + servers: ['Greater Boston Legal Services'], + }, }; export const formSources = { @@ -15,13 +15,11 @@ export const formSources = { key: 'suffolkListLab', url: 'https://apps.suffolklitlab.org', name: 'Suffolk LIT Lab', - }, { key: 'greaterBostonLegalService', url: 'https://interviews.gbls.org', name: 'Greater Boston Legal Services', - }, ], }; diff --git a/src/data/fetchInterviewData.ts b/src/data/fetchInterviewData.ts index 5067997..3b3a4ba 100644 --- a/src/data/fetchInterviewData.ts +++ b/src/data/fetchInterviewData.ts @@ -4,8 +4,12 @@ import { findClosestTopic } from './helpers'; export const fetchInterviews = async (path) => { const config = pathToServerConfig[path]; - const serverNames = config ? config.servers : [formSources.docassembleServers[0].name]; - const servers = formSources.docassembleServers.filter(server => serverNames.includes(server.name)); + const serverNames = config + ? config.servers + : [formSources.docassembleServers[0].name]; + const servers = formSources.docassembleServers.filter((server) => + serverNames.includes(server.name) + ); let allInterviews = []; for (const server of servers) { @@ -16,14 +20,18 @@ export const fetchInterviews = async (path) => { const response = await fetch(url.toString()); const data = await response.json(); if (data && data.interviews) { - const taggedInterviews = data.interviews.map(interview => ({ + const taggedInterviews = data.interviews.map((interview) => ({ ...interview, - serverUrl: server.url + serverUrl: server.url, })); allInterviews = allInterviews.concat(taggedInterviews); } } catch (error) { - console.error('Failed to fetch interviews from server:', server.name, error); + console.error( + 'Failed to fetch interviews from server:', + server.name, + error + ); } } @@ -51,7 +59,10 @@ export const fetchInterviews = async (path) => { } }); - if (uniqueTopics.size === 0 && !titlesInTopics['Other'].has(interview.title)) { + if ( + uniqueTopics.size === 0 && + !titlesInTopics['Other'].has(interview.title) + ) { interviewsByTopic['Other'] = interviewsByTopic['Other'] || []; interviewsByTopic['Other'].push(interview); titlesInTopics['Other'].add(interview.title); From f7be4cee69df69f3e46b464ae036d194fab5a561 Mon Sep 17 00:00:00 2001 From: jtmst Date: Mon, 20 May 2024 15:41:53 -0400 Subject: [PATCH 9/9] test --- src/data/helpers/index.test.ts | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/data/helpers/index.test.ts diff --git a/src/data/helpers/index.test.ts b/src/data/helpers/index.test.ts new file mode 100644 index 0000000..cd1f399 --- /dev/null +++ b/src/data/helpers/index.test.ts @@ -0,0 +1,42 @@ +import { findClosestTopic } from './'; + +describe('findClosestTopic', () => { + const legalTopics = [ + { + name: 'Housing', + codes: ['HO-01-00-00-00', 'HO-02-00-00-00'], + }, + { + name: 'Family', + codes: ['FA-00-00-00-00', 'FA-01-00-00-00'], + }, + { + name: 'Employment', + codes: ['EM-01-00-00-00'], + }, + ]; + + it('should return the exact matching topic', () => { + const topicCode = 'HO-02-00-00-00'; + const result = findClosestTopic(topicCode, legalTopics); + expect(result.name).toBe('Housing'); + }); + + it('should return the closest smaller match if exact match is not available', () => { + const topicCode = 'HO-01-50-00-00'; + const result = findClosestTopic(topicCode, legalTopics); + expect(result.name).toBe('Housing'); + }); + + it('should return undefined if no prefix matches', () => { + const topicCode = 'XX-01-00-00-00'; + const result = findClosestTopic(topicCode, legalTopics); + expect(result).toBeUndefined(); + }); + + it('handles cases with leading zeros in topic codes', () => { + const topicCode = 'FA-01-00-00-00'; + const result = findClosestTopic(topicCode, legalTopics); + expect(result.name).toBe('Family'); + }); +});