Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve details card #2

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# See docs here:
https://docs.google.com/document/d/1s2MIPcm9-fAWXRLkmm5gg_9BtlVpPups0XWNlaWxDnY/edit

694 changes: 295 additions & 399 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
"@t3-oss/env-nextjs": "^0.10.1",
"@tailwindcss/aspect-ratio": "^0.4.2",
"crypto": "^1.0.1",
"firebase": "^10.12.4",
"firebase": "^10.14.0",
"geist": "^1.3.0",
"next": "^14.2.1",
"next": "^14.2.13",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-redux": "^9.1.2",
Expand Down
Binary file added public/assets/Jane Doe.webp
Binary file not shown.
Binary file added public/assets/John Deer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
199 changes: 159 additions & 40 deletions src/app/(content)/feed/page.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,174 @@
"use client";
import { getFeed } from "~/lib/api/getFeed";
import { useUser } from "@clerk/nextjs";
import { useEffect, useRef, useState } from "react";
import PostCard from "~/components/PostCard";
import { Photo } from "~/types";
import { use, useState } from "react";
import Details from "~/components/Details";
import groupDetails from "~/types";

export default function FeedPage() {
const { user } = useUser();
const [photos, setPhotos] = useState<Photo[]>([]);
const [isScrollNearBottom, setIsScrollNearBottom] = useState(false);
const lastPhoto = useRef<string | null>(null);
const scheduled: groupDetails[]=[
{
groupName: "Concepts Preparation",
numParticipants: 3,
totalSeats: 4,
location: "Giant Eagle",
time: "Sun, Oct 6: 4:00 - 5:00pm",
course: "21-127",
participantDetails: [
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
{ name: "Jane Doe", url: "assets/Jane Doe.webp" },
{ name: "John Deer", url: "assets/John Deer.jpg" },
],
details: "This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!This is for Greggo's Class, not Newstead's!",
},

// This lazily loads the photos, avoiding lag.
function onScroll(e: React.UIEvent<HTMLDivElement>) {
const target = e.target as HTMLDivElement;
if (target.scrollHeight - target.scrollTop <= 2 * target.clientHeight) {
setIsScrollNearBottom(true);
} else {
setIsScrollNearBottom(false);
}
{
groupName: "ECE Preparation",
numParticipants: 2,
totalSeats: 10,
location: "Hunt",
time: "Sun, Oct 12: 4:00 - 5:00pm",
course: "18-100",
participantDetails: [
{ name: "Sylvia Smith", url: "assets/Jane Doe.webp" },

],
details: "We are preparing for the upcomming test 2! WE NEED SOMEONE SMART PLEASE",
}
];

useEffect(() => {
if (photos.length && !isScrollNearBottom) {
return;
}
const loadPhotos = async () => {
if (user) {
// We know this is safe because we check if the user is signed in
const [paginatedPhotos, lastSnapshot] = await getFeed(user?.emailAddresses[0]?.emailAddress as string, lastPhoto.current, 5);
lastPhoto.current = lastSnapshot || null;
setPhotos([...photos, ...paginatedPhotos]);
}
};
const open: groupDetails[]=[
{
groupName: "GRINDING SESSION",
numParticipants: 1,
totalSeats: 4,
location: "Sorrels",
time: "Sun, Oct 4: 4:00 - 10:00pm",
course: "15-112",
participantDetails: [
{ name: "Jane Doe", url: "assets/Jane Doe.webp" }
],
details: "I am grinding my homework just join me",
}
];
const displayDetails = () => {
// Ensure the study group selection for details card is the same as the currently open tab
if (showDetails && showDetails[1] == tabOpen) {
return showDetails;
}
return null;
};

const [showDetails, setShowDetails] = useState<[groupDetails, "Open" | "Scheduled"] | null>(null); // index 1 for open or scheduled
const [tabOpen, setTabOpen] = useState<"Open" | "Scheduled">("Scheduled");

loadPhotos();
}, [user, isScrollNearBottom]);
const displayOpens = open.map((group) => (
<div className="max-w-sm overflow-hidden rounded bg-white shadow-lg cursor-pointer" onClick={() => setShowDetails([group, "Open"])}>
<div className="px-6 py-4">
<div className="mb-2 text-xl font-bold">{group.groupName}</div>
<ul>
<li>{group.course}</li>
<li>{group.time}</li>
<li>{group.location}</li>
</ul>
</div>
</div>
));

const photoCards = photos.map((photo) => (
photo && <></> // What should go here? How do we render a post card?
))
const displayScheduled = scheduled.map((group) => (
<div className="max-w-sm overflow-hidden rounded bg-white shadow-lg cursor-pointer" onClick={() => setShowDetails([group, "Scheduled"])}>
<div className="px-6 py-4">
<div className="mb-2 text-xl font-bold">{group.groupName}</div>
<ul>
<li>{group.course}</li>
<li>{group.time}</li>
<li>{group.location}</li>
</ul>
</div>
</div>
));

return (
<main className="container relative overflow-scroll h-screen" onScroll={onScroll}>
<p className="text-4xl text-white font-bold pt-4 text-center">Feed</p>
<div className="container flex flex-col items-center justify-center gap-12 py-[1rem]">
<div className="grid grid-cols-1">
{photoCards}
<main className="container relative h-screen overflow-scroll">
<div className="flex w-full">
<div className="m-[2.5vw] w-[60vw]">
<div className="mb-4 border-b border-gray-200 dark:border-gray-700">
<ul
className="-mb-px flex flex-wrap"
id="myTab"
data-tabs-toggle="#myTabContent"
role="tablist"
>
<li className="mr-2" role="presentation">
<button
className="inline-block rounded-t-lg border-b-2 border-transparent px-4 py-4 text-center text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300"
id="open-tab"
data-tabs-target="#open"
type="button"
onClick={() => setTabOpen("Open")}
role="tab"
aria-controls="open"
aria-selected="false"
>
Open
</button>
</li>
<li className="mr-2" role="presentation">
<button
className="active inline-block rounded-t-lg border-b-2 border-transparent px-4 py-4 text-center text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300"
id="scheduled-tab"
data-tabs-target="#scheduled"
type="button"
onClick={() => setTabOpen("Scheduled")}
role="tab"
aria-controls="scheduled"
aria-selected="true"
>
Scheduled
</button>
</li>
</ul>
</div>
<div id="myTabContent">
<div
className="hidden rounded-lg bg-gray-50 p-4 dark:bg-gray-800"
id="open"
role="tabpanel"
aria-labelledby="open-tab"
>
<div className="mt-8">
<div className="grid grid-cols-2 gap-4">{displayOpens}</div>
</div>
</div>
<div
className="rounded-lg bg-gray-50 p-4 dark:bg-gray-800"
id="scheduled"
role="tabpanel"
aria-labelledby="scheduled-tab"
>
<div className="mt-8">
<div className="grid grid-cols-2 gap-4">{displayScheduled}</div>
</div>
</div>
</div>
</div>
<div className="h-full w-[25vw]">
{displayDetails() && (
<Details
details={showDetails![0]}
onClick={() => setShowDetails(null)}
></Details>
)}
</div>
</div>
<p className="text-white text-sm font-bold">Stop scrolling, it's bad for you!!</p>
<script src="https://unpkg.com/@themesberg/[email protected]/dist/flowbite.bundle.js"></script>
</main>
);
}
17 changes: 7 additions & 10 deletions src/app/(content)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,16 @@ export default async function ContentLayout({
}: {
children: React.ReactNode;
}) {

return (
<div className="flex h-screen w-screen">
<div className="bg-black left-0 top-0 flex-none w-[15vw] h-full">
<div className="flex h-screen w-screen bg-black">
<div className="bg-slate-400 left-0 top-0 flex-none w-[10vw] h-full">
<NavBar />
</div>
<div className="flex-initial w-[85vw]">
<div className="flex justify-center">
<main className="bg-black h-screen fixed w-[600px] container">
{children}
</main>
</div>
<div className="flex-1 overflow-auto">
<main className="container mx-auto px-4 py-8">
{children}
</main>
</div>
</div>
);
}
}
14 changes: 8 additions & 6 deletions src/app/(content)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";
import "~/styles/globals.css";

import { useEffect, useState } from "react";
import { useUser, SignOutButton } from "@clerk/nextjs";
import { getUserPhotos } from "~/lib/api/getUserPhotos";
import { Photo } from "~/types";
import { useEffect, useState } from "react";
import { ClassList } from "~/components/ClassList";

function ProfileGrid({ photos }: { photos: Photo[] }) {
return (
Expand All @@ -29,7 +29,6 @@ export default function ProfilePage() {
useEffect(() => {
const loadPhotos = async () => {
if (user) {
// We know this is safe because we check if the user is signed in
const userPhotos = await getUserPhotos(user.emailAddresses[0]?.emailAddress as string);
setPhotos(userPhotos);
}
Expand All @@ -38,17 +37,19 @@ export default function ProfilePage() {
loadPhotos();
}, [user]);

const displayName = user?.fullName || user?.firstName || user?.username || "User";

return (
<div className="p-4 font-sans">
<div className="flex items-center mb-4">
<img
src="https://via.placeholder.com/80"
src={user?.imageUrl || "https://via.placeholder.com/80"}
alt="Profile"
className="w-20 h-20 rounded-full"
/>
<div className="ml-4">
<h1 className="text-2xl font-bold">{user?.emailAddresses[0]?.emailAddress}</h1>
<p className="text-gray-500">{photos.length} posts</p>
<h1 className="text-2xl font-bold text-white">{displayName}</h1>
<p className="text-gray-500">{user?.emailAddresses[0]?.emailAddress}</p>
</div>
<div className="ml-auto">
<SignOutButton>
Expand All @@ -63,6 +64,7 @@ export default function ProfilePage() {
<div className="mt-8">
<ProfileGrid photos={photos} />
</div>
<ClassList />
</div>
);
}
3 changes: 2 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { redirect } from "next/navigation";

export default function HomePage() {
redirect("/login");
// change this to /login
redirect("/feed");
}
Loading