Skip to content

Commit

Permalink
Project Details page
Browse files Browse the repository at this point in the history
  • Loading branch information
gilhanan committed Nov 2, 2023
1 parent 992c7b6 commit 14be1a1
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 21 deletions.
1 change: 1 addition & 0 deletions e2e/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Project {
id: string;
name: string;
category: string;
url: string;
Expand Down
6 changes: 4 additions & 2 deletions e2e/specs/projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ for (const { route, path } of paths) {
test.describe("projects", () => {
const projects = [
{
id: "chat-gpt-rtl",
name: "ChatGPT RTL",
category: "Chrome Extension",
url: "https://chrome.google.com/webstore/detail/chatgpt-rtl/nabcbpmmefiigmjpopfciegmlgihkofd",
Expand All @@ -40,7 +41,7 @@ for (const { route, path } of paths) {
] satisfies Project[];

for (const project of projects) {
const { name, category, url, repo } = project;
const { name, category, id, repo } = project;

test.describe(name, () => {
test("can be rendered", async ({ projects }) => {
Expand Down Expand Up @@ -68,7 +69,8 @@ for (const { route, path } of paths) {
const responsePromise$ = context.waitForEvent(
"response",
(response) =>
response.url().includes(url) && response.status() === 200,
response.url().includes(`/projects/${id}`) &&
response.status() === 200,
);

await projects.clickOnProjectTileLink(project);
Expand Down
1 change: 1 addition & 0 deletions public/images/chrome-web-store.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/images/github-dark.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/images/github.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/app/components/ButtonLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render, screen } from "@testing-library/react";
import { ButtonLink, ButtonLinkProps } from "@components/ButtonLink";

describe("ButtonLink", () => {
const props: ButtonLinkProps = {
url: "https://example.com",
text: "Example Text",
lightSrc: "light-image.png",
darkSrc: "dark-image.png",
};

it("should render the link", () => {
render(<ButtonLink {...props} />);
const linkElement = screen.getByRole("link");
expect(linkElement).toHaveAttribute("href", props.url);
expect(linkElement).toHaveAttribute("target", "_blank");
});

it("should render the provided text", () => {
render(<ButtonLink {...props} />);
expect(screen.getByText(props.text)).toBeInTheDocument();
});

it("should render a ThemedImage component with the provided props", () => {
render(<ButtonLink {...props} />);
expect(
screen.getByRole("img", {
name: `${props.text} (dark)`,
}),
).toBeInTheDocument();
expect(
screen.getByRole("img", {
name: `${props.text} (light)`,
}),
).toBeInTheDocument();
});
});
32 changes: 32 additions & 0 deletions src/app/components/ButtonLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from "next/link";
import { ThemedImage } from "@components/ThemedImage";

export interface ButtonLinkProps {
url: string;
text: string;
lightSrc: string;
darkSrc: string;
}

export function ButtonLink({
url,
text,
lightSrc,
darkSrc,
}: ButtonLinkProps): ReturnType<React.FC> {
return (
<Link
href={url}
target="_blank"
className="flex gap-2 text-sm items-center p-1 border rounded-md shadow-sm hover:bg-slate-100 dark:hover:bg-slate-800"
>
<ThemedImage
lightSrc={lightSrc}
darkSrc={darkSrc}
width={30}
alt={text}
/>
<span className="text-primary">{text}</span>
</Link>
);
}
69 changes: 69 additions & 0 deletions src/app/components/ProjectDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { FiTag } from "react-icons/fi";
import chromeWebStore from "@images/chrome-web-store.svg";
import githubLight from "@images/github.svg";
import githubDark from "@images/github-dark.svg";
import { Project } from "@shared/models";
import { ThemedImage } from "@components/ThemedImage";
import { ButtonLink } from "@components/ButtonLink";

interface ProjectDetailsProps {
project: Project;
}

export function ProjectDetails({
project: {
title,
category,
url,
repo,
description,
images: { light, dark },
},
}: ProjectDetailsProps): ReturnType<React.FC> {
return (
<section className="mt-12 sm:mt-6">
<div className="flex sm:flex-row flex-col gap-8">
<div className="w-full sm:w-80 flex flex-col gap-8">
<ThemedImage
lightSrc={light}
darkSrc={dark}
className="border"
alt="Project tile image"
sizes="100vw"
style={{
width: "100%",
height: "auto",
}}
/>
<div>
<h2 className="text-primary">Links</h2>
<div className="flex flex-col gap-2">
<ButtonLink
url={url}
text="Get extension"
lightSrc={chromeWebStore}
darkSrc={chromeWebStore}
/>
<ButtonLink
url={repo}
text="Source code"
lightSrc={githubLight}
darkSrc={githubDark}
/>
</div>
</div>
</div>
<div>
<div className="flex flex-col gap-1">
<h1 className="text-3xl text-primary">{title}</h1>
<div className="flex gap-2 items-center text-secondary">
<FiTag className="w-4 h-4" />
<span className="text-sm">{category}</span>
</div>
</div>
<div className="mt-4 text-secondary">{description}</div>
</div>
</div>
</section>
);
}
4 changes: 3 additions & 1 deletion src/app/components/ProjectTile.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Project } from "@shared/models";
import { ProjectTile } from "@components/ProjectTile";

const project: Project = {
id: "1",
title: "Test Project",
url: "https://testproject.com",
category: "Chrome Extension",
Expand All @@ -11,6 +12,7 @@ const project: Project = {
light: "/test/light.jpg",
dark: "/test/dark.jpg",
},
description: <>Test project description</>,
};

describe("ProjectTile", () => {
Expand Down Expand Up @@ -51,6 +53,6 @@ describe("ProjectTile", () => {
screen.getByRole("link", {
name: `View ${project.title} ${project.category}`,
}),
).toHaveAttribute("href", project.url);
).toHaveAttribute("href", `/projects/${project.id}`);
});
});
4 changes: 2 additions & 2 deletions src/app/components/ProjectTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Project } from "@shared/models";
import { ThemedImage } from "@components/ThemedImage";

export function ProjectTile({
id,
title,
url,
category,
repo,
images: { light, dark },
Expand All @@ -20,7 +20,7 @@ export function ProjectTile({
>
<FiCode />
</Link>
<Link href={url} target="_blank" aria-label={`View ${title} ${category}`}>
<Link href={`/projects/${id}`} aria-label={`View ${title} ${category}`}>
<div className="flex flex-col relative divide-y border rounded-xl shadow-md hover:shadow-xl">
<ThemedImage
lightSrc={light}
Expand Down
6 changes: 6 additions & 0 deletions src/app/components/Projects.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Projects } from "@components/Projects";
jest.mock("../data/projects", () => ({
projects: [
{
id: "1",
title: "First project",
category: "Chrome Extension",
url: "https://www.example.com",
Expand All @@ -14,8 +15,10 @@ jest.mock("../data/projects", () => ({
light: "/images/first-project-light.png",
dark: "/images/first-project-dark.png",
},
description: <>First project description</>,
},
{
id: "2",
title: "Second project",
category: "Chrome Extension",
url: "https://www.example.com",
Expand All @@ -24,8 +27,10 @@ jest.mock("../data/projects", () => ({
light: "/images/second-project-light.png",
dark: "/images/second-project-dark.png",
},
description: <>Second project description</>,
},
{
id: "3",
title: "Third project",
category: "Web Development" as Category,
url: "https://www.example.com",
Expand All @@ -34,6 +39,7 @@ jest.mock("../data/projects", () => ({
light: "/images/third-project-light.png",
dark: "/images/third-project-dark.png",
},
description: <>Third project description</>,
},
] satisfies Project[],
}));
Expand Down
16 changes: 0 additions & 16 deletions src/app/data/projects.ts

This file was deleted.

75 changes: 75 additions & 0 deletions src/app/data/projects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Project } from "@shared/models";
import light from "@images/projects/chat-gpt-rtl.svg";
import dark from "@images/projects/chat-gpt-rtl-dark.svg";

export const projects: Project[] = [
{
id: "chat-gpt-rtl",
title: "ChatGPT RTL",
url: "https://chrome.google.com/webstore/detail/chatgpt-rtl/nabcbpmmefiigmjpopfciegmlgihkofd ",
category: "Chrome Extension",
repo: "https://github.com/gilhanan/chat-gpt-rtl",
images: {
light,
dark,
},
description: (
<div className="flex flex-col gap-4">
<div>
<p>
ChatGPT auto right-to-left alignments for Arabic, Persian, Hebrew,
and more.
</p>
<p>
An open-source plugin that automatically identifies right-to-left
paragraphs and adjusts and arranges the text in real-time.
</p>
</div>
<div>
<h2 className="text-lg text-primary">⭐️ Features ⭐️</h2>
<ul>
<li className="flex gap-1">
<span>📝</span>
<span>
Automatically identifies RTL paragraphs and adjusts the
direction in real-time.
</span>
</li>
<li className="flex gap-1">
<span>⚙️</span>
<span>
User-friendly settings popup for configuring the enabling
functionality.
</span>
</li>
<li className="flex gap-1">
<span>🌍</span>
<span>
Supports the following RTL languages: Arabic, Persian, Hebrew,
and more.
</span>
</li>
</ul>
</div>
<div>
<h2 className="text-lg text-primary">💡 How to use 💡</h2>
<ol>
<li className="flex gap-1">
<span>1️⃣</span>
<span>Install this extension.</span>
</li>
<li className="flex gap-1">
<span>2️⃣</span>
<span>Open ChatGPT discussion.</span>
</li>
<li className="flex gap-1">
<span>3️⃣</span>
<span>Enjoy chatting with RTL support!</span>
</li>
</ol>
</div>
<p>Enjoy! 🙏</p>
</div>
),
},
];
41 changes: 41 additions & 0 deletions src/app/projects/[id]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, screen } from "@testing-library/react";
import { notFound } from "next/navigation";
import { projects } from "@data/projects";
import ProjectPage, {
generateMetadata,
generateStaticParams,
} from "@projects/[id]/page";

jest.mock("next/navigation", () => ({
notFound: jest.fn(),
}));

describe("About", () => {
it("should contain metadata", async () => {
expect(
generateMetadata({ params: { id: "chat-gpt-rtl" } }),
).resolves.toEqual({
title: "ChatGPT RTL",
});
});

it("returns the correct params for static paths", () => {
const result = generateStaticParams();
const expectedParams = projects.map(({ id }) => ({
id,
}));
expect(result).toEqual(expectedParams);
});

it("renders the project details when project is found", () => {
render(<ProjectPage params={{ id: "chat-gpt-rtl" }} />);
expect(
screen.getByRole("heading", { name: "ChatGPT RTL" }),
).toBeInTheDocument();
});

it("returns not found when project is not found", () => {
render(<ProjectPage params={{ id: "non-existent-project" }} />);
expect(notFound).toHaveBeenCalled();
});
});

0 comments on commit 14be1a1

Please sign in to comment.