render list of articles from backend

This commit is contained in:
2026-01-24 18:44:37 -06:00
parent c5e8faf9ed
commit d050127113
7 changed files with 148 additions and 59 deletions
+6 -6
View File
@@ -8,7 +8,7 @@ export const articlesRouter = express.Router();
articlesRouter.get("/getAllArticles", async (_, res: Response) => { articlesRouter.get("/getAllArticles", async (_, res: Response) => {
const resp = await db.select().from(articles); const resp = await db.select().from(articles);
res.json({ resp }); res.json(resp);
}); });
articlesRouter.get("/getArticle/:articleId", async (req: Request, res: Response) => { articlesRouter.get("/getArticle/:articleId", async (req: Request, res: Response) => {
@@ -17,7 +17,7 @@ articlesRouter.get("/getArticle/:articleId", async (req: Request, res: Response)
const resp = await db.select() const resp = await db.select()
.from(articles) .from(articles)
.where(eq(articles.id, articleId)); .where(eq(articles.id, articleId));
res.json({ resp }); res.json(resp);
}); });
articlesRouter.get("/getArticleBySlug/:slug", async (req: Request, res: Response) => { articlesRouter.get("/getArticleBySlug/:slug", async (req: Request, res: Response) => {
@@ -26,7 +26,7 @@ articlesRouter.get("/getArticleBySlug/:slug", async (req: Request, res: Response
const resp = await db.select() const resp = await db.select()
.from(articles) .from(articles)
.where(eq(articles.slug, slug)); .where(eq(articles.slug, slug));
res.json({ resp }); res.json(resp);
}); });
articlesRouter.post("/postArticle", async (req: Request, res: Response) => { articlesRouter.post("/postArticle", async (req: Request, res: Response) => {
@@ -36,7 +36,7 @@ articlesRouter.post("/postArticle", async (req: Request, res: Response) => {
...req.body, ...req.body,
slug: req.body.name.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''), slug: req.body.name.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''),
}); });
res.json({ resp }); res.json(resp);
}); });
articlesRouter.put("/putArticle/:articleId", async (req: Request, res: Response) => { articlesRouter.put("/putArticle/:articleId", async (req: Request, res: Response) => {
@@ -52,7 +52,7 @@ articlesRouter.put("/putArticle/:articleId", async (req: Request, res: Response)
date_published: req.body.article_status == "published" ? new Date(Date.now()) : null date_published: req.body.article_status == "published" ? new Date(Date.now()) : null
}) })
.where(eq(articles.id, articleId)); .where(eq(articles.id, articleId));
res.json({ resp }); res.json(resp);
}); });
articlesRouter.delete("/deleteArticle/:articleId", async (req: Request, res: Response) => { articlesRouter.delete("/deleteArticle/:articleId", async (req: Request, res: Response) => {
@@ -60,5 +60,5 @@ articlesRouter.delete("/deleteArticle/:articleId", async (req: Request, res: Res
const resp = await db.delete(articles) const resp = await db.delete(articles)
.where(eq(articles.id, articleId)); .where(eq(articles.id, articleId));
res.json({ resp }); res.json(resp);
}); });
+55 -45
View File
@@ -11,12 +11,13 @@
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.5", "@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5", "@mui/material": "^7.3.7",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"tailwindcss": "^4.1.17" "tailwindcss": "^4.1.17",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
@@ -753,9 +754,9 @@
} }
}, },
"node_modules/@mui/core-downloads-tracker": { "node_modules/@mui/core-downloads-tracker": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.7.tgz",
"integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==", "integrity": "sha512-8jWwS6FweMkpyRkrJooamUGe1CQfO1yJ+lM43IyUJbrhHW/ObES+6ry4vfGi8EKaldHL3t3BG1bcLcERuJPcjg==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -789,22 +790,22 @@
} }
}, },
"node_modules/@mui/material": { "node_modules/@mui/material": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.7.tgz",
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", "integrity": "sha512-6bdIxqzeOtBAj2wAsfhWCYyMKPLkRO9u/2o5yexcL0C3APqyy91iGSWgT3H7hg+zR2XgE61+WAu12wXPON8b6A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.5", "@mui/core-downloads-tracker": "^7.3.7",
"@mui/system": "^7.3.5", "@mui/system": "^7.3.7",
"@mui/types": "^7.4.8", "@mui/types": "^7.4.10",
"@mui/utils": "^7.3.5", "@mui/utils": "^7.3.7",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12", "@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"csstype": "^3.1.3", "csstype": "^3.2.3",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-is": "^19.2.0", "react-is": "^19.2.3",
"react-transition-group": "^4.4.5" "react-transition-group": "^4.4.5"
}, },
"engines": { "engines": {
@@ -817,7 +818,7 @@
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.5.0", "@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^7.3.5", "@mui/material-pigment-css": "^7.3.7",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -838,13 +839,13 @@
} }
}, },
"node_modules/@mui/private-theming": { "node_modules/@mui/private-theming": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.7.tgz",
"integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==", "integrity": "sha512-w7r1+CYhG0syCAQUWAuV5zSaU2/67WA9JXUderdb7DzCIJdp/5RmJv6L85wRjgKCMsxFF0Kfn0kPgPbPgw/jdw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/utils": "^7.3.5", "@mui/utils": "^7.3.7",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
@@ -865,16 +866,16 @@
} }
}, },
"node_modules/@mui/styled-engine": { "node_modules/@mui/styled-engine": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.7.tgz",
"integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==", "integrity": "sha512-y/QkNXv6cF6dZ5APztd/dFWfQ6LHKPx3skyYO38YhQD4+Cxd6sFAL3Z38WMSSC8LQz145Mpp3CcLrSCLKPwYAg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@emotion/cache": "^11.14.0", "@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3", "@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0", "@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3", "csstype": "^3.2.3",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
@@ -899,18 +900,18 @@
} }
}, },
"node_modules/@mui/system": { "node_modules/@mui/system": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.7.tgz",
"integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==", "integrity": "sha512-DovL3k+FBRKnhmatzUMyO5bKkhMLlQ9L7Qw5qHrre3m8zCZmE+31NDVBFfqrbrA7sq681qaEIHdkWD5nmiAjyQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/private-theming": "^7.3.5", "@mui/private-theming": "^7.3.7",
"@mui/styled-engine": "^7.3.5", "@mui/styled-engine": "^7.3.7",
"@mui/types": "^7.4.8", "@mui/types": "^7.4.10",
"@mui/utils": "^7.3.5", "@mui/utils": "^7.3.7",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"csstype": "^3.1.3", "csstype": "^3.2.3",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
@@ -939,9 +940,9 @@
} }
}, },
"node_modules/@mui/types": { "node_modules/@mui/types": {
"version": "7.4.8", "version": "7.4.10",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.10.tgz",
"integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==", "integrity": "sha512-0+4mSjknSu218GW3isRqoxKRTOrTLd/vHi/7UC4+wZcUrOAqD9kRk7UQRL1mcrzqRoe7s3UT6rsRpbLkW5mHpQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4" "@babel/runtime": "^7.28.4"
@@ -956,17 +957,17 @@
} }
}, },
"node_modules/@mui/utils": { "node_modules/@mui/utils": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.7.tgz",
"integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==", "integrity": "sha512-+YjnjMRnyeTkWnspzoxRdiSOgkrcpTikhNPoxOZW0APXx+urHtUoXJ9lbtCZRCA5a4dg5gSbd19alL1DvRs5fg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/types": "^7.4.8", "@mui/types": "^7.4.10",
"@types/prop-types": "^15.7.15", "@types/prop-types": "^15.7.15",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-is": "^19.2.0" "react-is": "^19.2.3"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@@ -2274,9 +2275,9 @@
} }
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
@@ -3626,9 +3627,9 @@
} }
}, },
"node_modules/react-is": { "node_modules/react-is": {
"version": "19.2.0", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
"integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-refresh": { "node_modules/react-refresh": {
@@ -4265,6 +4266,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }
+3 -2
View File
@@ -13,12 +13,13 @@
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.5", "@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5", "@mui/material": "^7.3.7",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"tailwindcss": "^4.1.17" "tailwindcss": "^4.1.17",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
+20
View File
@@ -0,0 +1,20 @@
import { useNavigate } from 'react-router-dom';
import type { Article } from '../types/Article.ts';
import { Card } from '@mui/material';
interface Props {
article: Article;
}
export default function ArticleCard(props: Props) {
const navigate = useNavigate();
return (
<Card
className="p-1 cursor-pointer"
onClick={() => { navigate(`/article/${props.article.slug}`) }}
>
{props.article.name}
</Card>
);
};
+38 -3
View File
@@ -1,7 +1,42 @@
import { useState } from 'react';
import { Button } from '@mui/material';
import type { Article } from '../types/Article.ts';
import { ArticleListSchema } from '../types/Article.ts';
import ArticleCard from '../components/ArticleCard.tsx';
export default function ArticleList() { export default function ArticleList() {
const [articles, setArticles] = useState<Article[]>([]);
async function getAllArticles() {
const result = await fetch(
"/api/articles/getAllArticles",
{
method: "GET",
headers: { "Accept": "application/json" }
}
);
const json = await result.json();
const data = ArticleListSchema.parse(json);
console.log(data);
setArticles(data);
};
return ( return (
<div> <div className="p-3 flex flex-col gap-5">
List of articles to be populated and searchable <p>
</div> List of articles to be populated and searchable
</p>
<Button
style={{ border: "solid", borderWidth: "1px" }}
onClick={() => getAllArticles()}
>
Get All Articles
</Button>
<div>
{articles.map((article) => (<ArticleCard key={article.id} article={article} />))}
</div>
</div >
); );
}; };
+16
View File
@@ -0,0 +1,16 @@
import { z } from 'zod';
export const ArticleSchema = z.object({
id: z.string(),
name: z.string(),
article_status: z.enum(["draft", "published", "archived"]),
author_id: z.string(),
slug: z.string(),
summary: z.string(),
date_created: z.string(),
last_edit: z.string(),
date_published: z.string().nullable(),
});
export const ArticleListSchema = z.array(ArticleSchema);
export type Article = z.infer<typeof ArticleSchema>;
+10 -3
View File
@@ -1,7 +1,14 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, {
{ "path": "./tsconfig.node.json" } "path": "./tsconfig.app.json"
] },
{
"path": "./tsconfig.node.json"
}
],
"compilerOptions": {
"strict": true
}
} }