Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5520e66
Clicking a note now navigates to /notes/[id] instead of opening the P…
SXT230118 Apr 8, 2026
e71ca92
It only kind of matches the Figma, but progress!
SXT230118 Apr 15, 2026
f26fc80
Pretty :D
SXT230118 Apr 15, 2026
abce9d1
Revert package.json and package-lock.json
SXT230118 Apr 15, 2026
bd0824d
Reverting bundler to node
SXT230118 Apr 22, 2026
88c7e2d
Added a little bit of padding to make sure the info bar doesn't cove …
SXT230118 Apr 22, 2026
26350f9
I haven't reverted back to iFrame yet because of that pdf formatting,…
SXT230118 Apr 22, 2026
74792de
Not iFrame, but the notes page should load in FireFox now
SXT230118 Apr 22, 2026
0ce4af9
Forgot about pretty formatting
SXT230118 Apr 22, 2026
7666cc2
Merge branch 'develop' of https://github.com/UTDNebula/utd-notebook i…
SXT230118 Apr 22, 2026
37d8d55
Added the comments back into SaveButton.tsx
SXT230118 Apr 29, 2026
ca855f3
Dark mode colors
SXT230118 Apr 29, 2026
db28987
Forgot to pretty
SXT230118 Apr 29, 2026
975a63f
Switched from css grid for panel on top of note to mui collapse. The …
SXT230118 Apr 29, 2026
7b7715a
Author and professor hyperlinked!
SXT230118 Apr 29, 2026
14bb133
Restored iframe
SXT230118 Apr 29, 2026
6bb5e4f
Fixed save button
SXT230118 Apr 29, 2026
f87a0ab
Slight clean-up of divs on NoteInfoPanel
SXT230118 Apr 29, 2026
ce3364d
Small fixes, top panel finally works!
SXT230118 May 5, 2026
d8876a8
Forgot to run prettier
SXT230118 May 5, 2026
60b0d08
Add 2 buttons and style changes
TyHil Jun 6, 2026
327acc0
Merge branch 'develop' into feature/note-detail-page
TyHil Jun 6, 2026
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
62 changes: 15 additions & 47 deletions src/app/notes/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { notFound } from 'next/navigation';
import { BaseCard } from '@src/components/common/BaseCard';
import Header from '@src/components/header/Header';
import RatingWidget from '@src/components/sections/RatingWidget';
import NoteInfoPanel from '@src/components/sections/NoteInfoPanel';
import { api } from '@src/trpc/server';

type NotePageProps = {
Expand All @@ -13,54 +14,21 @@ export default async function NotePage({ params }: NotePageProps) {
if (!file) notFound();

return (
<>
<div className="flex flex-col h-full">
<Header />
<main>
<h1>{file.name}</h1>
<p>{file.description || 'No provided description.'}</p>
<main className="p-4 pt-0 flex w-full h-full flex-col items-center gap-4">
{/* Info panel pinned to the top, floats over the PDF */}
<NoteInfoPanel file={file} />

<section>
<p>
<strong>Course:</strong> {file.section?.prefix}{' '}
{file.section?.number}
</p>
<p>
<strong>Section: </strong> {file.section?.sectionCode}
</p>
<p>
<strong>Professor:</strong> {file.section?.profFirst}{' '}
{file.section?.profLast}
</p>
<p>
<strong>Author:</strong>{' '}
{file.author
? `${file.author.firstName} ${file.author.lastName}`
: 'Unknown'}
</p>
<p>
<strong>Last Updated:</strong>{' '}
{file.updatedAt?.toLocaleDateString()}
</p>
</section>

<section>
<RatingWidget fileId={id} />
</section>

<section>
<h2>PDF</h2>
{file.publicUrl ? (
<iframe
src={file.publicUrl}
title={file.name}
width="100%"
height="800px"
/>
) : (
<p>PDF not available yet.</p>
)}
</section>
{/* Scrollable area with white card background for the PDF */}
<BaseCard className="h-full min-h-[50vh] w-full overflow-hidden max-w-6xl">
<iframe
src={file.publicUrl}
title={file.name}
className="h-full w-full border-0"
/>
</BaseCard>
</main>
</>
</div>
);
}
9 changes: 2 additions & 7 deletions src/components/sections/FileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ export default function FileCard({ file }: FileCardProps) {

return (
<BaseCard variant="interactive" className="flex h-full flex-col">
<Link
href={file.publicUrl}
target="_blank"
rel="noreferrer"
className="flex grow flex-col"
>
<Link href={`/notes/${file.id}`} className="flex grow flex-col">
<div className="overflow-hidden rounded-t-lg border-b border-neutral-200 bg-white dark:border-neutral-600 dark:bg-neutral-700">
{thumbData ? (
<div className="relative aspect-[3/4] w-full">
Expand Down Expand Up @@ -136,7 +131,7 @@ export default function FileCard({ file }: FileCardProps) {
</div>

<p className="px-4 pb-2 text-xs font-medium text-slate-600 dark:text-slate-400">
Uploaded by{' '}
By{' '}
{file.author?.username ? (
<Link
href={`/profile/${file.author.username}`}
Expand Down
126 changes: 126 additions & 0 deletions src/components/sections/NoteInfoPanel.tsx
Comment thread
SXT230118 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use client';

import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { Collapse, IconButton, Typography } from '@mui/material';
import Link from 'next/link'; // To link back to specific profiles
import { useState } from 'react';
import Panel from '@src/components/common/Panel';
import NoteDeleteButton from '@src/components/sections/NoteDeleteButton';
import NoteEditButton from '@src/components/sections/NoteEditButton';
Comment thread
TyHil marked this conversation as resolved.
import RatingWidget from '@src/components/sections/RatingWidget';
import ReportButton from '@src/components/sections/ReportButton';
import SaveButton from '@src/components/sections/SaveButton';
import type { SelectFileWithUserMetadataAndSection } from '@src/server/db/models';
import { authClient } from '@src/utils/auth-client';

type NoteInfoPanelProps = {
file: SelectFileWithUserMetadataAndSection;
};

export default function NoteInfoPanel({ file }: NoteInfoPanelProps) {
const [expanded, setExpanded] = useState(true);
const { data: session } = authClient.useSession();
const isAuthor = session?.user?.id === file.authorId;

return (
<Panel
className="w-full py-2 border-l-4 border-royal dark:border-cornflower-300 rounded-t-none gap-0"
smallPadding
>
<div className="flex flex-col">
{/* Top piece, always visible, has the name and buttons */}
<div className="flex flex-wrap items-center gap-2">
<div className="flex items-center w-full md:w-auto flex-grow gap-2">
<IconButton
onClick={() => setExpanded((v) => !v)}
aria-label={
expanded ? 'Collapse info panel' : 'Expand info panel'
}
size="large"
>
<KeyboardArrowUpIcon
fontSize="inherit"
className={`transition ${expanded ? 'rotate-180' : 'rotate-90'}`}
/>
</IconButton>
<Typography variant="h2" className="text-xl font-bold">
{file.name}
</Typography>
</div>
<div className="w-full md:w-auto flex-shrink-0 flex md:ml-auto justify-end flex-wrap items-center gap-3">
<RatingWidget fileId={file.id} />
{isAuthor && <NoteEditButton fileId={file.id} />}
{isAuthor && <NoteDeleteButton fileId={file.id} />}
{!isAuthor && <ReportButton fileId={file.id} />}
<SaveButton fileId={file.id} />
<IconButton
LinkComponent={Link}
href={file.publicUrl}
target="_blank"
size="small"
>
<OpenInNewIcon fontSize="small" />
</IconButton>
</div>
</div>

{/* Bottom piece, collapsible, has author, professor, description, and last modified date */}
<Collapse in={expanded}>
<div className="pb-2 px-3 flex flex-col gap-1">
<p className="text-sm font-medium text-slate-800 dark:text-slate-200">
By{' '}
{
file.author.username ? (
<Link
href={`/profile/${file.author.username}`}
className="underline hover:text-slate-900 dark:hover:text-slate-200"
>
{file.author.username}
</Link>
) : (
`${file.author.firstName} ${file.author.lastName}`
) // Render as plain text if no username to link to
}
</p>
{file.section && (
<p className="text-sm font-medium text-slate-800 dark:text-slate-200">
<Link
href={`/notes/${file.section.prefix}/${file.section.number}`}
className="underline hover:text-slate-900 dark:hover:text-slate-200"
>
{file.section.prefix} {file.section.number}
</Link>
.{file.section.sectionCode}{' '}
<Link
href={`/notes/${file.section.profFirst}/${file.section.profLast}`}
className="underline hover:text-slate-900 dark:hover:text-slate-200"
>
{file.section.profFirst} {file.section.profLast}
</Link>{' '}
{file.section.term} {file.section.year}
</p>
)}
{file.description ? (
<p className="text-sm text-slate-600 dark:text-slate-400 leading-relaxed mt-1">
{file.description}
</p>
) : (
<p className="text-sm text-slate-400 italic mt-1">
No provided description.
</p>
)}
<span className="text-xs text-slate-600 dark:text-slate-400 mt-1">
Last modified{' '}
{file.updatedAt.toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric',
})}
</span>
</div>
</Collapse>
</div>
</Panel>
);
}
41 changes: 29 additions & 12 deletions src/server/db/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,35 @@ export type SelectFileWithUserMetadata = z.infer<
typeof selectFileWithUserMetadata
>;

export const selectFileWithAuthorPreview = selectFile.extend({
author: selectUserMetadata.pick({
username: true,
firstName: true,
lastName: true,
}),
});

export type SelectFileWithAuthorPreview = z.infer<
typeof selectFileWithAuthorPreview
>;

/* =========================
FILE WITH USER METADATA AND SECTION
========================= */

export const selectFileWithUserMetadataAndSection = selectFile.extend({
author: selectUserMetadata.pick({
username: true,
firstName: true,
lastName: true,
}),
section: selectSection.nullable(),
});

export type SelectFileWithUserMetadataAndSection = z.infer<
typeof selectFileWithUserMetadataAndSection
>;

/* =========================
SECTION WITH FILES
========================= */
Expand All @@ -95,15 +124,3 @@ export const sectionWithFilesWithUserMetadata = selectSection.extend({
export type SectionWithFilesWithUserMetadata = z.infer<
typeof sectionWithFilesWithUserMetadata
>;

export const selectFileWithAuthorPreview = selectFile.extend({
author: selectUserMetadata.pick({
username: true,
firstName: true,
lastName: true,
}),
});

export type SelectFileWithAuthorPreview = z.infer<
typeof selectFileWithAuthorPreview
>;
Loading