Skip to content

Commit

Permalink
Implemented Question Card
Browse files Browse the repository at this point in the history
  • Loading branch information
Aman254 committed Jan 29, 2025
1 parent 6c7c6ff commit e3eadb2
Show file tree
Hide file tree
Showing 13 changed files with 697 additions and 221 deletions.
79 changes: 23 additions & 56 deletions app/(root)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import QuestionCard from "@/components/cards/QuestionCard";
import HomeFilter from "@/components/filters/HomeFilter";
import LocalSearch from "@/components/search/LocalSearch";
import { Button } from "@/components/ui/button";
import ROUTES from "@/constants/routes";
Expand All @@ -15,9 +17,14 @@ const questions = [
{ _id: "1", name: "React" },
{ _id: "4", name: "Performance" },
],
author: { _id: "3", name: "Emily" },
author: {
_id: "3",
name: "Emily",
image: "https://avatar.iran.liara.run/public",
},
upvotes: 10,
views: 5,
answers: 10,
createdAt: new Date(),
},
{
Expand All @@ -27,69 +34,25 @@ const questions = [
{ _id: "1", name: "React" },
{ _id: "2", name: "State Management" },
],
author: { _id: "4", name: "John" },
author: {
_id: "4",
name: "John",
image: "https://avatar.iran.liara.run/public",
},
upvotes: 15,
views: 8,
createdAt: new Date(),
},
{
_id: "3",
title: "How to secure a Node.js REST API?",
tags: [
{ _id: "3", name: "Node.js" },
{ _id: "5", name: "Security" },
],
author: { _id: "5", name: "Alice" },
upvotes: 7,
views: 12,
createdAt: new Date(),
},
{
_id: "4",
title: "What are the key differences between SQL and NoSQL databases?",
tags: [
{ _id: "6", name: "Databases" },
{ _id: "7", name: "SQL" },
{ _id: "8", name: "NoSQL" },
],
author: { _id: "6", name: "David" },
upvotes: 20,
views: 30,
createdAt: new Date(),
},
{
_id: "5",
title:
"How to improve Lighthouse performance scores for a web application?",
tags: [
{ _id: "4", name: "Performance" },
{ _id: "9", name: "Web Development" },
],
author: { _id: "7", name: "Sophia" },
upvotes: 12,
views: 18,
createdAt: new Date(),
},
{
_id: "6",
title: "What are the differences between React and Vue.js?",
tags: [
{ _id: "1", name: "React" },
{ _id: "10", name: "Vue.js" },
],
author: { _id: "8", name: "Michael" },
upvotes: 9,
views: 22,
createdAt: new Date(),
answers: 10,
createdAt: new Date("2024-12-1"),
},
];

const Home = async ({ searchParams }: SearchParams) => {
const { query = "" } = await searchParams;
const { query = "", filter = "" } = await searchParams;

const filtredQuestions = questions.filter((question) =>
question.title.toLowerCase().includes(query?.toLowerCase())
);

return (
<>
<section className="flex w-full flex-col-reverse sm:flex-row justify-between gap-4 sm:items-center">
Expand All @@ -108,10 +71,14 @@ const Home = async ({ searchParams }: SearchParams) => {
route="/"
/>
</section>
<section className="mt-11">Home Filter</section>
<section className="mt-11">
<HomeFilter />
</section>
<div className="mt-10 flex w-full flex-col gap-6">
{filtredQuestions.map((question) => (
<div>{question.title}</div>
<h1 key={question.title}>
<QuestionCard key={question._id} question={question} />
</h1>
))}
</div>
</>
Expand Down
1 change: 0 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export default async function RootLayout({
>
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem
disableTransitionOnChange
>
Expand Down
56 changes: 56 additions & 0 deletions components/Metric.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Image from "next/image";
import Link from "next/link";
import React from "react";

interface Props {
imgUrl: string;
alt: string;
value: string | number;
title: string;
href?: string;
textStyles: string;
imgStyles?: string;
isAuthor?: Boolean;
}

const Metric = ({
imgUrl,
alt,
value,
title,
href,
textStyles,
imgStyles,
isAuthor,
}: Props) => {
const metricContent = (
<>
<Image
src={imgUrl}
width={16}
height={16}
alt={alt}
className={`rounded-full object-contain ${imgStyles}`}
/>
<p className={`${textStyles} flex items-center gap-1`}>
{value}
<span
className={`small-regular line-clamp-1 ${
isAuthor ? "max-sm:hidden" : ""
}`}
>
{title}
</span>
</p>
</>
);
return href ? (
<Link href={href} className="flex-center gap-1">
{metricContent}
</Link>
) : (
<div className="flex-center gap-1">{metricContent}</div>
);
};

export default Metric;
72 changes: 72 additions & 0 deletions components/cards/QuestionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import ROUTES from "@/constants/routes";
import { getTimeStamp } from "@/lib/utils";
import Link from "next/link";
import TagCard from "./TagCard";
import Metric from "../Metric";
import { auth } from "@/auth";

interface Props {
question: Question;
}

const QuestionCard = ({
question: { _id, title, tags, author, createdAt, upvotes, answers, views },
}: Props) => {
return (
<div className="card-wrapper rounded-[10px] p-9 sm:px-11">
<div className="flex flex-col-reverse justify-between items-start gap-5 sm:flex-row">
<div>
<span className="subtle-regular text-dark400_light700 line-clamp-1 flex sm:hidden">
{getTimeStamp(createdAt)}
</span>
<Link href={ROUTES.QUESTION(_id)}>
<h3 className="sm:h3-semibold bas-semibold text-dark200_light900">
{title}
</h3>
</Link>
</div>
</div>
<div className="mt-3.5 flex w-full flex-wrap gap-2">
{tags.map((tag: Tag) => (
<TagCard key={tag._id} _id={tag._id} name={tag.name} compact />
))}
</div>
<div className="flex-between mt-6 w-full flex-wrap gap-3">
<Metric
imgUrl={author.image}
alt={author.name}
value={author.name}
title={`. asked ${getTimeStamp(createdAt)}`}
href={ROUTES.PROFILE(author._id)}
textStyles="body-medium text-dark400_light700"
isAuthor
/>
<div className="flex items-center gap-3 max-sm:flex-wrap max-sm:flex-start">
<Metric
imgUrl="/icons/like.svg"
alt=" like"
value={upvotes}
title=" Votes"
textStyles="small-medium text-dark400_light800"
/>
<Metric
imgUrl="/icons/message.svg"
alt="answers"
value={answers}
title=" Answers"
textStyles="small-medium text-dark400_light800"
/>
<Metric
imgUrl="/icons/eye.svg"
alt="views"
value={views}
title=" Views"
textStyles="small-medium text-dark400_light800"
/>
</div>
</div>
</div>
);
};

export default QuestionCard;
2 changes: 1 addition & 1 deletion components/cards/TagCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getDeviconClassName } from "@/lib/utils";
interface Props {
_id: string;
name: String;
questions: number;
questions?: number;
showCount?: boolean;
compact?: boolean;
}
Expand Down
61 changes: 61 additions & 0 deletions components/filters/HomeFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button";
import { useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { formUrlQuery, removeKeysFormUrlQuery } from "@/lib/url";

const filters = [
{ name: "React", value: "react" },
{ name: "JavaScript", value: "javascript" },
];

const HomeFilter = () => {
const router = useRouter();
const searchParams = useSearchParams();
const filterParams = searchParams.get("filter");

const [active, setActive] = useState("");

const handleTypeClick = (filter: string) => {
let newUrl = "";
if (filter === active) {
setActive("");
newUrl = removeKeysFormUrlQuery({
keysToRemove: ["filter"],
params: searchParams.toString(),
});
} else {
setActive(filter);
newUrl = formUrlQuery({
params: searchParams.toString(),
key: "filter",
value: filter.toLowerCase(),
});
}
router.push(newUrl, { scroll: false });
};

return (
<div className="mt-10 hidden flex-wrap gap-3 sm:flex">
{filters.map((filter) => (
<Button
key={filter.value}
className={cn(
`body-medium rounded-lg px-6 py-3 capitalize shadow-none`,
active === filter.value
? "bg-primary-100 text-primary-500 hover:bg-primary-100 dark:bg-dark-400 dark:text-primary-500 dark:hover:bg-dark-400"
: "bg-light-800 text-light-500 hover:bg-light-800 dark:bg-dark-300 dark:text-light-500 dark:hover:bg-dark-300"
)}
onClick={() => {
handleTypeClick(filter.value);
}}
>
{filter.name}
</Button>
))}
</div>
);
};

export default HomeFilter;
1 change: 1 addition & 0 deletions constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const ROUTES = {
SIGN_UP: "/sign-up",
ASK_QUESTION: "/ask-question",
PROFILE: (id: string) => `/profile/${id}`,
QUESTION: (id: string) => `/question/${id}`,
TAGS: (id: string) => `/tags/${id}`,
};
export default ROUTES;
35 changes: 35 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,38 @@ export const getDeviconClassName = (techName: String) => {
? `${techMap[normalizedTechName]} colored`
: "devicon-devicon-plain";
};
export const getTimeStamp = (date: Date): string => {
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);

if (diffInSeconds < 60) {
return `${diffInSeconds} seconds ago`;
}

const diffInMinutes = Math.floor(diffInSeconds / 60);
if (diffInMinutes < 60) {
return `${diffInMinutes} minutes ago`;
}

const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) {
return `${diffInHours} hours ago`;
}

const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays < 30) {
return `${diffInDays} days ago`;
}

const diffInMonths = Math.floor(diffInDays / 30);
if (diffInMonths < 12) {
return `${diffInMonths} months ago`;
}

const diffInYears = Math.floor(diffInMonths / 12);
return `${diffInYears} years ago`;
};

// Example usage:
const someDate = new Date("2024-12-25T10:00:00Z");
console.log(getTimeStamp(someDate)); // Output will vary based on the current date and time
4 changes: 3 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [{ protocol: "https", hostname: "avatar.iran.liara.run" }],
},
};

export default nextConfig;
Loading

0 comments on commit e3eadb2

Please sign in to comment.