Skip to content
Draft
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
3 changes: 0 additions & 3 deletions packages/dapp/.eslintrc.json

This file was deleted.

1 change: 0 additions & 1 deletion packages/dapp/config/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Flip this back when DB access is restored.
export const PRISMA_DISABLED = true;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const prisma: any = null;

export default prisma;
22 changes: 22 additions & 0 deletions packages/dapp/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig, globalIgnores } from 'eslint/config';
import nextVitals from 'eslint-config-next/core-web-vitals';
import prettierConfig from 'eslint-config-prettier';

export default defineConfig([
...nextVitals,
{
rules: {
'react-hooks/set-state-in-effect': 'off',
'react-hooks/immutability': 'off',
'react-hooks/error-boundaries': 'off',
},
},
prettierConfig,
globalIgnores([
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
'postcss.config.js',
]),
]);
5 changes: 3 additions & 2 deletions packages/dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"three-stdlib": "^2.34.0",
"unified": "^11.0.5",
"viem": "~2.10.11",
"wagmi": "^2.13.0"
"wagmi": "^2.13.0",
"cheerio": "^1.2.0"
},
"devDependencies": {
"@react-three/gltfjsx": "^4.3.4",
Expand All @@ -61,7 +62,7 @@
"@types/three": "0.154.0",
"@typescript-eslint/parser": "^5.62.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint": "^9.39.0",
"eslint-config-next": "^16.1.6",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
Expand Down
48 changes: 21 additions & 27 deletions packages/dapp/src/app/(main)/blog/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkHtml from 'remark-html';
import remarkGfm from 'remark-gfm';
import {
fetchAllTeamNotes,
fetchNoteContent,
parseNoteContent,
} from '@/lib/hackMD';
import { fetchNoteContent, parseNoteContent, renderMarkdownToHtml } from '@/lib/hackMD';
import Image from 'next/image';

const Note = async ({ params }: { params: { id: string } }) => {
const noteId = params.id;
interface NotePageProps {
params: {
id: string;
};
}

// Log the noteId to ensure it is correctly captured -- only for dev
// //console.log('Fetching note with ID---:', noteId);
const Note = async ({ params }: NotePageProps): Promise<React.JSX.Element> => {
const noteId = params.id;

try {
const note = await fetchNoteContent(noteId);
const parsedNote = parseNoteContent(note.content);

const processedContent = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkHtml, { sanitize: false })
.process(parsedNote.content);
const contentHtml = processedContent.toString();
const parsedNote = parseNoteContent(note.content ?? '');
const contentHtml = await renderMarkdownToHtml(parsedNote.content);
const noteTitle =
typeof parsedNote.data?.title === 'string' && parsedNote.data.title.trim()
? parsedNote.data.title
: note.title ?? 'Blog Post';

return (
<div className="relative min-h-screen w-full overflow-hidden">
{/* Watermark */}
<div className="absolute inset-0 flex items-center justify-center opacity-5 pointer-events-none">
<div className="relative w-[150%] aspect-square">
<Image
Expand All @@ -40,19 +32,21 @@ const Note = async ({ params }: { params: { id: string } }) => {
</div>
</div>

{/* Content */}
<div className="relative space-y-24 mx-6 my-28 lg:mx-[15%]">
<h1>{parsedNote.data.title}</h1>
<h1>{noteTitle}</h1>
<div
className="space-y-6 prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: contentHtml }}
/>
</div>
</div>
);
} catch (error) {
console.error('Error fetching note content:', error);
return <div>Error loading note</div>;
} catch (_error: unknown) {
return (
<div className="w-full h-full text-center bg-red-100 text-red-500 py-2">
Error loading note... Please Refresh...
</div>
);
}
};

Expand Down
79 changes: 44 additions & 35 deletions packages/dapp/src/components/BlogList.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { fetchAllTeamNotes } from '@/lib/hackMD';
import { fetchAllTeamNotes, extractPlainTextFromHtml, renderMarkdownToHtml } from '@/lib/hackMD';
import { Note } from '@/types/frontend';
import { ArrowRightIcon } from 'lucide-react';
import Link from 'next/link';
import React from 'react';
import { Button } from './button/Button';

const getExcerpt = (content: string, length: number = 100): string => {
const getExcerpt = (content: string, length: number = 160): string => {
return content.length > length
? content.substring(0, length) + '...'
? `${content.substring(0, length).trim()}...`
: content;
};

const BlogList = async () => {
const BlogList = async (): Promise<React.JSX.Element> => {
try {
const notes: Note[] = await fetchAllTeamNotes();

const notesWithPreview = await Promise.all(
notes.map(async (note: Note): Promise<Note> => {
const htmlPreview: string = await renderMarkdownToHtml(note.content ?? '');
const plainTextPreview: string = extractPlainTextFromHtml(htmlPreview);

return {
...note,
content: plainTextPreview,
};
})
);

return (
<section className=" space-y-4 md:mx-[15%] mx-[5%] ">
<div className="space-y-4">
Expand All @@ -32,46 +43,44 @@ const BlogList = async () => {
</p>
</div>

{/* Blog List with spacing */}

<div className="container">
<ul className="w-full grid grid-cols-1 gap-8 md:grid-cols-2 lg:gap-12">
{notes.map((note) => (
<li
key={note.shortId}
className={`
relative w-full p-8
border-2 border-orange-600/50
bg-white hover:bg-neutral-50
transform hover:-translate-y-1 transition-all duration-300 ease-in-out
shadow-[4px_4px_0px_0px_rgba(234,88,12,0.2)]
hover:shadow-[6px_6px_0px_0px_rgba(234,88,12,0.3)]
`}
>
<div className="relative flex flex-col gap-6 z-10">
<div className="space-y-2">
<h3 className="text-2xl font-bold text-neutral-800">
{note.title}
</h3>
<p className="text-neutral-600 leading-relaxed">
{getExcerpt(note.content)}
</p>
</div>
<Link href={`/blog/${note.shortId}`}>
<Button className="flex gap-2 border border-orange-600 bg-orange-600 text-white hover:bg-orange-600/95 focus:ring-orange-600">
{notesWithPreview.map((note: Note) => (
<li key={note.shortId}>
<Link
href={`/blog/${note.shortId}`}
className={`
relative block w-full p-8 h-full
border-2 border-orange-600/50
bg-white hover:bg-neutral-50
transform hover:-translate-y-1 transition-all duration-300 ease-in-out
shadow-[4px_4px_0px_0px_rgba(234,88,12,0.2)]
hover:shadow-[6px_6px_0px_0px_rgba(234,88,12,0.3)]
focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-500
`}
>
<div className="relative flex flex-col gap-6 z-10 h-full">
<div className="space-y-2">
<h3 className="text-2xl font-bold text-neutral-800">
{note.title}
</h3>
<p className="text-neutral-600 leading-relaxed">
{getExcerpt(note.content)}
</p>
</div>
<div className="flex items-center gap-2 border border-orange-600 bg-orange-600 text-white hover:bg-orange-600/95 px-4 py-2 w-fit rounded-md text-sm font-medium">
Read More
<ArrowRightIcon className="w-4 h-4" />
</Button>
</Link>
</div>
</div>
</div>
</Link>
</li>
))}
</ul>
</div>
</section>
);
} catch (error) {
console.error('Error in Blog component:', error);
} catch (_error: unknown) {
return (
<div className="w-full h-full text-center bg-red-100 text-red-500 py-2">
Error loading blog posts... Please Refresh...
Expand Down
17 changes: 15 additions & 2 deletions packages/dapp/src/features/ticketpurchasecomponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ const TicketPurchaseComponent = ({
setButtonType('secondary');
setButtonText(isCountdownOver ? 'View Exhibit' : 'Read Insights');
}
}, [purchaseSuccessful, isCountdownOver]);
}, [
purchaseSuccessful,
isCountdownOver,
setHasTicket,
setButtonType,
setButtonText,
]);

useEffect(() => {
if (userAddress && user_id) {
Expand All @@ -71,7 +77,14 @@ const TicketPurchaseComponent = ({
setIsValidating(false);
});
}
}, [userAddress, user_id, purchaseSuccessful]);
}, [
userAddress,
user_id,
purchaseSuccessful,
setHasTicket,
setButtonType,
setButtonText,
]);

if (!exhibit) return <div>Loading Exhibit</div>;

Expand Down
42 changes: 42 additions & 0 deletions packages/dapp/src/lib/hackMD.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import axios, { AxiosError } from 'axios';
import matter from 'gray-matter';
import { unified } from 'unified';
import remarkGfm from 'remark-gfm';
import remarkHtml from 'remark-html';
import remarkParse from 'remark-parse';
import { Note } from '@/types/frontend';
import * as cheerio from 'cheerio';

const API_URL = 'https://api.hackmd.io/v1/notes';
const BEARER_TOKEN = process.env.HACKMD_API_TOKEN as string;
Expand Down Expand Up @@ -139,3 +144,40 @@ export const parseNoteContent = (
content: parsed.content,
};
};

/**
* Render markdown content to HTML.
* @param {string} markdown - markdown content to render.
* @returns {Promise<string>} rendered HTML string.
*/
export const renderMarkdownToHtml = async (
markdown: string
): Promise<string> => {
const processedContent = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkHtml, { sanitize: false })
.process(markdown);

return processedContent.toString();
};

/**
* Extract readable text from rendered HTML.
* @param {string} html - rendered HTML content.
* @returns {string} plain text content.
*/
export const extractPlainTextFromHtml = (html: string): string => {
const $ = cheerio.load(html);

// Remove script and style elements entirely
$('script, style').remove();

let text = $.root().text();

return text
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/\s+/g, ' ')
.trim();
};
Loading
Loading