Skip to content

Commit

Permalink
implement donate page
Browse files Browse the repository at this point in the history
  • Loading branch information
dominicarrojado committed May 22, 2024
1 parent 35877c2 commit deab830
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 22 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": ["next/core-web-vitals", "prettier"]
"extends": ["next/core-web-vitals", "prettier"],
"rules": {
"@next/next/no-img-element": "off"
}
}
116 changes: 116 additions & 0 deletions app/donate/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Metadata } from "next";
import React from "react";
import { Container } from "@/components/ui/container";
import { Anchor } from "@/components/ui/anchor";
import Heading from "@/components/ui/heading";
import Subheading from "@/components/ui/subheading";
import Paragraph from "@/components/ui/paragraph";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { getAssetUrl } from "@/lib/assets";
import { Routes } from "@/lib/enums";
import { META_OPEN_GRAPH, META_TWITTER } from "../shared-metadata";
import Testimonials from "./testimonials";

const title = "Support and Donate";
const description =
"Your kind donation will be sincerely appreciated and will go a long way.";
const url = Routes.Donate;

export const metadata: Metadata = {
title,
description,
alternates: {
canonical: url,
},
openGraph: {
...META_OPEN_GRAPH,
title,
description,
url,
},
twitter: {
...META_TWITTER,
title,
description,
},
};

export default function Donate() {
return (
<Container>
<div className="space-y-2">
<Heading>{title}</Heading>
<Subheading>{description}</Subheading>
</div>
<Paragraph>
I created <span className="font-medium">SG Alerts</span> to help people
save time and effort. While this service is free to use, it takes time
and effort to maintain and incur costs to run. Your donation will help
me cover the costs and keep the services running. It will also be a
great motivation for me to continue improving the service with new
features.
</Paragraph>
<Paragraph>
If you&apos;d like to support my work with a donation, I would be very
grateful. Every donation will be sincerely appreciated and will go a
long way. Thank you very much for your support ~
</Paragraph>
<div className="flex flex-col gap-4 mt-6 sm:flex-row">
<Card className="w-72 max-w-full mx-auto sm:w-1/2">
<CardContent className="flex aspect-square items-center justify-center p-4">
<img
src={getAssetUrl("paylah.jpg")}
className="w-full h-auto aspect-square"
alt="DBS PayLah! QR code"
width="540"
height="540"
draggable="false"
/>
</CardContent>
<CardFooter className="justify-center">
<Paragraph className="text-center">
Scan with{" "}
<Anchor
href="https://www.dbs.com.sg/personal/deposits/pay-with-ease/dbs-paylah"
isExternal
>
DBS PayLah!
</Anchor>{" "}
<br />
(for Singapore Residents)
</Paragraph>
</CardFooter>
</Card>
<Card className="w-72 max-w-full mx-auto sm:w-1/2">
<Anchor
href="https://www.paypal.com/paypalme/DominicArrojado"
className="no-underline"
isExternal
>
<CardContent className="flex aspect-square items-center justify-center p-4">
<img
src={getAssetUrl("paypal.svg")}
className="w-full h-auto aspect-square bg-white p-10"
alt="PayPal"
width="540"
height="540"
draggable="false"
/>
</CardContent>
<CardFooter className="justify-center">
<Paragraph className="text-center">
Click to donate via PayPal <br />
(For International Supporters)
</Paragraph>
</CardFooter>
</Anchor>
</Card>
</div>
<Paragraph>
Thank you to all the kind souls who have donated so far. I truly
appreciate your support and encouragement.
</Paragraph>
<Testimonials />
</Container>
);
}
76 changes: 76 additions & 0 deletions app/donate/testimonials.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import React, { useEffect, useMemo, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { DONATION_TESTIMONIALS } from "@/lib/content";
import { DonationTestimonials } from "@/lib/types";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";

export default function Testimonials() {
const [isMobile, setIsMobile] = useState(false);
const testimonialArrays = useMemo(() => {
if (isMobile) {
return [DONATION_TESTIMONIALS];
}

// split testimonials into 2 columns
return DONATION_TESTIMONIALS.reduce(
(acc, curr, index) => {
if (index % 2 === 0) {
acc[0].push(curr);
} else {
acc[1].push(curr);
}
return acc;
},
[[] as DonationTestimonials, [] as DonationTestimonials]
);
}, [isMobile]);

useEffect(() => {
const windowOnResize = () => {
setIsMobile(window.innerWidth < 640);
};

window.addEventListener("resize", windowOnResize);

return () => {
window.removeEventListener("resize", windowOnResize);
};
}, []);

return (
<div className="mt-6 grid grid-cols-1 grid-rows-1 gap-4 sm:grid-cols-2">
{testimonialArrays.map((testimonials, index) => (
<ul key={index} className="space-y-4">
{testimonials.map((testimonial, index) => (
<Card key={index}>
<CardContent className="p-4 space-y-2">
<div className="flex items-center space-x-4">
<Avatar className="w-[48px] h-[48px]">
<AvatarFallback>
{testimonial.firstName[0]}
{testimonial.lastName}
</AvatarFallback>
</Avatar>
<div>
<h4 className="text-md">{testimonial.firstName}</h4>
<span className="text-xs text-muted-foreground">
{testimonial.date}
</span>
</div>
</div>
{testimonial.message && (
<blockquote className="pt-2">
<p className="text-sm">{testimonial.message}</p>
</blockquote>
)}
<div className="flex items-center"></div>
</CardContent>
</Card>
))}
</ul>
))}
</div>
);
}
9 changes: 5 additions & 4 deletions app/settings/settings-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { FormEvent, useMemo, useState } from "react";
import { AlertCircle, CheckCircle, Loader2Icon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
Card,
Expand All @@ -23,10 +24,10 @@ import { Subscription, SubscriptionTopics } from "@/lib/types";
import {
FetchStatus,
GoogleAnalyticsEvent,
Routes,
SubscriptionTopic,
} from "@/lib/enums";
import { NOTIFICATION_SETTINGS } from "@/lib/content";
import { OWNER_DONATION_LINK } from "@/lib/constants";

type Props = {
subscription: Subscription;
Expand Down Expand Up @@ -136,9 +137,9 @@ export default function SettingsForm({ subscription }: Props) {
You have unsubscribed from all topics. We hope to see you
again soon! <br />
If SG Alerts has been helpful to you, please consider{" "}
<Anchor href={OWNER_DONATION_LINK} target="_blank">
donating
</Anchor>
<Link href={Routes.Donate} passHref legacyBehavior>
<Anchor>donating</Anchor>
</Link>
.
</>
)}
Expand Down
5 changes: 3 additions & 2 deletions app/shared-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
MAIN_DESCRIPTION,
MAIN_IMAGE,
MAIN_TITLE,
MAIN_URL,
SITE_NAME,
Expand All @@ -17,7 +18,7 @@ export const META_OPEN_GRAPH = {
siteName: SITE_NAME,
images: [
{
url: `${MAIN_URL}og-image.png`,
url: MAIN_IMAGE,
width: 1200,
height: 750,
alt: SITE_NAME,
Expand All @@ -32,5 +33,5 @@ export const META_TWITTER = {
template: `%s - ${MAIN_TITLE}`,
},
description: MAIN_DESCRIPTION,
images: [`${MAIN_URL}og-image.png`],
images: [MAIN_IMAGE],
};
14 changes: 6 additions & 8 deletions components/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import Link from "next/link";
import React from "react";
import Balancer from "react-wrap-balancer";
import {
OWNER_DONATION_LINK,
OWNER_NAME,
OWNER_WEBSITE,
} from "@/lib/constants";
import { Routes } from "@/lib/enums";
import { OWNER_NAME, OWNER_WEBSITE } from "@/lib/constants";
import { Container } from "./ui/container";
import { Anchor } from "./ui/anchor";

Expand All @@ -14,9 +12,9 @@ export default function Footer() {
<p className="text-center text-sm leading-loose text-muted-foreground">
<Balancer>
Like the service? Please consider{" "}
<Anchor href={OWNER_DONATION_LINK} target="_blank">
donating
</Anchor>{" "}
<Link href={Routes.Donate} passHref legacyBehavior>
<Anchor>donating</Anchor>
</Link>{" "}
to support this free notification service. Every donation is sincerely
appreciated! 🙏
</Balancer>
Expand Down
4 changes: 2 additions & 2 deletions components/ui/anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from "clsx";
import React, { ForwardedRef, HTMLProps } from "react";
import { cn } from "@/lib/utils";

export type Props = HTMLProps<HTMLAnchorElement> & {
isExternal?: boolean;
Expand All @@ -12,7 +12,7 @@ const Anchor = React.forwardRef(
<a
{...props}
ref={ref}
className={clsx("font-medium underline underline-offset-4", className)}
className={cn("font-medium underline underline-offset-4", className)}
rel={isExternal ? "noopener noreferrer nofollow" : undefined}
target={isExternal ? "_blank" : target}
>
Expand Down
50 changes: 50 additions & 0 deletions components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client"

import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

import { cn } from "@/lib/utils"

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName

export { Avatar, AvatarImage, AvatarFallback }
10 changes: 8 additions & 2 deletions components/ui/paragraph.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { ReactNode } from "react";
import { cn } from "@/lib/utils";

type Props = {
children: ReactNode;
className?: string;
};

export default function Paragraph({ children }: Props) {
return <p className="leading-7 [&:not(:first-child)]:mt-6">{children}</p>;
export default function Paragraph({ children, className }: Props) {
return (
<p className={cn("leading-7 [&:not(:first-child)]:mt-6", className)}>
{children}
</p>
);
}
7 changes: 7 additions & 0 deletions lib/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MAIN_URL } from "./constants";

export function getAssetUrl(path: string) {
return process.env.NODE_ENV === "production"
? `${MAIN_URL}${path}`
: `/${path}`;
}
3 changes: 1 addition & 2 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { CdcService, JapanVisaType, Routes, SsdcService } from "./enums";
export const OWNER_NAME = "Dominic Arrojado";
export const OWNER_WEBSITE = "https://dominicarrojado.com";
export const OWNER_EMAIL = "dominicarrojado@gmail.com";
export const OWNER_DONATION_LINK = `${OWNER_WEBSITE}/donate/`;

export const API_URL = process.env.NEXT_PUBLIC_API_URL;

Expand All @@ -13,7 +12,7 @@ export const MAIN_DESCRIPTION =
"Subscribe to all things Singapore. Save time. Stay updated.";
export const MAIN_ORIGIN = "https://dominicarrojado.com";
export const MAIN_URL = `${MAIN_ORIGIN}/sg-alerts/`;
export const MAIN_IMAGE = `${MAIN_ORIGIN}/images/og-image.png`;
export const MAIN_IMAGE = `${MAIN_URL}og-image.png`;

export const ROUTES_WITH_NO_TITLE = [
Routes.Home,
Expand Down
Loading

0 comments on commit deab830

Please sign in to comment.