@@ -64,6 +64,6 @@ export function Layout({
)}
-
+ >
);
}
diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx
index c2a345e..6ae3b62 100644
--- a/src/components/map/map.tsx
+++ b/src/components/map/map.tsx
@@ -1,14 +1,16 @@
-import { useRef, useEffect } from 'react';
+import React, { useRef, useEffect } from 'react';
import { Icon, Marker, layerGroup } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useMap } from './use-map.ts';
import { City } from '../../dataTypes/city.ts';
import { Point } from '../../dataTypes/point.ts';
+import cn from 'classnames';
interface MapProps {
city: City;
points: Point[];
selectedPoint: Point | undefined;
+ isOnMainPage?: boolean;
}
const defaultCustomIcon = new Icon({
@@ -23,8 +25,8 @@ const currentCustomIcon = new Icon({
iconAnchor: [20, 40],
});
-export function Map(props: MapProps): JSX.Element {
- const { city, points, selectedPoint } = props;
+export function Map(props: MapProps): React.JSX.Element {
+ const { city, points, selectedPoint, isOnMainPage } = props;
const mapRef = useRef(null);
const map = useMap(mapRef, city);
@@ -53,5 +55,15 @@ export function Map(props: MapProps): JSX.Element {
}
}, [map, points, selectedPoint]);
- return
;
+ return (
+
+ );
}
diff --git a/src/components/offer/offer-card.tsx b/src/components/offer/offer-card.tsx
index b728c02..4ae3767 100644
--- a/src/components/offer/offer-card.tsx
+++ b/src/components/offer/offer-card.tsx
@@ -1,6 +1,8 @@
import { RoomType } from '../../dataTypes/enums/room-type.ts';
import { Link } from 'react-router-dom';
import { AppRoutes } from '../../dataTypes/enums/app-routes.ts';
+import cn from 'classnames';
+import { Rating } from '../rating.tsx';
interface PlaceCardProps {
id: string;
@@ -8,10 +10,12 @@ interface PlaceCardProps {
type: RoomType;
image: string;
title: string;
+ rating: number;
onMouseEnter?: (id: string) => void;
onMouseLeave?: () => void;
isPremium?: boolean;
isFavorite?: boolean;
+ isOnMainPage?: boolean;
}
export function OfferCard({
@@ -20,10 +24,12 @@ export function OfferCard({
type,
image,
title,
+ rating,
onMouseEnter,
onMouseLeave,
isPremium,
isFavorite,
+ isOnMainPage,
}: PlaceCardProps): React.JSX.Element {
const handleMouseEnter = (): void => onMouseEnter?.(id);
const handleMouseLeave = (): void => onMouseLeave?.();
@@ -31,15 +37,25 @@ export function OfferCard({
{isPremium && (
Premium
)}
-
-
+
+
-
+
- {title}
+ {title}
{type}
diff --git a/src/components/offer/offer-gallery.tsx b/src/components/offer/offer-gallery.tsx
new file mode 100644
index 0000000..a1047be
--- /dev/null
+++ b/src/components/offer/offer-gallery.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+interface OfferGalleryProps {
+ imageSources: string[];
+}
+
+export function OfferGallery({
+ imageSources,
+}: OfferGalleryProps): React.JSX.Element {
+ return (
+
+
+ {imageSources.map((src) => (
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/offer/offer-host.tsx b/src/components/offer/offer-host.tsx
new file mode 100644
index 0000000..e4c288b
--- /dev/null
+++ b/src/components/offer/offer-host.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { User } from '../../dataTypes/user.ts';
+import { getFirstName } from '../../utils/username-utils.ts';
+
+interface OfferHostProps {
+ host: User;
+}
+
+export function OfferHost({ host }: OfferHostProps): React.JSX.Element {
+ return (
+ <>
+ Meet the host
+
+
+
+
+
{getFirstName(host.name)}
+ {host.isPro &&
Pro }
+
+ >
+ );
+}
diff --git a/src/components/offer/offer-inside-items.tsx b/src/components/offer/offer-inside-items.tsx
new file mode 100644
index 0000000..793f717
--- /dev/null
+++ b/src/components/offer/offer-inside-items.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+interface OfferInsideItemsProps {
+ items: string[];
+}
+
+export function OfferInsideItems({
+ items,
+}: OfferInsideItemsProps): React.JSX.Element {
+ return (
+
+
What's inside
+
+ {items.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/offer/offers-list.tsx b/src/components/offer/offers-list.tsx
index 9bb90cd..7d9c3bc 100644
--- a/src/components/offer/offers-list.tsx
+++ b/src/components/offer/offers-list.tsx
@@ -6,11 +6,13 @@ import { Nullable } from 'vitest';
interface OffersListProps {
offers: Offer[];
onActiveOfferChange?: (offer: Nullable) => void;
+ isOnMainPage?: boolean;
}
export function OffersList({
offers,
onActiveOfferChange,
+ isOnMainPage,
}: OffersListProps): React.JSX.Element {
const handleActiveOfferChange = (offer: Nullable): void => {
onActiveOfferChange?.(offer);
@@ -25,10 +27,12 @@ export function OffersList({
type={offer.type}
image={offer.previewImage}
title={offer.title}
+ rating={offer.rating}
onMouseEnter={() => handleActiveOfferChange(offer)}
onMouseLeave={() => handleActiveOfferChange(null)}
isFavorite={offer.isFavorite}
isPremium={offer.isPremium}
+ isOnMainPage={isOnMainPage}
/>
))}
diff --git a/src/components/rating.tsx b/src/components/rating.tsx
new file mode 100644
index 0000000..7db3415
--- /dev/null
+++ b/src/components/rating.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+interface RatingProps {
+ rating: number;
+ usePlace: string;
+ isInOffer?: boolean;
+}
+
+export function Rating({
+ rating,
+ isInOffer,
+ usePlace,
+}: RatingProps): React.JSX.Element {
+ return (
+
To submit review please make sure to set{' '}
diff --git a/src/components/reviews/reviews-list.tsx b/src/components/reviews/reviews-list.tsx
new file mode 100644
index 0000000..8f463fa
--- /dev/null
+++ b/src/components/reviews/reviews-list.tsx
@@ -0,0 +1,29 @@
+import { Review } from '../../dataTypes/review.ts';
+import { ReviewComponent } from './review-component.tsx';
+
+interface ReviewsListProps {
+ reviews: Review[];
+}
+
+export function ReviewsList({ reviews }: ReviewsListProps): React.JSX.Element {
+ return (
+ <>
+
+ Reviews ·{' '}
+ {reviews.length}
+
+
+ {reviews.map((review: Review) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/components/reviews/reviews.tsx b/src/components/reviews/reviews.tsx
new file mode 100644
index 0000000..5578d04
--- /dev/null
+++ b/src/components/reviews/reviews.tsx
@@ -0,0 +1,30 @@
+import { ReviewForm } from './review-form.tsx';
+import { ReviewsList } from './reviews-list.tsx';
+import { Review } from '../../dataTypes/review.ts';
+
+interface ReviewsProps {
+ reviews: Review[];
+}
+
+export function Reviews({ reviews }: ReviewsProps): React.JSX.Element {
+ const reviewsAvailable = reviews && reviews.length !== 0;
+ return (
+
+ {reviewsAvailable ? (
+
+ ) : (
+
+ No reviews available
+
+ )}
+
+
+ );
+}
diff --git a/src/dataTypes/detailed-offer.ts b/src/dataTypes/detailed-offer.ts
new file mode 100644
index 0000000..c7e3130
--- /dev/null
+++ b/src/dataTypes/detailed-offer.ts
@@ -0,0 +1,21 @@
+import { City } from './city.ts';
+import { User } from './user.ts';
+import { Location } from './location.ts';
+
+export type DetailedOffer = {
+ id: string;
+ title: string;
+ type: string;
+ price: number;
+ city: City;
+ location: Location;
+ isFavorite: boolean;
+ isPremium: boolean;
+ rating: number;
+ description: string;
+ bedrooms: number;
+ goods: string[];
+ host: User;
+ images: string[];
+ maxAdults: number;
+};
diff --git a/src/dataTypes/enums/authorization-status.ts b/src/dataTypes/enums/authorization-status.ts
index 3e0b810..b24c350 100644
--- a/src/dataTypes/enums/authorization-status.ts
+++ b/src/dataTypes/enums/authorization-status.ts
@@ -1,5 +1,5 @@
export enum AuthorizationStatus {
Authorized,
Unauthorized,
- Unknown
+ Unknown,
}
diff --git a/src/dataTypes/enums/room-type.ts b/src/dataTypes/enums/room-type.ts
index 10b288f..86c0b35 100644
--- a/src/dataTypes/enums/room-type.ts
+++ b/src/dataTypes/enums/room-type.ts
@@ -1,4 +1,4 @@
export enum RoomType {
- Apartment = 'apartment',
- Room = 'room',
+ Apartment = 'Apartment',
+ Room = 'Room',
}
diff --git a/src/dataTypes/location.ts b/src/dataTypes/location.ts
index 5cd6bac..39692f0 100644
--- a/src/dataTypes/location.ts
+++ b/src/dataTypes/location.ts
@@ -2,4 +2,4 @@ export type Location = {
latitude: number;
longitude: number;
zoom: number;
-}
+};
diff --git a/src/dataTypes/review.ts b/src/dataTypes/review.ts
new file mode 100644
index 0000000..733843c
--- /dev/null
+++ b/src/dataTypes/review.ts
@@ -0,0 +1,9 @@
+import { User } from './user.ts';
+
+export type Review = {
+ id: string;
+ date: string;
+ user: User;
+ comment: string;
+ rating: number;
+};
diff --git a/src/dataTypes/user.ts b/src/dataTypes/user.ts
new file mode 100644
index 0000000..3b221f9
--- /dev/null
+++ b/src/dataTypes/user.ts
@@ -0,0 +1,5 @@
+export type User = {
+ name: string;
+ avatarUrl: string;
+ isPro: boolean;
+};
diff --git a/src/mocks/detailed-offer.ts b/src/mocks/detailed-offer.ts
new file mode 100644
index 0000000..7d2407e
--- /dev/null
+++ b/src/mocks/detailed-offer.ts
@@ -0,0 +1,141 @@
+export const detailedOfferMocks = [
+ {
+ id: '6af6f711-c28d-4121-82cd-e0b462a27f00',
+ title: 'Beautiful & luxurious studio at great location',
+ type: 'apartment',
+ price: 120,
+ city: {
+ name: 'Amsterdam',
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ },
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ isFavorite: false,
+ isPremium: false,
+ rating: 4,
+ description:
+ 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.',
+ bedrooms: 3,
+ goods: ['Heating'],
+ host: {
+ name: 'Oliver Conner',
+ avatarUrl: 'https://url-to-image/image.png',
+ isPro: false,
+ },
+ images: ['https://url-to-image/image.png'],
+ maxAdults: 4,
+ },
+ {
+ id: '6af6f711-c28d-4121-82cd-e0b462a27f11',
+ title: 'Beautiful & luxurious studio at great location',
+ type: 'apartment',
+ price: 120,
+ city: {
+ name: 'Amsterdam',
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ },
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ isFavorite: false,
+ isPremium: false,
+ rating: 4,
+ description:
+ 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.',
+ bedrooms: 3,
+ goods: ['Heating'],
+ host: {
+ name: 'Oliver Conner',
+ avatarUrl: 'https://url-to-image/image.png',
+ isPro: false,
+ },
+ images: ['https://url-to-image/image.png'],
+ maxAdults: 4,
+ },
+ {
+ id: '6af6f711-c28d-4121-82cd-e0b462a27f22',
+ title: 'Beautiful & luxurious studio at great location',
+ type: 'apartment',
+ price: 120,
+ city: {
+ name: 'Amsterdam',
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ },
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ isFavorite: false,
+ isPremium: false,
+ rating: 4,
+ description:
+ 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.',
+ bedrooms: 3,
+ goods: ['Heating'],
+ host: {
+ name: 'Oliver Conner',
+ avatarUrl: 'https://url-to-image/image.png',
+ isPro: false,
+ },
+ images: ['https://url-to-image/image.png'],
+ maxAdults: 4,
+ },
+ {
+ id: '6af6f711-c28d-4121-82cd-e0b462a27f33',
+ title: 'Beautiful & luxurious studio at great location',
+ type: 'apartment',
+ price: 120,
+ city: {
+ name: 'Amsterdam',
+ location: {
+ latitude: 52.35514938496378,
+ longitude: 4.673877537499948,
+ zoom: 8,
+ },
+ },
+ location: {
+ latitude: 52.3609553943508,
+ longitude: 4.85309666406198,
+ zoom: 8,
+ },
+ isFavorite: false,
+ isPremium: false,
+ rating: 4,
+ description:
+ 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.',
+ bedrooms: 3,
+ goods: ['Heating', 'plastation 5 pro max', 'Aboba', 'Amogus'],
+ host: {
+ name: 'Angelina Noname',
+ avatarUrl: 'img/avatar-angelina.jpg',
+ isPro: true,
+ },
+ images: [
+ 'img/apartment-01.jpg',
+ 'img/apartment-02.jpg',
+ 'img/apartment-03.jpg',
+ 'img/room.jpg',
+ 'img/studio-01.jpg',
+ 'img/apartment-01.jpg',
+ ],
+ maxAdults: 4,
+ },
+];
diff --git a/src/mocks/reviews.ts b/src/mocks/reviews.ts
new file mode 100644
index 0000000..10b85f8
--- /dev/null
+++ b/src/mocks/reviews.ts
@@ -0,0 +1,27 @@
+import { Review } from '../dataTypes/review.ts';
+
+export const reviewMocks: Review[] = [
+ {
+ id: 'b67ddfd5-b953-4a30-8c8d-bd083cd6b62a',
+ date: '2019-05-08T14:13:56.569Z',
+ user: {
+ name: 'Oliver Conner',
+ avatarUrl: 'img/avatar-max.jpg',
+ isPro: false,
+ },
+ comment:
+ 'A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.',
+ rating: 4,
+ },
+ {
+ id: 'b67ddfd5-b953-4a30-8c8d-bd083cd6b62b',
+ date: '2019-05-09T14:16:56.569Z',
+ user: {
+ name: 'Alice Conner',
+ avatarUrl: 'img/avatar-angelina.jpg',
+ isPro: true,
+ },
+ comment: 'aboba amogus',
+ rating: 5,
+ },
+];
diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts
new file mode 100644
index 0000000..f5f0347
--- /dev/null
+++ b/src/utils/string-utils.ts
@@ -0,0 +1,14 @@
+export function capitalize(value: string): string {
+ return value.charAt(0).toUpperCase() + value.slice(1);
+}
+
+export function pluralize(value: string, count: number): string {
+ if (count === 1) {
+ return value;
+ }
+ return `${value}s`;
+}
+
+export function pluralizeAndCombine(value: string, count: number): string {
+ return `${count} ${pluralize(value, count)}`;
+}
diff --git a/src/utils/username-utils.ts b/src/utils/username-utils.ts
new file mode 100644
index 0000000..00f42a3
--- /dev/null
+++ b/src/utils/username-utils.ts
@@ -0,0 +1,3 @@
+export function getFirstName(name: string): string {
+ return name.split(' ')[0];
+}