Skip to content

Commit

Permalink
feat: real estate page
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Feb 27, 2025
1 parent 14cc73b commit 167ff0b
Show file tree
Hide file tree
Showing 26 changed files with 850 additions and 22 deletions.
Binary file added public/leaflet/layers-2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/leaflet/layers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/leaflet/marker-icon-2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/leaflet/marker-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/leaflet/marker-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
<lastmod>2024-12-20</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://ekokedao.com/real-estate</loc>
<lastmod>2025-02-27</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://ekokedao.com/presale</loc>
<lastmod>2024-12-20</lastmod>
Expand Down
20 changes: 18 additions & 2 deletions src/js/components/App/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ const ContractPage = React.lazy(
const Agencies = React.lazy(() => import('./pages/Agencies'));
const Agency = React.lazy(() => import('./pages/Agencies/pages/Agency'));

// real estate
const RealEstate = React.lazy(() => import('./pages/RealEstate'));
const RealEstateProperty = React.lazy(
() => import('./pages/RealEstate/pages/RealEstateProperty'),
);

// reserved area
const Profile = React.lazy(() => import('./pages/Profile'));
const ProfileCollected = React.lazy(
Expand Down Expand Up @@ -97,7 +103,7 @@ const AppRouter = () => (
element={<Marketplace />}
/>
<RouterRoute
path={`${Route.url(Route.MARKETPLACE_CONTRACT)}/:id`}
path={Route.url(Route.MARKETPLACE_CONTRACT)}
element={<ContractPage />}
/>

Expand All @@ -107,10 +113,20 @@ const AppRouter = () => (
element={<Agencies />}
/>
<RouterRoute
path={`${Route.url(Route.AGENCIES_AGENCY)}/:id`}
path={Route.url(Route.AGENCIES_AGENCY)}
element={<Agency />}
/>

{/* real estate */}
<RouterRoute
path={Route.url(Route.REAL_ESTATE)}
element={<RealEstate />}
/>
<RouterRoute
path={Route.url(Route.REAL_ESTATE_PROPERTY)}
element={<RealEstateProperty />}
/>

{/* reserved area */}
<RouterRoute path={Route.url(Route.PROFILE)} element={<Profile />} />
<RouterRoute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ const Item = ({
{realEstate.rooms} Rooms
</Container.Container>
)}
{realEstate.squareMeters !== undefined && (
{realEstate.square_meters !== undefined && (
<Container.Container className="text-sm text-gray-500">
<MdIcon.MdSquareFoot
size={16}
className="text-gray-500 mr-2 inline"
/>
{realEstate.squareMeters} Square Meters
{realEstate.square_meters} Square Meters
</Container.Container>
)}
{realEstate.bathrooms !== undefined && (
Expand Down
14 changes: 10 additions & 4 deletions src/js/components/App/pages/Marketplace/pages/Contract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ const ContractPage = () => {
return (
<>
<Helmet>
<title>{contract.realEstate.name}</title>
<meta name="description" content={contract.realEstate.description} />
<meta property="og:title" content={contract.realEstate.name} />
<title>{`${contract.realEstate.name} - Real estate on sale on Ethereum`}</title>
<meta
name="description"
content={`Buy real estate with Ethereum - ${contract.realEstate.description}`}
/>
<meta
property="og:title"
content={`${contract.realEstate.name} - Real estate on sale on Ethereum`}
/>
<meta
property="og:description"
content={contract.realEstate.description}
content={`Buy real estate with Ethereum - ${contract.realEstate.description}`}
/>
</Helmet>
<Container.Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ const RealEstateCard = ({ contract }: Props) => (
{contract.realEstate.rooms} Rooms
</Container.Container>
)}
{contract.realEstate.squareMeters !== undefined && (
{contract.realEstate.square_meters !== undefined && (
<Container.Container className="text-sm text-gray-500">
<MdIcon.MdSquareFoot
size={16}
className="text-gray-500 mr-2 inline"
/>
{contract.realEstate.squareMeters} Square Meters
{contract.realEstate.square_meters} Square Meters
</Container.Container>
)}
{contract.realEstate.bathrooms !== undefined && (
Expand All @@ -100,13 +100,13 @@ const RealEstateCard = ({ contract }: Props) => (
{contract.realEstate.bedrooms} Bedrooms
</Container.Container>
)}
{contract.realEstate.yearOfConstruction !== undefined && (
{contract.realEstate.year_of_construction !== undefined && (
<Container.Container className="text-sm text-gray-500">
<MdIcon.MdCalendarToday
size={16}
className="text-gray-500 mr-2 inline"
/>
{contract.realEstate.yearOfConstruction}
{contract.realEstate.year_of_construction}
</Container.Container>
)}
{contract.realEstate.balconies !== undefined && (
Expand Down Expand Up @@ -159,9 +159,9 @@ const RealEstateCard = ({ contract }: Props) => (
</Container.FlexResponsiveRow>
<Progress contractId={contract.id} installments={contract.installments} />
<Paragraph.Leading>{contract.realEstate.description}</Paragraph.Leading>
{contract.realEstate.youtubeUrl && (
{contract.realEstate.youtube && (
<Container.Container className="mx-auto">
<YoutubeVideo width={720} url={contract.realEstate.youtubeUrl} />
<YoutubeVideo width={720} url={contract.realEstate.youtube} />
</Container.Container>
)}
{contract.documents.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ const ContractCard = ({ id, signature }: Props) => {
{realEstate.rooms} Rooms
</Container.Container>
)}
{realEstate.squareMeters !== undefined && (
{realEstate.square_meters !== undefined && (
<Container.Container className="text-sm text-gray-500">
<MdIcon.MdSquareFoot
size={16}
className="text-gray-500 mr-2 inline"
/>
{realEstate.squareMeters} Square Meters
{realEstate.square_meters} Square Meters
</Container.Container>
)}
{realEstate.bathrooms !== undefined && (
Expand Down
27 changes: 27 additions & 0 deletions src/js/components/App/pages/RealEstate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';

import Container from '../../reusable/Container';
import Filters from './RealEstate/Filters';
import PropertyTable from './RealEstate/PropertyTable';

export interface IFilters {
city?: string;
country?: string;
}

const Marketplace = () => {
const [filters, setFilters] = React.useState<IFilters>({});

return (
<Container.Container>
<Container.FlexResponsiveRow className="flex-1 gap-8">
<Filters filters={filters} setFilters={setFilters} />
<Container.Container className="flex-1 py-4">
<PropertyTable filters={filters} />
</Container.Container>
</Container.FlexResponsiveRow>
</Container.Container>
);
};

export default Marketplace;
77 changes: 77 additions & 0 deletions src/js/components/App/pages/RealEstate/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react';
import * as Icon from 'react-icons/fi';

import Container from '../../../reusable/Container';
import { Map, MapLink } from './Filters/Map';
import FiltersTab from './Filters/FiltersTab';
import Button from '../../../reusable/Button';
import Link from '../../../reusable/Link';
import { IFilters } from '../RealEstate';

interface Props {
filters: IFilters;
setFilters: React.Dispatch<React.SetStateAction<IFilters>>;
}

const Filters = (props: Props) => (
<>
<Container.Container className="block sm:hidden w-2/6 xl:w-3/12">
<PostsSidebarDesktop {...props} />
</Container.Container>
<Container.Container className="w-full hidden sm:block">
<PostsSidebarMobile {...props} />
</Container.Container>
</>
);

const PostsSidebarDesktop = (props: Props) => (
<Container.FlexCols className="w-full">
<Map />
<Container.Card className="bg-white w-full block">
<FiltersTab {...props} />
</Container.Card>
</Container.FlexCols>
);

const PostsSidebarMobile = (props: Props) => (
<Container.FlexRow className="justify-around py-2">
<MapLink />
<FiltersOnMobile tab={<FiltersTab {...props} />} />
</Container.FlexRow>
);

const FiltersOnMobile = ({ tab }: { tab: React.ReactNode }) => {
const [showFilters, setShowFilters] = React.useState<boolean>(false);

const onOpenFilters = () => {
setShowFilters(true);
};

return (
<>
{showFilters && (
<Container.Container className="bg-white fixed z-50 top-0 left-0 w-screen min-h-screen overflow-y-scroll">
<Container.FlexCols className="shadow-md p-1">
<Icon.FiX
className="text-brandAlt hover:cursor-pointer"
size={24}
onClick={() => setShowFilters(false)}
/>
</Container.FlexCols>
<Container.Container className="p-4">{tab}</Container.Container>
<Container.Container className="p-4">
<Button.Primary onClick={() => setShowFilters(false)}>
Apply filters
</Button.Primary>
</Container.Container>
</Container.Container>
)}
<Link.Default className="!text-brand" onClick={onOpenFilters}>
<Icon.FiFilter className="text-brand inline mr-2" size={24} />
Filters
</Link.Default>
</>
);
};

export default Filters;
107 changes: 107 additions & 0 deletions src/js/components/App/pages/RealEstate/Filters/FiltersTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as React from 'react';
import * as Icon from 'react-icons/md';

import Container from '../../../../reusable/Container';
import Input from '../../../../reusable/Input';
import { IFilters } from '../../RealEstate';

interface Props {
filters: IFilters;
setFilters: React.Dispatch<React.SetStateAction<IFilters>>;
}

const FiltersTab = ({ filters, setFilters }: Props) => {
const [debouncedCity, setDebouncedCity] = React.useState<string>(
filters.city ?? '',
);
const [cityDebouncer, setCityDebouncer] = React.useState<NodeJS.Timeout>();
const [debouncedCountry, setDebouncedCountry] = React.useState<string>(
filters.country ?? '',
);
const [countryDebouncer, setCountryDebouncer] =
React.useState<NodeJS.Timeout>();

const onCityChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
setDebouncedCity(e.target.value);
};

const onCountryChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
setDebouncedCountry(e.target.value);
};

const resetFilters = () => {
setFilters({});
setDebouncedCity('');
setDebouncedCountry('');
};

React.useEffect(() => {
if (cityDebouncer) {
clearTimeout(cityDebouncer);
}
setCityDebouncer(
setTimeout(() => {
setFilters((prevFilters) => ({
...prevFilters,
city: debouncedCity.length > 0 ? debouncedCity : undefined,
}));
}, 1000),
);
}, [debouncedCity]);

React.useEffect(() => {
if (countryDebouncer) {
clearTimeout(countryDebouncer);
}
setCountryDebouncer(
setTimeout(() => {
setFilters((prevFilters) => ({
...prevFilters,
country: debouncedCountry.length > 0 ? debouncedCountry : undefined,
}));
}, 1000),
);
}, [debouncedCountry]);

return (
<Container.FlexCols className="gap-4">
<Container.FlexRow className="justify-between">
<Container.Container>
<span className="text-lg font-bold pb-4">Filters</span>
</Container.Container>
<Container.Container>
<span
className="text-brand hover:underline hover:cursor-pointer"
onClick={resetFilters}
>
<Icon.MdClear className="inline mr-2" size={24} /> Reset Filters
</span>
</Container.Container>
</Container.FlexRow>
<Container.FlexCols className="gap-2">
<span className="block text-brand text-lg">City</span>
<Input.IconInput
id="filters--city"
containerClassName="!mb-0"
placeholder="London"
icon={<Icon.MdLocationCity className="text-brand" size={20} />}
value={debouncedCity}
onChange={onCityChanged}
/>
</Container.FlexCols>
<Container.FlexCols className="gap-2">
<span className="block text-brand text-lg">Country</span>
<Input.IconInput
id="filters--country"
containerClassName="!mb-0"
placeholder="United Kingdom"
icon={<Icon.MdLocationPin className="text-brand" size={20} />}
value={debouncedCountry}
onChange={onCountryChanged}
/>
</Container.FlexCols>
</Container.FlexCols>
);
};

export default FiltersTab;
Loading

0 comments on commit 167ff0b

Please sign in to comment.