Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions src/components/filters/AdvancedFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
interface Props {
values: any;
onChange: (name: string, value: any) => void;
}

export default function AdvancedFilters({
values,
onChange,
}: Props) {
return (
<>
<ParkingFilter
value={values.parking}
onChange={onChange}
/>

<YearBuiltFilter
value={values.yearBuilt}
onChange={onChange}
/>

<AmenitiesFilter
value={values.amenities}
onChange={onChange}
/>

<FurnishedFilter
value={values.furnished}
onChange={onChange}
/>
</>
);
}
33 changes: 33 additions & 0 deletions src/components/filters/FilterSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState } from "react";

interface FilterSectionProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
}

export default function FilterSection({
title,
children,
defaultOpen = true,
}: FilterSectionProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);

return (
<div className="border-b py-4">
<button
className="flex w-full items-center justify-between font-semibold"
onClick={() => setIsOpen(!isOpen)}
>
<span>{title}</span>
<span>{isOpen ? "−" : "+"}</span>
</button>

{isOpen && (
<div className="mt-4 space-y-3">
{children}
</div>
)}
</div>
);
}
33 changes: 33 additions & 0 deletions src/components/filters/FilterSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import FilterSection from "./FilterSection";
import AdvancedFilters from "./AdvancedFilters";

export default function FilterSidebar(props) {
return (
<>
<FilterSection title="Location">
<LocationFilter />
</FilterSection>

<FilterSection title="Price">
<PriceFilter />
</FilterSection>

<FilterSection title="Type">
<PropertyTypeFilter />
</FilterSection>

<FilterSection title="Size">
<BedroomsFilter />
<BathroomsFilter />
<AreaFilter />
</FilterSection>

<FilterSection
title="Advanced"
defaultOpen={false}
>
<AdvancedFilters {...props} />
</FilterSection>
</>
);
}
3 changes: 3 additions & 0 deletions src/components/filters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as FilterSidebar } from "./FilterSidebar";
export { default as FilterSection } from "./FilterSection";
export { default as AdvancedFilters } from "./AdvancedFilters";
35 changes: 35 additions & 0 deletions src/components/referral/EmailTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
interface Props {
referralLink: string;
}

export default function EmailTemplate({
referralLink,
}: Props) {
return (
<div
style={{
fontFamily: "Arial",
padding: "40px",
background: "#f5f5f5",
}}
>
<div
style={{
background: "#fff",
padding: "30px",
borderRadius: "10px",
}}
>
<h2>Join PropChain</h2>

<p>
I invited you to join PropChain.
</p>

<a href={referralLink}>
Accept Invitation
</a>
</div>
</div>
);
}
29 changes: 29 additions & 0 deletions src/components/referral/ReferralShare.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ShareButtons from "./ShareButtons";
import CopyButton from "./CopyButton";

interface Props {
referralLink: string;
referralCode: string;
}

export default function ReferralShare({
referralLink,
}: Props) {
return (
<div className="rounded-lg border p-6 space-y-4">
<h2>Invite Friends</h2>

<p>
Share your referral link and earn rewards.
</p>

<ShareButtons
referralLink={referralLink}
/>

<CopyButton
text={referralLink}
/>
</div>
);
}
29 changes: 29 additions & 0 deletions src/components/referral/ShareButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useShare } from "@/hooks/useShare";
import {
SHARE_TEXT,
SHARE_TITLE,
} from "@/lib/share";

interface Props {
referralLink: string;
}

export default function ShareButtons({
referralLink,
}: Props) {
const { share } = useShare();

return (
<button
onClick={() =>
share({
title: SHARE_TITLE,
text: SHARE_TEXT,
url: referralLink,
})
}
>
Share
</button>
);
}
4 changes: 4 additions & 0 deletions src/components/referral/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { default as ReferralShare } from "./ReferralShare";
export { default as ShareButtons } from "./ShareButtons";
export { default as CopyButton } from "./CopyButton";
export { default as EmailTemplate } from "./EmailTemplate";
35 changes: 35 additions & 0 deletions src/components/toast/ErrorToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
interface ErrorToastProps {
message: string;
onRetry?: () => void;
docsUrl?: string;
}

export default function ErrorToast({
message,
onRetry,
docsUrl,
}: ErrorToastProps) {
return (
<div className="space-y-3">
<p>{message}</p>

<div className="flex gap-2">
{onRetry && (
<button onClick={onRetry}>
Try Again
</button>
)}

{docsUrl && (
<a
href={docsUrl}
target="_blank"
rel="noreferrer"
>
Learn More
</a>
)}
</div>
</div>
);
}
18 changes: 18 additions & 0 deletions src/hooks/usePaginationUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useSearchParams } from "react-router-dom";

export function usePaginationUrl() {
const [searchParams, setSearchParams] = useSearchParams();

const page =
Number(searchParams.get("page")) || 1;

const setPage = (newPage: number) => {
searchParams.set("page", newPage.toString());
setSearchParams(searchParams);
};

return {
page,
setPage,
};
}
22 changes: 22 additions & 0 deletions src/hooks/useShare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface ShareData {
title: string;
text: string;
url: string;
}

export function useShare() {
const share = async ({ title, text, url }: ShareData) => {
if (navigator.share) {
await navigator.share({
title,
text,
url,
});
return;
}

await navigator.clipboard.writeText(url);
};

return { share };
}
6 changes: 6 additions & 0 deletions src/lib/share.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const SHARE_TITLE = "Join PropChain";

export const SHARE_TEXT =
"Join me on PropChain and start investing using my referral link.";

export const SHARE_BUTTON_TEXT = "Share";
24 changes: 24 additions & 0 deletions src/lib/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { toast } from "sonner";
// or react-hot-toast depending on the project

import ErrorToast from "@/components/toast/ErrorToast";

interface ErrorToastOptions {
message: string;
onRetry?: () => void;
docsUrl?: string;
}

export function showErrorToast({
message,
onRetry,
docsUrl,
}: ErrorToastOptions) {
toast.custom(() => (
<ErrorToast
message={message}
onRetry={onRetry}
docsUrl={docsUrl}
/>
));
}
9 changes: 9 additions & 0 deletions tests/ReferralShare.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
describe("ReferralShare", () => {
it("renders share button", () => {});

it("renders copy button", () => {});

it("calls navigator.share when supported", () => {});

it("copies the link when Web Share API isn't available", () => {});
});