Skip to content

Commit

Permalink
Merge pull request #5 from ElfenB/add-offer-overview
Browse files Browse the repository at this point in the history
Add chats list component
  • Loading branch information
ElfenB authored Mar 9, 2024
2 parents 7009c93 + f0ea77f commit 2f74298
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 39 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.eslintrc*
3 changes: 3 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ module.exports = {
// NOTE: Prettier has to be the last one to work
"prettier",
],
"eslint.rules.customizations": [
{ "rule": "perfectionist/*", "severity": "warn" }
]
};
17 changes: 0 additions & 17 deletions .eslintrc.js

This file was deleted.

55 changes: 33 additions & 22 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Redirect, Route } from "react-router-dom";
import { useAuth0 } from "@auth0/auth0-react";
import { App as CapApp } from "@capacitor/app";
import { Browser } from "@capacitor/browser";
import {
IonApp,
IonIcon,
Expand All @@ -10,47 +12,46 @@ import {
setupIonicReact,
} from "@ionic/react";
import { IonReactRouter } from "@ionic/react-router";
import { chatbox, person, home } from "ionicons/icons";
import { Overview } from "./pages/Overview";
import { chatbox, home, person } from "ionicons/icons";
import { useEffect } from "react";
import { Redirect, Route } from "react-router-dom";
import { Chats } from "./pages/Chats";
import { Login } from "./pages/Login";
import { OfferDetails } from "./pages/OfferDetails";
import { Overview } from "./pages/Overview";
import { Personal } from "./pages/Personal";

import { Settings } from "./pages/Settings";
import { Login } from "./pages/Login";
import { useEffect } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Browser } from "@capacitor/browser";
import { App as CapApp } from "@capacitor/app";

/* Theme variables */
import "./theme/variables.css";
/* Core CSS required for Ionic components to work properly */
import "@ionic/react/css/core.css";
import "@ionic/react/css/display.css";

import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/float-elements.css";
/* Basic CSS for apps built with Ionic */
import "@ionic/react/css/normalize.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/typography.css";

/* Optional CSS utils that can be commented out */
import "@ionic/react/css/padding.css";
import "@ionic/react/css/float-elements.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/text-alignment.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/display.css";

/* Theme variables */
import "./theme/variables.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/typography.css";

setupIonicReact();

const isEnvDefined = import.meta.env.VITE_AUTH0_CLIENT_ID_WEB && import.meta.env.VITE_AUTH0_DOMAIN;

export function App() {
// Get the callback handler from the Auth0 React hook
const { handleRedirectCallback, isAuthenticated } = useAuth0();
const { handleRedirectCallback } = useAuth0();

useEffect(() => {
// Handle the 'appUrlOpen' event and call `handleRedirectCallback`
CapApp.addListener("appUrlOpen", async ({ url }) => {
void CapApp.addListener("appUrlOpen", async ({ url }) => {
if (url.includes("state") && (url.includes("code") || url.includes("error"))) {
await handleRedirectCallback(url);
}
Expand All @@ -67,6 +68,9 @@ export function App() {
);
}

// DEBUG
const isAuthenticated = true;

return (
<IonApp>
<IonReactRouter>
Expand Down Expand Up @@ -97,6 +101,11 @@ export function App() {
<Personal />
</Route>

{/* Offer */}
<Route exact path="/offer/:id">
<OfferDetails />
</Route>

{/* Other routes (e.g. settings) */}
<Route exact path="/settings">
<Settings />
Expand All @@ -109,15 +118,17 @@ export function App() {
</IonRouterOutlet>

<IonTabBar slot="bottom">
<IonTabButton tab="overview" href="/overview">
<IonTabButton href="/overview" tab="overview">
<IonIcon aria-hidden="true" icon={home} />
<IonLabel>Overview</IonLabel>
</IonTabButton>
<IonTabButton tab="chats" href="/chats">

<IonTabButton href="/chats" tab="chats">
<IonIcon aria-hidden="true" icon={chatbox} />
<IonLabel>Chats</IonLabel>
</IonTabButton>
<IonTabButton tab="personal" href="/personal">

<IonTabButton href="/personal" tab="personal">
<IonIcon aria-hidden="true" icon={person} />
<IonLabel>Personal</IonLabel>
</IonTabButton>
Expand Down
41 changes: 41 additions & 0 deletions src/components/OfferCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useAuth0 } from "@auth0/auth0-react";
import { IonAvatar, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle } from "@ionic/react";
import { useMemo } from "react";
import type { Offer } from "./OfferCard.types";

interface Props {
offer: Offer;
}

export function OfferCard({ offer }: Props) {
const { user } = useAuth0();

const { description, id: offerId, image, offerType, title } = offer;

const shortenedDescription = useMemo(() => {
if (!description) {
return "";
}

return description.length > 100 ? `${description.slice(0, 100)}...` : description;
}, [description]);

return (
<IonCard routerDirection="forward" routerLink={`/offer/${offerId}`}>
{image && <img alt={title} src={image} style={{ height: "120px", objectFit: "cover", width: "100%" }} />}

<IonCardHeader>
<IonCardTitle>{title}</IonCardTitle>
<IonCardSubtitle>{offerType}</IonCardSubtitle>

{user && (
<IonAvatar style={{ position: "absolute", right: "1rem", top: "1rem" }}>
<img alt={user.name} src={user.picture} />
</IonAvatar>
)}
</IonCardHeader>

{shortenedDescription.length > 0 && <IonCardContent>{shortenedDescription}</IonCardContent>}
</IonCard>
);
}
7 changes: 7 additions & 0 deletions src/components/OfferCard.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Offer {
description?: string;
id: string;
image?: string;
offerType: "offer" | "request";
title: string;
}
73 changes: 73 additions & 0 deletions src/components/OfferList.mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable sonarjs/no-duplicate-string */

import type { Offer } from "./OfferCard.types";

export const offerListMockData: Offer[] = [
{
description: "Professional house cleaning services tailored to your needs. We'll leave your home spotless and fresh.",
id: "1",
image: "https://via.placeholder.com/300",
offerType: "offer",
title: "House Cleaning",
},
{
description: "Need assistance with your garden? Our experienced team can help you with planting, pruning, and maintaining your outdoor space.",
id: "2",
offerType: "request",
title: "Gardening Help",
},
{
description: "Transform your space with our top-notch painting services. From walls to ceilings, we'll give your home a fresh new look.",
id: "3",
image: "https://via.placeholder.com/300",
offerType: "offer",
title: "Painting Services",
},
{
description: "Get reliable and efficient delivery assistance for all your packages and parcels. We'll ensure your items reach their destination safely.",
id: "4",
image: "https://via.placeholder.com/300",
offerType: "request",
title: "Delivery Assistance",
},
{
description: "Going away? Trust our experienced pet sitters to provide loving care for your furry friends while you're gone.",
id: "5",
offerType: "offer",
title: "Pet Sitting",
},
{
description: "Make your move stress-free with our professional moving help. We'll handle the heavy lifting and logistics, so you can focus on settling into your new home.",
id: "6",
image: "https://via.placeholder.com/300",
offerType: "request",
title: "Moving Help",
},
{
description: "Planning an event? Let us take care of the setup. From decorations to seating arrangements, we'll create a memorable experience for your guests.",
id: "7",
image: "https://via.placeholder.com/300",
offerType: "offer",
title: "Event Setup",
},
{
description: "Don't struggle with complicated instructions. Our skilled team will assemble your furniture quickly and correctly.",
id: "8",
image: "https://via.placeholder.com/300",
offerType: "request",
title: "Furniture Assembly",
},
{
description: "From minor repairs to home improvement projects, our handyman services are here to help. Trust us to tackle any task with precision and expertise.",
id: "9",
offerType: "offer",
title: "Handyman Services",
},
{
description: "Need academic support? Our qualified tutors provide personalized assistance to help you excel in your studies.",
id: "10",
image: "https://via.placeholder.com/300",
offerType: "request",
title: "Tutoring Assistance",
}
]
6 changes: 6 additions & 0 deletions src/components/OfferList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { OfferCard } from "./OfferCard";
import { offerListMockData } from "./OfferList.mockData";

export function OfferList() {
return offerListMockData.map((offer) => <OfferCard offer={offer} />);
}
55 changes: 55 additions & 0 deletions src/pages/OfferDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { IonBackButton, IonButtons, IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from "@ionic/react";
import { useParams } from "react-router-dom";
import { offerListMockData } from "../components/OfferList.mockData";
import { useCallback, useState } from "react";

export function OfferDetails() {
const { id } = useParams<{ id: string }>();

const [imageHeight, setImageHeight] = useState<"25vh" | undefined>("25vh");

const toggleImageHeight = useCallback(
() => setImageHeight(imageHeight === "25vh" ? undefined : "25vh"),
[imageHeight, setImageHeight],
);

const offer = offerListMockData.find((offer) => offer.id === id);

const { title, image, description, offerType } = offer ?? { title: "Offer not found" };

return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton />
</IonButtons>

<IonTitle>{title}</IonTitle>
</IonToolbar>
</IonHeader>

<IonContent fullscreen>
{image && (
<img
onClick={toggleImageHeight}
src={image}
alt={title}
style={{ width: "100%", objectFit: "cover", height: imageHeight }}
/>
)}

{/* TODO: Make beautiful, look on Kleinanzeigen for reference */}
<div style={{ padding: "1rem" }}>
<h2>
{title} ({offerType})
</h2>
<h3>Offer details</h3>
<p>{description}</p>
</div>

{/* <pre>{JSON.stringify(offer, null, 2)}</pre> */}
</IonContent>
</IonPage>
);
}
3 changes: 3 additions & 0 deletions src/pages/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from "@ionic/react";
import { OfferList } from "../components/OfferList";

export function Overview() {
return (
Expand All @@ -15,6 +16,8 @@ export function Overview() {
<IonTitle size="large">Overview</IonTitle>
</IonToolbar>
</IonHeader>

<OfferList />
</IonContent>
</IonPage>
);
Expand Down

0 comments on commit 2f74298

Please sign in to comment.