Repository: LetsGetTechnical/elecretanta
Pull request #319: Cody/131 add back to dashboard link
Author: login: kepsteen
Created: 2025-04-14T17:55:38Z | Merged: 2025-04-24T03:25:22Z

Closes #131 

- Reused back to dashboard link from  `/create-group-page` and created a new component `<LinkCustom />`
- Added unit tests
- extracted type into its own file

![Recording 2025-04-14 at 10 54 18](https://github.com/user-attachments/assets/0d78a537-003e-4b8a-87b0-9fc960e5d6d7)


Linked issue #131: As a user, I should be able to click a `dashboard` button while on my profile and navigate back to `/dashboard`
## Feedback from Hackathon: 
Navigation via the Elfgorithm logo to `/dashboard` from user profile is not intuitive.

## Acceptance Criteria
- Button routes user to /dashboard onclick
- Button is clearly labelled i.e. text says 'dashboard'


![Image](https://github.com/user-attachments/assets/9e9d06c5-0bb6-4fdb-b20f-6d86ebfb3873)

 

Diff:
diff --git a/app/create-group-page/page.tsx b/app/create-group-page/page.tsx
index 6a8c4f9a..d9c9500d 100644
--- a/app/create-group-page/page.tsx
+++ b/app/create-group-page/page.tsx
@@ -35,7 +35,7 @@ import {
 import { Textarea } from '@/components/TextArea/textarea';
 import { ImageSelector } from '@/components/ImageSelector/ImageSelector';
 import { useRouter } from 'next/navigation';
-import Link from 'next/link';
+import LinkCustom from '@/components/LinkCustom/LinkCustom';
 
 const priceRanges = [
   { label: '$10 - $20', value: '10-20' },
@@ -105,15 +105,12 @@ export default function CreateGroupPage() {
   }
   const giftDrawingDate = form.watch('drawing_date');
   return (
-    <div className=" flex justify-center align-center flex-col px-4 md:px-16 lg:px-32 xl:px-52 pt-12">
+    <div className="create-group-page flex justify-center align-center flex-col px-4 md:px-16 lg:px-32 xl:px-52 pt-12">
       <div className="flex flex-row">
-        <Link
-          href={'/dashboard'}
-          className="flex items-center gap-1 text-sm text-primary-foreground m-5"
-        >
+        <LinkCustom href="/dashboard">
           <ChevronLeft size={16} strokeWidth={2.25} />
-          <span>Back to Dashboard</span>
-        </Link>
+          Back to Dashboard
+        </LinkCustom>
       </div>
       <div className="flex items-center justify-center h-full">
         <div className="bg-white w-full xl:w-1/2 mb-5 flex justify-center align-center rounded flex-col ">
diff --git a/app/gift-exchanges/[id]/edit/page.tsx b/app/gift-exchanges/[id]/edit/page.tsx
index 4ccdf55f..ff5a537f 100644
--- a/app/gift-exchanges/[id]/edit/page.tsx
+++ b/app/gift-exchanges/[id]/edit/page.tsx
@@ -39,6 +39,7 @@ import { useEffect, useState } from 'react';
 import Link from 'next/link';
 import { createClient } from '@/lib/supabase/client';
 import { GiftExchange } from '@/app/types/giftExchange';
+import LinkCustom from '@/components/LinkCustom/LinkCustom';
 
 const priceRanges = [
   { label: '$10 - $20', value: '10-20' },
@@ -174,15 +175,12 @@ export default function CreateGroupPage() {
 
   const giftDrawingDate = form.watch('drawing_date');
   return (
-    <div className=" flex justify-center align-center flex-col px-4 md:px-16 lg:px-32 xl:px-52 pt-12">
+    <div className="edit-group-page flex justify-center align-center flex-col px-4 md:px-16 lg:px-32 xl:px-52 pt-12">
       <div className="flex flex-row">
-        <Link
-          href={'/dashboard'}
-          className="flex items-center gap-1 text-sm text-primary-foreground m-5"
-        >
+        <LinkCustom href="/dashboard">
           <ChevronLeft size={16} strokeWidth={2.25} />
           <span>Back to Dashboard</span>
-        </Link>
+        </LinkCustom>
       </div>
       <div className="flex items-center justify-center h-full">
         <div className="bg-white w-full xl:w-1/2 mb-5 flex justify-center align-center rounded flex-col ">
diff --git a/app/profile/page.tsx b/app/profile/page.tsx
index 99e57672..e9df31c4 100644
--- a/app/profile/page.tsx
+++ b/app/profile/page.tsx
@@ -3,6 +3,8 @@ import ProfileCard from '@/components/ProfileCard/ProfileCard';
 import { useEffect, useState } from 'react';
 import { Profile } from '@/app/types/profile';
 import LoadingCard from '@/components/LoadingCard/LoadingCard';
+import { ChevronLeft } from 'lucide-react';
+import LinkCustom from '@/components/LinkCustom/LinkCustom';
 
 export default function PersonalProfile() {
   const [profile, setProfile] = useState<Profile | null>(null);
@@ -35,7 +37,13 @@ export default function PersonalProfile() {
   }
 
   return (
-    <section className="h-svh flex justify-center items-start py-16">
+    <section className="profile-page h-svh flex flex-col items-center py-16 px-4 md:px-16 lg:px-32 xl:px-52">
+      <div className="flex flex-row w-full">
+        <LinkCustom href="/dashboard">
+          <ChevronLeft size={16} strokeWidth={2.25} />
+          Back to Dashboard
+        </LinkCustom>
+      </div>
       <div className="max-w-5xl">
         {profile ? (
           <ProfileCard profile={profile} showEditButton />
diff --git a/components/GiftExchangeHeader/GiftExchangeHeader.tsx b/components/GiftExchangeHeader/GiftExchangeHeader.tsx
index cd909521..b12b792a 100644
--- a/components/GiftExchangeHeader/GiftExchangeHeader.tsx
+++ b/components/GiftExchangeHeader/GiftExchangeHeader.tsx
@@ -26,6 +26,7 @@ import {
 } from '../AlertDialogue/AlertDialgoue';
 import { GiftExchangeMember } from '@/app/types/giftExchangeMember';
 import { Button } from '@/components/Button/button';
+import LinkCustom from '../LinkCustom/LinkCustom';
 // initialize type for exchange data response
 
 interface MembersListProps {
@@ -150,10 +151,10 @@ export const GiftExchangeHeader = ({
   return (
     <>
       <div className="flex justify-between mb-6">
-        <Link href={'/dashboard'} className="flex items-center gap-1 text-sm">
+        <LinkCustom href="/dashboard">
           <ChevronLeft size={16} strokeWidth={2.25} />
-          <span>Back to Dashboard</span>
-        </Link>
+          Back to Dashboard
+        </LinkCustom>
         {isOwner && (
           <Link
             href={`/gift-exchanges/${giftExchangeData.id}/edit`}
@@ -165,7 +166,7 @@ export const GiftExchangeHeader = ({
         )}
       </div>
 
-      <section className="flex flex-col grow-0 gap-8 sm:flex-row">
+      <section className="gift-exchange-header flex flex-col grow-0 gap-8 sm:flex-row">
         <div className="w-36 h-36 grow-0 shrink-0">
           <img
             className="w-full h-full rounded-xl ring-4 ring-white"
diff --git a/components/LinkCustom/ILinkCustomProps.ts b/components/LinkCustom/ILinkCustomProps.ts
new file mode 100644
index 00000000..601c0584
--- /dev/null
+++ b/components/LinkCustom/ILinkCustomProps.ts
@@ -0,0 +1,10 @@
+// Copyright (c) Gridiron Survivor.
+// Licensed under the MIT License.
+
+import { ReactNode } from 'react';
+
+export interface ILinkCustomProps {
+  href: string;
+  children?: ReactNode;
+  className?: string;
+}
diff --git a/components/LinkCustom/LinkCustom.test.tsx b/components/LinkCustom/LinkCustom.test.tsx
new file mode 100644
index 00000000..b98839db
--- /dev/null
+++ b/components/LinkCustom/LinkCustom.test.tsx
@@ -0,0 +1,33 @@
+// Copyright (c) Gridiron Survivor.
+// Licensed under the MIT License.
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import LinkCustom from './LinkCustom';
+import { describe } from 'node:test';
+
+describe('LinkCustom Component', () => {
+  it('renders with default props', () => {
+    render(<LinkCustom href="https://example.com" />);
+    const link = screen.getByTestId('link-custom');
+    expect(link).toBeInTheDocument();
+    expect(link).toHaveAttribute('href', 'https://example.com');
+  });
+
+  it('renders children', () => {
+    render(<LinkCustom href="https://example.com">{'Link Text'}</LinkCustom>);
+    const link = screen.getByTestId('link-custom');
+    expect(link).toBeInTheDocument();
+    expect(link).toHaveTextContent('Link Text');
+  });
+
+  it('renders with custom classes', () => {
+    render(
+      <LinkCustom href="https://example.com" className="test-class">
+        {'Link Text'}
+      </LinkCustom>,
+    );
+    const link = screen.getByTestId('link-custom');
+    expect(link).toHaveClass('test-class');
+  });
+});
diff --git a/components/LinkCustom/LinkCustom.tsx b/components/LinkCustom/LinkCustom.tsx
new file mode 100644
index 00000000..bae85b4a
--- /dev/null
+++ b/components/LinkCustom/LinkCustom.tsx
@@ -0,0 +1,34 @@
+// Copyright (c) Gridiron Survivor.
+// Licensed under the MIT License.
+
+import { cn } from '@/lib/utils';
+import Link from 'next/link';
+import { JSX } from 'react';
+import { ILinkCustomProps } from './ILinkCustomProps';
+/**
+ * A navigation link component that uses Next.js Link component.
+ * @param {string} href - The URL to link to.
+ * @param {ReactNode} [children] - The content to display inside the link.
+ * @param {string} [className] - The class name to apply to the link.
+ * @returns A custom navigation link component.
+ */
+const LinkCustom = ({
+  href,
+  children,
+  className,
+}: ILinkCustomProps): JSX.Element => {
+  return (
+    <Link
+      href={href}
+      data-testid="link-custom"
+      className={cn(
+        'LinkCustom flex items-center gap-1 text-sm text-primary-foreground m-5',
+        className,
+      )}
+    >
+      {children}
+    </Link>
+  );
+};
+
+export default LinkCustom;


Reviews:
user: login: Danielle254
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T18:48:03Z
user: login: shashilo
body: @kepsteen Please review my comments.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T19:51:54Z
user: login: kepsteen
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T22:57:00Z
user: login: shashilo
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T13:32:33Z
user: login: shashilo
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-22T14:11:44Z
user: login: Danielle254
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T15:33:40Z

Review comments:
diff_hunk: @@ -35,7 +37,16 @@ export default function PersonalProfile() {
   }
 
   return (
-    <section className="h-svh flex justify-center items-start py-16">
+    <section className="h-svh flex flex-col items-center py-16 px-4 md:px-16 lg:px-32 xl:px-52">
path: app/profile/page.tsx
user: login: shashilo
body: For each page, we need to add the className for that page. It will be much easier to debug.
created_at: 2025-04-14T19:50:05Z
author_association: COLLABORATOR
diff_hunk: @@ -35,7 +37,16 @@ export default function PersonalProfile() {
   }
 
   return (
-    <section className="h-svh flex justify-center items-start py-16">
+    <section className="h-svh flex flex-col items-center py-16 px-4 md:px-16 lg:px-32 xl:px-52">
+      <div className="flex flex-row w-full">
+        <Link
+          href={'/dashboard'}
+          className="flex items-center gap-1 text-sm text-primary-foreground m-5"
+        >
+          <ChevronLeft size={16} strokeWidth={2.25} />
+          <span>Back to Dashboard</span>
path: app/profile/page.tsx
user: login: shashilo
body: Is the `<span>` required here? 
created_at: 2025-04-14T19:50:24Z
author_association: COLLABORATOR
diff_hunk: @@ -35,7 +37,16 @@ export default function PersonalProfile() {
   }
 
   return (
-    <section className="h-svh flex justify-center items-start py-16">
+    <section className="h-svh flex flex-col items-center py-16 px-4 md:px-16 lg:px-32 xl:px-52">
+      <div className="flex flex-row w-full">
+        <Link
+          href={'/dashboard'}
+          className="flex items-center gap-1 text-sm text-primary-foreground m-5"
path: app/profile/page.tsx
user: login: shashilo
body: Do we have a default set of styles for links? I'd prefer not to add the tailwind classes as we're using them, but to embed them into the component. If we need to, create a custom component with variants that house the tailwind classes. Otherwise, every time we call links, we have to get the exact same classNames to get the same look and feel. 
created_at: 2025-04-14T19:51:47Z
author_association: COLLABORATOR
diff_hunk: @@ -35,7 +37,16 @@ export default function PersonalProfile() {
   }
 
   return (
-    <section className="h-svh flex justify-center items-start py-16">
+    <section className="h-svh flex flex-col items-center py-16 px-4 md:px-16 lg:px-32 xl:px-52">
path: app/profile/page.tsx
user: login: kepsteen
body: By this, do you mean add the class `profile-page` to the section? 
created_at: 2025-04-16T22:57:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,39 @@
+// Copyright (c) Gridiron Survivor.
+// Licensed under the MIT License.
+
+import { cn } from '@/lib/utils';
+import Link from 'next/link';
+import { ReactNode, JSX } from 'react';
+type NavLinkProps = {
+  href: string;
+  children?: ReactNode;
+  className?: string;
+};
+
+/**
+ * A navigation link component that uses Next.js Link component.
+ * @param {string} href - The URL to link to.
+ * @param {ReactNode} [children] - The content to display inside the link.
+ * @param {string} [className] - The class name to apply to the link.
+ * @returns A navigation link component.
+ */
+const LinkCustom = ({
+  href,
+  children,
+  className,
+}: NavLinkProps): JSX.Element => {
+  return (
+    <Link
+      href={href}
+      data-testid="link-custom"
+      className={cn(
+        'flex items-center gap-1 text-sm text-primary-foreground m-5',
path: components/NavLink/LinkCustom.tsx
user: login: shashilo
body: Same here. Add a className for `linkCustom`. Identifying our components and pages with a simple classname will help with debugging in the future. 
created_at: 2025-04-18T13:30:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,39 @@
+// Copyright (c) Gridiron Survivor.
+// Licensed under the MIT License.
+
+import { cn } from '@/lib/utils';
+import Link from 'next/link';
+import { ReactNode, JSX } from 'react';
+type NavLinkProps = {
path: components/NavLink/LinkCustom.tsx
user: login: shashilo
body: Let's stick to Interfaces and name this INavLinkProps. Also, move this out into its own file. 
created_at: 2025-04-18T13:31:21Z
author_association: COLLABORATOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-14T17:55:43Z
body: [vc]: #iz3OecezNaWnlY93Cu1nF1TkdCFRYouhn41hjadD69c=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJlbGVjcmV0YW50YSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJlbGVjcmV0YW50YS1naXQtY29keS0xMzEtYWRkLWRhc2hib2FyZC1saW5rLWxldHNnZXR0ZWNobmljYWwudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbGV0c2dldHRlY2huaWNhbC9lbGVjcmV0YW50YS9BQ3lDcUdVclcyUlJkVWJaM2tvVjNRNXI4WGR0IiwicHJldmlld1VybCI6ImVsZWNyZXRhbnRhLWdpdC1jb2R5LTEzMS1hZGQtZGFzaGJvYXJkLWxpbmstbGV0c2dldHRlY2huaWNhbC52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwicm9vdERpcmVjdG9yeSI6bnVsbH0seyJuYW1lIjoiZWxlY3JldGFudGEtc3Rvcnlib29rIiwicm9vdERpcmVjdG9yeSI6bnVsbCwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2xldHNnZXR0ZWNobmljYWwvZWxlY3JldGFudGEtc3Rvcnlib29rLzloanBxV2NtOVFhRUhMTjMxMzdzNHlTcXM5b2EiLCJwcmV2aWV3VXJsIjoiZWxlY3JldGFudGEtc3Rvcnlib29rLWdpdC1jb2R5LTEzMS1hZGQtN2QwZmRmLWxldHNnZXR0ZWNobmljYWwudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJlbGVjcmV0YW50YS1zdG9yeWJvb2stZ2l0LWNvZHktMTMxLWFkZC03ZDBmZGYtbGV0c2dldHRlY2huaWNhbC52ZXJjZWwuYXBwIn19LHsibmFtZSI6ImVsZWNyZXRhbnRhLXVuaXQtdGVzdCIsInJvb3REaXJlY3RvcnkiOm51bGwsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9sZXRzZ2V0dGVjaG5pY2FsL2VsZWNyZXRhbnRhLXVuaXQtdGVzdC9vTjJycTh3Q01hbXRCZ0x1YWYzYWttWW9HS254IiwicHJldmlld1VybCI6ImVsZWNyZXRhbnRhLXVuaXQtdGVzdC1naXQtY29keS0xMzEtYWRkLTI3MGEwYS1sZXRzZ2V0dGVjaG5pY2FsLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZWxlY3JldGFudGEtdW5pdC10ZXN0LWdpdC1jb2R5LTEzMS1hZGQtMjcwYTBhLWxldHNnZXR0ZWNobmljYWwudmVyY2VsLmFwcCJ9fV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **elecretanta** | ✅ Ready ([Inspect](https://vercel.com/letsgettechnical/elecretanta/ACyCqGUrW2RRdUbZ3koV3Q5r8Xdt)) | [Visit Preview](https://elecretanta-git-cody-131-add-dashboard-link-letsgettechnical.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/elecretanta-git-cody-131-add-dashboard-link-letsgettechnical.vercel.app?via=pr-comment-feedback-link) | Apr 23, 2025 7:51pm |
| **elecretanta-storybook** | ✅ Ready ([Inspect](https://vercel.com/letsgettechnical/elecretanta-storybook/9hjpqWcm9QaEHLN3137s4ySqs9oa)) | [Visit Preview](https://elecretanta-storybook-git-cody-131-add-7d0fdf-letsgettechnical.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/elecretanta-storybook-git-cody-131-add-7d0fdf-letsgettechnical.vercel.app?via=pr-comment-feedback-link) | Apr 23, 2025 7:51pm |
| **elecretanta-unit-test** | ✅ Ready ([Inspect](https://vercel.com/letsgettechnical/elecretanta-unit-test/oN2rq8wCMamtBgLuaf3akmYoGKnx)) | [Visit Preview](https://elecretanta-unit-test-git-cody-131-add-270a0a-letsgettechnical.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/elecretanta-unit-test-git-cody-131-add-270a0a-letsgettechnical.vercel.app?via=pr-comment-feedback-link) | Apr 23, 2025 7:51pm |




author_association: NONE
user: login: Danielle254
created_at: 2025-04-14T18:47:12Z
body: Looks good! LGTM
author_association: CONTRIBUTOR