Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into fix/backend-api-issues
Browse files Browse the repository at this point in the history
  • Loading branch information
yunho7687 committed Jul 26, 2024
2 parents f5ba144 + 2d4b742 commit 222a25b
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 162 deletions.
54 changes: 53 additions & 1 deletion client/src/components/ui/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,58 @@ const InboxIcon: React.FC<BottomNavIconProps> = ({
);
};

export { ChevronRightIcon, InboxIcon, LogoutIcon, SettingsIcon };

function HelpCircleIcon(props: BottomNavIconProps) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"
stroke="#0B1920"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01"
stroke="#0B1920"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

function PaymentIcon(props: BottomNavIconProps) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M21 4H3a2 2 0 00-2 2v12a2 2 0 002 2h18a2 2 0 002-2V6a2 2 0 00-2-2zM1 10h22"
stroke="#0B1920"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

export { HelpCircleIcon, PaymentIcon };
const AboutInfoIcon: React.FC<BottomNavIconProps> = ({
className = "text-penni-alert-warning",
}) => {
Expand Down Expand Up @@ -350,4 +402,4 @@ const AboutInfoIcon: React.FC<BottomNavIconProps> = ({
);
};

export { AboutInfoIcon, ChevronRightIcon, InboxIcon, LogoutIcon, SettingsIcon };
export { AboutInfoIcon };
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import PersonDetail from "./person-detail";
import PersonDetail from "../person-detail";

export interface BidderOfferProps {
name: string;
Expand All @@ -25,7 +25,8 @@ export default function BidderOfferCard({
className,
onClick,
}: BidderOfferProps) {
const defaultClassName = "relative m-4 rounded-lg border border-gray-300 p-4";
const defaultClassName =
"cursor-pointer relative m-4 rounded-lg border border-gray-300 p-4";
return (
<div className={className ? className : defaultClassName} onClick={onClick}>
<PersonDetail personName={name} personImg={profile} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import jwt from "jsonwebtoken";
import Image from "next/image";
import { useRouter } from "next/router";
import * as React from "react";

export default function TaskTopBar() {
const router = useRouter();
const handleOnClick = async () => {
const token = localStorage.getItem("token");
if (!token) throw new Error("No token found");

const decoded = jwt.decode(token) as { user_id: string };
const user_id = decoded.user_id;
router.push(`/poster/${user_id}/profile`);
};
return (
<div className="sticky top-0 z-10 flex w-full items-center justify-between bg-white px-4">
<p className="text-xl font-semibold">Penni</p>
<button>
<button onClick={handleOnClick}>
<Image
// TO DO replace it with api fetch user profile.
src="/profile-2.svg"
Expand Down
33 changes: 33 additions & 0 deletions client/src/hooks/use-fetch-data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

import { axiosInstance } from "@/lib/api";

function useFetchData(apiEndpoint: string, trigger: boolean) {
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>("");

useEffect(() => {
if (!trigger) return;
const fetchData = async () => {
try {
const response = await axiosInstance.get(apiEndpoint);
setData(response.data);
} catch (error: unknown) {
if (error instanceof Error) {
setError(error.message);
} else {
setError("An unexpected error occurred");
}
} finally {
setLoading(false);
}
};
fetchData();
}, [apiEndpoint]);

return { data, loading, error };
}

export default useFetchData;
2 changes: 1 addition & 1 deletion client/src/pages/component-showcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState } from "react";

import BottomNav from "@/components/ui/bidder/bottom-nav";
import TaskCard from "@/components/ui/bidder/task-card";
import BidderOfferCard from "@/components/ui/bidder-offer-card";
import { Button } from "@/components/ui/button";
import {
ErrorCallout,
Expand Down Expand Up @@ -31,6 +30,7 @@ import {
SingleLineInput,
} from "@/components/ui/inputs";
import PersonDetail from "@/components/ui/person-detail";
import BidderOfferCard from "@/components/ui/poster/bidder-offer-card";
import PosterTaskCard from "@/components/ui/poster/task-card";
import ProfileTag from "@/components/ui/profile-tags";
import TopNavtab from "@/components/ui/top-navtab";
Expand Down
90 changes: 90 additions & 0 deletions client/src/pages/poster/[posterid]/profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import jwt from "jsonwebtoken";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

import useFetchData from "@/hooks/use-fetch-data";
import { axiosInstance } from "@/lib/api";

import {
AboutInfoIcon,
EditIcon,
HelpCircleIcon,
LogoutIcon,
PaymentIcon,
SettingsIcon,
} from "../../../components/ui/icons";
import { PersonImg } from "../../../components/ui/person-detail";
import ProfileTag from "../../../components/ui/profile-tags";

const ProfilePage: React.FC = () => {
const router = useRouter();
const { posterid } = router.query;
const queryReady = typeof posterid === "string";

const {
data: user,
loading: userLoading,
error: userError,
} = useFetchData(`/app/profiles/${posterid}/`, queryReady);
if (userLoading) return <div>Loading...</div>;
if (userError) return <div>Error: {userError}</div>;
const token = localStorage.getItem("token");
if (!token) throw new Error("No token found");
const decoded = jwt.decode(token) as { email: string };
const email = decoded.email;
// img src needs to change to user.avatar_url later
return (
<div className="m-0">
<div className="mt-20 flex flex-col items-center">
<PersonImg personImg="" size={120} />
<p className="mt-4 text-t3 font-semibold text-penni-text-regular-light-mode">
{user.full_name}
</p>
<p className="text-sh font-normal text-penni-text-secondary-light-mode">
{email}
</p>
</div>
<div className="mt-6">
<ProfileTag
icon={EditIcon}
title="Edit Profile"
description="Update your personal information"
link=""
/>
<ProfileTag
icon={PaymentIcon}
title="Payments"
description="To make payments"
link=""
/>
<ProfileTag
icon={SettingsIcon}
title="Account Settings"
description="Lorem ipsum dolor sit amet."
link=""
/>
<ProfileTag
icon={AboutInfoIcon}
title="About"
description="Lorem ipsum dolor sit amet."
link=""
/>
<ProfileTag
icon={HelpCircleIcon}
title="Help Centre"
description="Lorem ipsum dolor sit amet."
link=""
/>

<ProfileTag
icon={LogoutIcon}
title="Logout"
description=""
nestedContent=""
/>
</div>
</div>
);
};

export default ProfilePage;
2 changes: 1 addition & 1 deletion client/src/pages/poster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ErrorCallout } from "@/components/ui/callout";
import PosterTaskCard, {
PosterTaskCardProps,
} from "@/components/ui/poster/task-card";
import TaskTopBar from "@/components/ui/task-top-bar";
import TaskTopBar from "@/components/ui/poster/task-top-bar";
import { axiosInstance } from "@/lib/api";

export function Create() {
Expand Down
86 changes: 61 additions & 25 deletions client/src/pages/poster/tasks/[taskid]/[bidderid]/bid-details.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,71 @@
import { useRouter } from "next/router";
import React from "react";
import { useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import Header from "@/components/ui/header";
import PersonDetail from "@/components/ui/person-detail";
import useFetchData from "@/hooks/use-fetch-data";
import { axiosInstance } from "@/lib/api";

export default function BidDetail() {
const router = useRouter();
if (router.query.bidder === undefined || router.query.taskid === undefined) {
return <>Loading...</>;
}
const bidder = JSON.parse(String(router.query.bidder));
const taskid = String(router.query.taskid);
function OnClick() {
// axios.post to api
router.push(`/poster/task/${taskid}/${bidder.id}/payment`);
}
return (
<div className="w-screenbg-green-400 h-lvh">
<Header title="Bid Details" className="sticky top-0 z-10 w-full" />
<div className="absolute flex h-5/6 w-full flex-col p-4">
<PersonDetail personName={bidder.name} personImg={bidder.profile} />
<p className="mt-4 self-center">{bidder.bio}</p>
<Button
size="penni"
onClick={OnClick}
className="absolute -bottom-4 w-10/12 self-center"
>
Hire for ${bidder.price}
</Button>
const { taskid, bidderid } = router.query;
// Check if taskid and bidderid are strings
const queryReady = typeof taskid === "string" && typeof bidderid === "string";

// Fetch bids of this task
const {
data: bids,
loading: bidsLoading,
error: bidsError,
} = useFetchData(`/app/tasks/${taskid}/bids/`, queryReady);
// Fetch profiles of all users
const {
data: profiles,
loading: profilesLoading,
error: profilesError,
} = useFetchData(`/app/profiles/`, true);
if (bidsLoading || profilesLoading) return <div>Loading...</div>;

if (bidsError) return <div>Error: {bidsError}</div>;
if (profilesError) return <div>Error: {profilesError}</div>;
const OnClick = () => {
router.push(`/poster/payment`); //link to payment detail page
};

if (typeof bidderid != "string") {
return <div>Loading...</div>;
} else {
const bidInfo = bids.data.filter(
(bid: any) => bid.bidder_id === parseInt(bidderid),
)[0];
// filter profile of this bidder
const bidderProfile = profiles.filter(
(profile: any) => profile.user_id === parseInt(bidderid),
)[0];

bidInfo["avatar_url"] = ""; // source of img has error. needs to change to bidderProfile.avatar_url later
bidInfo["full_name"] = bidderProfile.full_name;
bidInfo["bio"] = bidderProfile.bio;

return (
<div className="w-screenbg-green-400 h-lvh">
<Header title="Bid Details" className="sticky top-0 z-10 w-full" />
<div className="absolute flex h-5/6 w-full flex-col border-t border-penni-grey-border-light-mode p-4">
<PersonDetail
personName={bidInfo.full_name}
personImg={bidInfo.avatar_url}
/>
<p className="mt-4">{bidInfo.bio}</p>
<Button
size="penni"
onClick={OnClick}
className="absolute -bottom-4 w-10/12 self-center"
>
Hire for {bidInfo.price}
</Button>
</div>
</div>
</div>
);
);
}
}
Loading

0 comments on commit 222a25b

Please sign in to comment.