Skip to content

Commit

Permalink
data initial render
Browse files Browse the repository at this point in the history
  • Loading branch information
jtmst committed Apr 25, 2024
1 parent 04083cc commit 26f1470
Show file tree
Hide file tree
Showing 167 changed files with 5,556 additions and 5,054 deletions.
7,824 changes: 4,245 additions & 3,579 deletions .pnp.cjs

Large diffs are not rendered by default.

1,249 changes: 878 additions & 371 deletions .pnp.loader.mjs

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint"]
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
},
"eslint.nodePath": ".yarn/sdks",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/has-npm-1.0.3-b7f00631c1-a449f3185b.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/open-npm-9.1.0-d104a17ec5-b45bcc7a67.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion .yarnrc.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
yarnPath: ".yarn/releases/yarn-3.6.1.cjs"
yarnPath: '.yarn/releases/yarn-3.6.1.cjs'
6 changes: 0 additions & 6 deletions formSources.config.js

This file was deleted.

9 changes: 9 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const nextConfig = {
images: {
unoptimized: true,
},
async rewrites() {
return [
{
// This will match anything after /api/ and proxy it to your backend server
source: '/api/:path*', // Matches all API requests
destination: 'https://apps.suffolklitlab.org/:path*', // Proxy to Backend, maintaining the path and query
},
];
},
};

module.exports = nextConfig;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"react-bootstrap": "^2.7.4",
"react-dom": "18.2.0",
"react-icons": "^5.0.1",
"reactstrap": "^9.1.9"
"reactstrap": "^9.1.9",
"swr": "^2.2.5"
},
"devDependencies": {
"@types/bootstrap": "^5.2.6",
Expand Down
2 changes: 1 addition & 1 deletion src/app/[topic]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GetStaticPathsResult } from 'next';
import { legalTopics, Topic } from '../../../topics.config';
import { legalTopics, Topic } from '../../config/topics.config';

interface PageProps {
params: {
Expand Down
49 changes: 49 additions & 0 deletions src/app/components/TopicCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Link from 'next/link';

interface TopicCardProps {
topic: {
name: string;
long_name: string;
icon: string;
};
interviews: any;
}

const FontAwesomeIcon: React.FC<IconProps> = ({ iconName, className = '' }) => {
return <i className={`fas fa-${iconName} ${className}`}></i>;
};

const TopicCard = ({ topic, interviews }) => {
return (
<div className="col-lg-4">
<Link
href={`/${topic.name.toLowerCase()}`}
className="text-decoration-none text-dark"
>
<div className="card m-1 topic-card h-100">
<div className="card-header d-flex align-items-center">
<FontAwesomeIcon
iconName={topic.icon}
className="fa-icon"
style={{ minWidth: '40px', minHeight: '40px' }}
/>
<h5 className="card-title ms-3">{topic.long_name}</h5>
</div>
<div className="card-body">
{interviews.length > 0 ? (
interviews.map((interview, index) => (
<span key={index} className="form-tag">
{interview.metadata.title}
</span>
))
) : (
<p>No interviews available.</p>
)}
</div>
</div>
</Link>
</div>
);
};

export default TopicCard;
25 changes: 13 additions & 12 deletions src/app/forms/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Form } from '../interfaces/Form';
import InteractiveForm from '../components/InteractiveForm';
import serverList from '../../../formSources.config.js';
import { formSources } from '../../config/formSources.config';

interface LegalFormsPageProps {
forms: Form[];
Expand All @@ -9,33 +9,34 @@ interface LegalFormsPageProps {
async function getData() {
let allData: Form[] = [];

for (const [serverName, serverUrl] of Object.entries(
serverList['docassemble servers']
)) {
const url = new URL(serverUrl);
// Iterating over an array of server objects
for (const server of formSources.docassembleServers) {
const url = new URL(server.url); // Access the URL directly from the server object
url.pathname = '/list';
url.search = 'json=1';

const res = await fetch(url);
const res = await fetch(url.toString());

// Recommendation: handle errors
// Handle errors
if (!res.ok) {
console.error(`Failed to fetch data from ${serverUrl}`);
console.error(`Failed to fetch data from ${server.url}`);
continue; // Skip this server and continue with the next one
}

const data = await res.json();

if (!data.hasOwnProperty('interviews')) {
console.error(`Data from ${serverUrl} does not contain "interviews" key`);
console.error(
`Data from ${server.url} does not contain "interviews" key`
);
continue; // Skip this server and continue with the next one
}

// If you want to include the server name and server URL in the data:
// Include the server name and server URL in the data
const interviews = data['interviews'].map((interview: Form) => ({
...interview,
serverName,
serverUrl,
serverName: server.name,
serverUrl: server.url,
}));

allData = allData.concat(interviews);
Expand Down
116 changes: 29 additions & 87 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,15 @@
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import React from 'react';
import HeroSection from './components/HeroSection';
import HowItWorksSection from './components/HowItWorksSection';
import { Topic } from '../../topics.config';

const { legalTopics, findParentTopic } = require('../../topics.config.ts');

interface IconProps {
iconName: string;
className: string;
}

const FontAwesomeIcon: React.FC<IconProps> = ({ iconName, className = '' }) => {
return <i className={`fas fa-${iconName} ${className}`}></i>;
};

const fakeFormNames = [
'Fee waiver',
'209A Domestic Violence Restraining Order',
'Enlarge Time to File (Appeals Court)',
'Appeal or Stay Your Eviction',
'Eviction Moratorium',
'Civil Docketing Statement',
'Massachusetts Defense for Eviction (MADE)',
'Dismiss your CRA case',
'Interpreter Notice',
'Petition to Change Name of Adult',
];

const getRandomItems = (arr: Array<string>, min: number, max: number) => {
const newArr = [...arr]; // Copy array to avoid mutating the original one.
let count = Math.floor(min + Math.random() * (max - min + 1));
let result: Array<string> = [];
while (count--) {
result.push(newArr.splice(Math.floor(Math.random() * newArr.length), 1)[0]);
}
return result;
};

// const formPill

const TopicCard = ({ topic }: { topic: Topic }) => (
<div className="col-lg-4">
<Link
href={`/${topic.name.toLowerCase()}`}
className="text-decoration-none text-dark"
>
<div className="card m-1 topic-card h-100">
<div className="card-header d-flex align-items-center">
<div
style={{ minWidth: '40px', minHeight: '40px' }}
className="icon-container d-inline-flex justify-content-center align-items-center rounded"
>
<FontAwesomeIcon iconName={topic.icon} className="fa-icon" />
</div>
<h5 className="card-title ms-3">{topic.long_name}</h5>
</div>
<div className="card-body">
{getRandomItems(fakeFormNames, 2, 5).map((form: string) => (
<span key={form} className="form-tag">
{form}
</span>
))}
<span className="form-tag">+2</span>
</div>
</div>
</Link>
</div>
);
import { useInterviews } from '../data/fetchInterviewData';
import TopicCard from './components/TopicCard';
import { legalTopics } from '../config/topics.config';

export default function TopicsPage() {
const { interviewsByTopic, isLoading, isError } = useInterviews();

return (
<div>
<HeroSection />
Expand All @@ -78,30 +18,32 @@ export default function TopicsPage() {
<div className="container">
<h2>Browse court forms by category</h2>
<div className="row row-cols-1 row-cols-md-3 g-5">
{legalTopics
.sort((a: Topic, b: Topic) => (a.priority < b.priority ? 1 : -1))
.filter((topic: Topic) => topic.always_visible)
.map((topic: Topic) => (
<TopicCard key={topic.codes[0]} topic={topic} />
))}
{isLoading ? (
<div>Loading interviews...</div>
) : isError ? (
<div>Error loading interviews.</div>
) : (
// Only display topics who either are marked always_visible or have interviews
legalTopics
.sort((a, b) => b.priority - a.priority)
.filter(
(topic) =>
topic.always_visible ||
(interviewsByTopic[topic.name] &&
interviewsByTopic[topic.name].length > 0)
)
.map((topic) => (
<TopicCard
key={topic.codes[0]}
topic={topic}
interviews={interviewsByTopic[topic.name] || []}
/>
))
)}
</div>
<Link href="#">Show all categories</Link>
</div>
</section>
<div className="container">
<div className="row mt-4">
<div className="col">
<h2>About</h2>
<p>
Court Forms Online is operated by Suffolk University Law School's
Legal Innovation and Technology Lab. It began as a volunteer
project in cooperation with the Massachusetts Access to Justice
Commission's COVID-19 task force and volunteers from around the
world. <Link href="/about">Learn more...</Link>
</p>
</div>
</div>
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions src/config/formSources.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const formSources = {
docassembleServers: [
{
key: 'suffolkListLab',
url: 'https://apps.suffolklitlab.org',
name: 'Suffolk LIT Lab',
},
{
key: 'greaterBostonLegalService',
url: 'https://interviews.gbls.org',
name: 'Greater Boston Legal Services',
},
],
};
8 changes: 8 additions & 0 deletions topics.config.ts → src/config/topics.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ export const legalTopics: Topic[] = [
always_visible: true,
priority: 10,
},
{
codes: ['OT-00-00-00-00'],
name: 'Other',
long_name: 'Other Topics',
icon: 'folder-open',
always_visible: false,
priority: 0,
},
];

export function findParentTopic(tag: string) {
Expand Down
49 changes: 49 additions & 0 deletions src/data/fetchInterviewData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import useSWR from 'swr';
import { formSources } from '../config/formSources.config';
import { legalTopics } from '../config/topics.config';

export const fetcher = (url) => fetch(url).then((res) => res.json());

export const useInterviews = () => {
// This can be modified to grab a different url from formsources based on subdomain or route.
const serverUrl = formSources.docassembleServers[0].url;
const url = new URL(`${serverUrl}/list`);
url.search = 'json=1';

const { data, error, isLoading } = useSWR(url.toString(), fetcher);

const interviewsByTopic = { Other: [] };

// Match returned interview data to corresponding topic from topics.config.ts
if (data && data.interviews) {
data.interviews.forEach((interview) => {
let assigned = false;
const tags = interview.tags || [];
if (tags.length === 0) {
interviewsByTopic['Other'].push(interview);
} else {
tags.forEach((tag) => {
const topic = legalTopics.find((t) => t.codes.includes(tag));
if (topic) {
if (!interviewsByTopic[topic.name]) {
interviewsByTopic[topic.name] = [];
}
interviewsByTopic[topic.name].push(interview);
assigned = true;
}
});
if (!assigned) {
interviewsByTopic['Other'].push(interview);
}
}
});
}

return {
interviewsByTopic,
isLoading,
isError: error,
};
};
Loading

0 comments on commit 26f1470

Please sign in to comment.