diff --git a/.stylelintrc.json b/.stylelintrc.json
index 4448120a..5a21bb89 100644
--- a/.stylelintrc.json
+++ b/.stylelintrc.json
@@ -2,5 +2,8 @@
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-prettier-scss"
- ]
+ ],
+ "rules": {
+ "declaration-block-no-redundant-longhand-properties": null
+ }
}
diff --git a/cypress/e2e/Home.cy.ts b/cypress/e2e/HomePage.cy.ts
similarity index 100%
rename from cypress/e2e/Home.cy.ts
rename to cypress/e2e/HomePage.cy.ts
diff --git a/cypress/e2e/LocationPage.cy.ts b/cypress/e2e/LocationPage.cy.ts
new file mode 100644
index 00000000..f04e42b0
--- /dev/null
+++ b/cypress/e2e/LocationPage.cy.ts
@@ -0,0 +1,32 @@
+///
+
+export {};
+
+context('Given a user is on the Location page', () => {
+ const baseUrl = Cypress.config('baseUrl');
+ context('When the user clicks the "Back to home" link', () => {
+ specify('Then the user is directed to the Home page', () => {
+ cy.visit('/');
+ cy.findByRole('heading', {
+ name: /Basil's on Market/,
+ }).click();
+ cy.findByRole('link', {
+ name: /Back to home/,
+ }).click();
+ cy.url().should('eq', baseUrl);
+ });
+ });
+
+ context('When the user clicks the "Add a review" link', () => {
+ specify('Then the user is directed to the Home page', () => {
+ cy.visit('/');
+ cy.findByRole('heading', {
+ name: /Basil's on Market/,
+ }).click();
+ cy.findByRole('link', {
+ name: /Add a review/,
+ }).click();
+ cy.url().should('eq', `${baseUrl}reviews/new`);
+ });
+ });
+});
diff --git a/index.html b/index.html
index 9fd09439..e3b22be2 100644
--- a/index.html
+++ b/index.html
@@ -14,63 +14,63 @@
-
-
-
+
+
+
{
{
{location.name}
@@ -39,11 +40,11 @@ const LocationHeading = ({ location }: { location: Location }) => {
const LocationAddress = ({ location }: { location: Location }) => {
return (
-
+
{location.address}
{location.phone && (
-
+
{location.phone}
diff --git a/src/components/home/LocationCards.tsx b/src/components/HomePage/LocationCards.tsx
similarity index 72%
rename from src/components/home/LocationCards.tsx
rename to src/components/HomePage/LocationCards.tsx
index 740c18d1..ded2841f 100644
--- a/src/components/home/LocationCards.tsx
+++ b/src/components/HomePage/LocationCards.tsx
@@ -1,11 +1,11 @@
import { LocationCard } from './LocationCard';
-import type { Location } from '../../types/sparkeats';
+import type { Locations } from '../../types/sparkeats';
-export function LocationCards({ locations }: { locations: Location[] }) {
+export function LocationCards({ locations }: { locations: Locations }) {
return (
- {locations.map((location) => (
+ {Object.values(locations).map((location) => (
))}
diff --git a/src/components/home/HomePage.tsx b/src/components/HomePage/index.tsx
similarity index 100%
rename from src/components/home/HomePage.tsx
rename to src/components/HomePage/index.tsx
diff --git a/src/components/LocationPage/LocationDetails.tsx b/src/components/LocationPage/LocationDetails.tsx
new file mode 100644
index 00000000..fa52cad7
--- /dev/null
+++ b/src/components/LocationPage/LocationDetails.tsx
@@ -0,0 +1,37 @@
+import { Location } from '../../types/sparkeats';
+
+function LocationAddress({ location }: { location: Location }) {
+ return (
+ <>
+
+ {location?.address}
+
+ {location?.phone && (
+
+
+ {location?.phone}
+
+
+ )}
+ {location?.url && (
+
+
+ Visit Site
+
+
+ )}
+ >
+ );
+}
+
+export function LocationDetails({ location }: { location: Location }) {
+ return (
+
+
+ {location.reviewCountText}
+ Average Stars
+
+
+
+ );
+}
diff --git a/src/components/LocationPage/LocationHeader.tsx b/src/components/LocationPage/LocationHeader.tsx
new file mode 100644
index 00000000..0ee60dd8
--- /dev/null
+++ b/src/components/LocationPage/LocationHeader.tsx
@@ -0,0 +1,15 @@
+import { Location } from '../../types/sparkeats';
+
+export function LocationHeader({ location }: { location: Location }) {
+ return (
+
+
+
{location.name}
+
+ {location.city}, {location.region}
+
+
+
+
+ );
+}
diff --git a/src/components/LocationPage/LocationReviews.tsx b/src/components/LocationPage/LocationReviews.tsx
new file mode 100644
index 00000000..d6dac5c8
--- /dev/null
+++ b/src/components/LocationPage/LocationReviews.tsx
@@ -0,0 +1,48 @@
+import { Link } from 'react-router-dom';
+import { Location, Review } from '../../types/sparkeats';
+
+export function LocationReviews({
+ location,
+ reviews = [],
+}: {
+ location: Location;
+ reviews: Review[];
+}) {
+ return (
+
+
+ Add a review
+
+
+ {reviews.map((review: Review) => (
+
+ {review.reviewerName}
+
+ {/* Created year/month/day*/}
+
+
+ Stars
+
+ Comments
+ {review.text}
+ {review.imageURL && (
+
+
+
+ )}
+
+ ))}
+
+ );
+}
diff --git a/src/components/LocationPage/index.tsx b/src/components/LocationPage/index.tsx
new file mode 100644
index 00000000..09c8d01e
--- /dev/null
+++ b/src/components/LocationPage/index.tsx
@@ -0,0 +1,30 @@
+import { useLocation as useWindowLocation } from 'react-router-dom';
+import { Link } from 'react-router-dom';
+import { LocationHeader } from './LocationHeader';
+import { LocationDetails } from './LocationDetails';
+import { LocationReviews } from './LocationReviews';
+import { useLocations } from '../../useLocations';
+
+export function LocationPage() {
+ const {
+ state: { id },
+ } = useWindowLocation();
+
+ const locations = useLocations();
+
+ const location = locations[id];
+
+ return (
+
+
+
+
+ Back to home
+
+
+
+
+
+
+ );
+}
diff --git a/src/locations.ts b/src/locations.ts
index 3f243991..6d3ba71e 100644
--- a/src/locations.ts
+++ b/src/locations.ts
@@ -9,7 +9,7 @@ import legacyPlaces from '../data/place.json';
import legacyReviews from '../data/review.json';
import legacyPlaceImages from '../data/placeImage.json';
import legacyReviewImages from '../data/reviewImage.json';
-import { Location, Review } from './types/sparkeats';
+import { Locations, Location, Review } from './types/sparkeats';
type LegacyPlace = {
createdAt: number;
@@ -47,7 +47,18 @@ type LegacyReview = {
placeId: number;
};
-function getImageURL(
+function getReviewImageURL(
+ imagePath: string,
+ imageID: string,
+ legacyImages: LegacyImage[]
+) {
+ const imageName = legacyImages.find(
+ (image) => image.id.toString() === imageID
+ )?.fd;
+ return imageName ? `${imagePath}${imageName}` : null;
+}
+
+function getLocationImageURL(
imagePath: string,
imageID: string,
legacyImages: LegacyImage[]
@@ -81,7 +92,7 @@ function transformReview({
id,
reviewerName,
text,
- imageURL: getImageURL('img/reviews/', imageID, legacyReviewImages),
+ imageURL: getReviewImageURL('/img/reviews/', imageID, legacyReviewImages),
imageDescription: getImageDescription(reviewImageAlt),
starRating,
placeID,
@@ -94,35 +105,58 @@ function getReviews(placeID: number) {
.map(transformReview);
}
-function transformLocations(legacyPlaces: LegacyPlace[]): Location[] {
- return legacyPlaces.map(
- ({
- id,
- placeName: name,
- city,
- state: region,
- address,
- phone,
- placeURL: url,
- placeImage: imageID,
- placeImageAlt,
- }) => {
- return {
+function getReviewCountText(reviewCount: number): string {
+ return reviewCount !== 1 ? `${reviewCount} Reviews` : `${reviewCount} Review`;
+}
+
+function mapLocation(
+ locationMap: { [key: string]: Location },
+ location: Location
+): { [key: string]: Location } {
+ return {
+ [location.id]: location,
+ ...locationMap,
+ };
+}
+
+function transformLocations(legacyPlaces: LegacyPlace[]): Locations {
+ return legacyPlaces
+ .map(
+ ({
id,
- name,
+ placeName: name,
city,
- region,
- country: '', // TODO
+ state: region,
address,
phone,
- url,
- locationURL: getLocationURL(id),
- imageURL: getImageURL('img/locations/', imageID, legacyPlaceImages),
- imageDescription: getImageDescription(placeImageAlt),
- reviews: getReviews(id),
- };
- }
- );
+ placeURL: url,
+ placeImage: imageID,
+ placeImageAlt,
+ }) => {
+ const reviews = getReviews(id);
+
+ return {
+ id,
+ name,
+ city,
+ region,
+ country: '',
+ address,
+ phone,
+ url,
+ locationURL: getLocationURL(id),
+ imageURL: getLocationImageURL(
+ '/img/locations/',
+ imageID,
+ legacyPlaceImages
+ ),
+ imageDescription: getImageDescription(placeImageAlt),
+ reviews,
+ reviewCountText: getReviewCountText(reviews.length),
+ };
+ }
+ )
+ .reduce(mapLocation, {});
}
const locations = transformLocations(legacyPlaces);
diff --git a/src/main.tsx b/src/main.tsx
index 1839ecbb..2ca5c5a2 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -2,7 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { App } from './App';
-import { HomePage } from './components/home/HomePage';
+import { HomePage } from './components/HomePage';
+import { LocationPage } from './components/LocationPage';
window.__SPARKEATS_VERSION__ = import.meta.env['VITE_SPARKEATS_VERSION'];
@@ -12,7 +13,7 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
}>
} />
- Location Page} />
+ } />
New Location Page} />
New Review Page} />
diff --git a/src/scss/components/_review-overview.scss b/src/scss/components/_review-details.scss
similarity index 94%
rename from src/scss/components/_review-overview.scss
rename to src/scss/components/_review-details.scss
index 3f757eff..3be71b94 100644
--- a/src/scss/components/_review-overview.scss
+++ b/src/scss/components/_review-details.scss
@@ -1,12 +1,12 @@
/* stylelint-disable scss/no-global-function-names */
-.review-overview {
+.review-details {
box-sizing: border-box;
display: flex;
justify-content: space-between;
flex-direction: column;
background: $secondary-color;
- background: url('./img/background-img.png');
+ background: url('/img/background-img.png');
border: 1px solid $primary-color;
box-shadow: 2px 4px 28px rgba(0 0 0 / 8%);
padding: 3rem 3rem 1rem;
diff --git a/src/scss/components/_review-header.scss b/src/scss/components/_review-header.scss
index e55ae735..7848c0dc 100644
--- a/src/scss/components/_review-header.scss
+++ b/src/scss/components/_review-header.scss
@@ -1,6 +1,6 @@
.review-header {
height: 22.25rem;
- background: url('./img/review-header_bg.svg') no-repeat;
+ background: url('/img/review-header_bg.svg') no-repeat;
background-size: cover;
position: relative;
@@ -26,7 +26,7 @@
padding: 1rem;
position: absolute;
background: $secondary-color;
- background: url('./img/background-img.png');
+ background: url('/img/background-img.png');
border: 0.0625rem solid $primary-color;
bottom: 1rem;
left: 1rem;
diff --git a/src/scss/components/_review-nav.scss b/src/scss/components/_review-nav.scss
index 85c57d08..1b63dc33 100644
--- a/src/scss/components/_review-nav.scss
+++ b/src/scss/components/_review-nav.scss
@@ -5,7 +5,7 @@
&__svg {
height: 0.9375rem;
width: 1rem;
- background: url('./img/review-header_home-arrow.svg') no-repeat;
+ background: url('/img/review-header_home-arrow.svg') no-repeat;
display: inline-block;
}
diff --git a/src/scss/elements/_body.scss b/src/scss/elements/_body.scss
index 2e0bd5bc..5ad51946 100644
--- a/src/scss/elements/_body.scss
+++ b/src/scss/elements/_body.scss
@@ -1,6 +1,6 @@
body {
font-family: $roboto-condensed-stack;
color: $primary-color;
- background: url('./img/background-img.png');
+ background: url('/img/background-img.png');
background-blend-mode: darken;
}
diff --git a/src/scss/objects/_review-page.scss b/src/scss/objects/_review-page.scss
index 5ba8d2de..2bc86d1d 100644
--- a/src/scss/objects/_review-page.scss
+++ b/src/scss/objects/_review-page.scss
@@ -14,11 +14,12 @@
@supports (display: grid) {
display: grid;
grid-column-gap: 2rem;
- grid-template:
- 'nav . .' auto
- 'header header header' auto
- 'review review overview' auto
- / repeat(3, 1fr);
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: auto;
+ grid-template-areas:
+ 'nav . .'
+ 'header header header'
+ 'review review overview';
}
@media (min-width: $bp-full-size-desktop) {
@@ -56,7 +57,7 @@
}
}
- .review-overview {
+ .review-details {
order: 2;
@media (max-width: $bp-rating-overview-move-right) {
@@ -66,7 +67,7 @@
@media (min-width: $bp-rating-overview-move-right) {
width: calc(33% - 4rem);
align-self: flex-start;
- margin: 8.8rem 0 0 1rem; // magic number that aligns the review-overview with the top of review-submission.
+ margin: 8.8rem 0 0 1rem; // magic number that aligns the review-details with the top of review-submission.
flex-grow: 1;
order: 3;
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index 84e70867..d7339193 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -4,4 +4,6 @@ declare global {
interface Window {
__SPARKEATS_VERSION__: string;
}
+
+ type WindowLocation = Location;
}
diff --git a/src/types/sparkeats.ts b/src/types/sparkeats.ts
index 3835af27..b9ef1014 100644
--- a/src/types/sparkeats.ts
+++ b/src/types/sparkeats.ts
@@ -1,3 +1,21 @@
+export type Locations = {
+ [key: string]: {
+ id: number;
+ name: string;
+ city: string;
+ region: string;
+ country: string;
+ address: string;
+ phone: string;
+ url: string;
+ locationURL: string;
+ imageURL: string;
+ imageDescription: string;
+ reviews: Review[];
+ reviewCountText: string;
+ };
+};
+
export type Location = {
id: number;
name: string;
@@ -11,13 +29,14 @@ export type Location = {
imageURL: string;
imageDescription: string;
reviews: Review[];
+ reviewCountText: string;
};
export type Review = {
id: number;
reviewerName: string;
text: string;
- imageURL: string;
+ imageURL: string | null;
imageDescription: string;
starRating: number;
placeID: number;