From 94378628a0c517eb1c7ab1693b845aab2cb03b52 Mon Sep 17 00:00:00 2001
From: jsun969
Date: Tue, 20 Feb 2024 02:13:51 +1030
Subject: [PATCH 1/7] feat: enable ssr for header
---
package.json | 1 +
pnpm-lock.yaml | 17 ++
src/components/Header.tsx | 194 ------------------
src/components/Header/HeaderClient.tsx | 94 +++++++++
src/components/Header/HeaderMobileClient.tsx | 93 +++++++++
src/components/Header/components/Links.tsx | 48 +++++
.../Header/components/LogoTitle.tsx | 27 +++
.../Header/components/ScrollShader.tsx | 20 ++
.../Header/components/SignInJoin.tsx | 14 ++
src/components/Header/index.tsx | 43 ++++
src/components/UserButton.tsx | 72 -------
11 files changed, 357 insertions(+), 266 deletions(-)
delete mode 100644 src/components/Header.tsx
create mode 100644 src/components/Header/HeaderClient.tsx
create mode 100644 src/components/Header/HeaderMobileClient.tsx
create mode 100644 src/components/Header/components/Links.tsx
create mode 100644 src/components/Header/components/LogoTitle.tsx
create mode 100644 src/components/Header/components/ScrollShader.tsx
create mode 100644 src/components/Header/components/SignInJoin.tsx
create mode 100644 src/components/Header/index.tsx
delete mode 100644 src/components/UserButton.tsx
diff --git a/package.json b/package.json
index 548b55ed..5e3d64ea 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"redis": "^4.6.13",
"square": "^34.0.1",
"swr": "^2.2.5",
+ "usehooks-ts": "^2.14.0",
"zod": "^3.22.4",
"zustand": "^4.4.7"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index aa25db9c..560a906d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,6 +62,9 @@ dependencies:
swr:
specifier: ^2.2.5
version: 2.2.5(react@18.2.0)
+ usehooks-ts:
+ specifier: ^2.14.0
+ version: 2.14.0(react@18.2.0)
zod:
specifier: ^3.22.4
version: 3.22.4
@@ -4255,6 +4258,10 @@ packages:
p-locate: 5.0.0
dev: true
+ /lodash.debounce@4.0.8:
+ resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+ dev: false
+
/lodash.flatmap@4.5.0:
resolution: {integrity: sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg==}
dev: false
@@ -6134,6 +6141,16 @@ packages:
react: 18.2.0
dev: false
+ /usehooks-ts@2.14.0(react@18.2.0):
+ resolution: {integrity: sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==}
+ engines: {node: '>=16.15.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ dependencies:
+ lodash.debounce: 4.0.8
+ react: 18.2.0
+ dev: false
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
deleted file mode 100644
index ea3e8824..00000000
--- a/src/components/Header.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-'use client';
-
-import FancyRectangle from '@/components/FancyRectangle';
-import { BREAKPOINTS } from '@/constants/breakpoints';
-import { useMount } from '@/hooks/use-mount';
-import { fetcher } from '@/lib/fetcher';
-import { useUser } from '@clerk/nextjs';
-import Image from 'next/image';
-import Link from 'next/link';
-import { useState } from 'react';
-import { IoMdClose, IoMdMenu } from 'react-icons/io';
-import useSWR from 'swr';
-import Button from './Button';
-import UserButton from './UserButton';
-
-export default function Header() {
- const [isMenuOpen, setIsMenuOpen] = useState(false);
- const toggleMenu = () => {
- setIsMenuOpen(!isMenuOpen);
- };
- const closeMenu = () => {
- setIsMenuOpen(false);
- };
-
- const clerkUser = useUser();
- const checkUserExists = useSWR<{ exists: boolean }>(['user-existence'], fetcher.get.query, {
- isPaused: () => clerkUser.isLoaded && !clerkUser.isSignedIn,
- });
-
- const checkUserPaid = useSWR<{ paid: boolean }>(['payment'], fetcher.get.query, {
- isPaused: () => clerkUser.isLoaded && !clerkUser.isSignedIn,
- });
-
- const [isScrolled, setIsScrolled] = useState(false);
- useMount(() => {
- window.addEventListener('scroll', handleScroll);
- window.addEventListener('resize', handleResize);
-
- return () => {
- window.removeEventListener('scroll', handleScroll);
- window.removeEventListener('resize', handleResize);
- };
- });
- const handleResize = () => {
- if (window.innerWidth >= BREAKPOINTS.md) {
- setIsMenuOpen(false);
- }
- };
- const handleScroll = () => {
- const scrollPosition = window.scrollY;
- setIsScrolled(scrollPosition > 0);
- };
-
- const isLoading = !clerkUser.isLoaded || checkUserExists.isLoading;
- return (
-
-
-
-
-
-
-
-
-
-
- CS CLUB
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/Header/HeaderClient.tsx b/src/components/Header/HeaderClient.tsx
new file mode 100644
index 00000000..552cdb6d
--- /dev/null
+++ b/src/components/Header/HeaderClient.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import { useClerk } from '@clerk/clerk-react';
+import { Transition } from '@headlessui/react';
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
+import { useRef, useState } from 'react';
+import { useOnClickOutside } from 'usehooks-ts';
+import type { HeaderData } from '.';
+import Button from '../Button';
+import FancyRectangle from '../FancyRectangle';
+import { Links, MenuLinks } from './components/Links';
+import LogoTitle from './components/LogoTitle';
+import ScrollShader from './components/ScrollShader';
+import SignInJoin from './components/SignInJoin';
+
+function UserButton({ data }: { data: HeaderData }) {
+ const [isMenuOpen, setMenuOpen] = useState(false);
+ const ref = useRef(null);
+ const closeMenu = () => {
+ setMenuOpen(false);
+ };
+ useOnClickOutside(ref, closeMenu);
+ const handleButtonClick = () => {
+ setMenuOpen(!isMenuOpen);
+ };
+
+ const { signOut } = useClerk();
+ const router = useRouter();
+ const handleSignOut = async () => {
+ await signOut();
+ router.push('/');
+ router.refresh();
+ };
+
+ const userExists = data.nextStep !== 'signup';
+ return (
+
+
+
+
+
+ {userExists && }
+
+
+
+
+
+ );
+}
+
+export default function HeaderClient({
+ data,
+ className,
+}: {
+ data: HeaderData;
+ className?: string;
+}) {
+ return (
+
+ );
+}
diff --git a/src/components/Header/HeaderMobileClient.tsx b/src/components/Header/HeaderMobileClient.tsx
new file mode 100644
index 00000000..64d1dd33
--- /dev/null
+++ b/src/components/Header/HeaderMobileClient.tsx
@@ -0,0 +1,93 @@
+'use client';
+
+import Link from 'next/link';
+import { useState } from 'react';
+import { IoMdClose, IoMdMenu } from 'react-icons/io';
+import type { HeaderData } from '.';
+import FancyRectangle from '../FancyRectangle';
+import { Links, MenuLinks } from './components/Links';
+import LogoTitle from './components/LogoTitle';
+import ScrollShader from './components/ScrollShader';
+
+export default function HeaderMobileClient({
+ data,
+ className,
+}: {
+ data: HeaderData;
+ className?: string;
+}) {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const toggleMenu = () => {
+ setIsMenuOpen(!isMenuOpen);
+ };
+ const closeMenu = () => {
+ setIsMenuOpen(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {isMenuOpen && (
+ <>
+
+
+
+
+ {data.nextStep === 'signup' && (
+
+ Continue Signing Up
+
+ )}
+ {data.nextStep === 'payment' && (
+
+ Continue to payment
+
+ )}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/Header/components/Links.tsx b/src/components/Header/components/Links.tsx
new file mode 100644
index 00000000..232cde8f
--- /dev/null
+++ b/src/components/Header/components/Links.tsx
@@ -0,0 +1,48 @@
+import { env } from '@/env.mjs';
+import Link from 'next/link';
+import type { HeaderData } from '..';
+
+export function MenuLinks({ data, onClick }: { data: HeaderData; onClick?: () => void }) {
+ const isMember = data.nextStep === null;
+ return (
+ <>
+ {isMember && (
+
+ CS Club Drive
+
+ )}
+
+ Settings
+
+ {data.isAdmin && (
+
+ Admin Panel
+
+ )}
+ >
+ );
+}
+
+const LINKS = ['about', 'events', 'sponsors', 'contact'];
+export function Links({ onClick }: { onClick?: () => void }) {
+ return (
+ <>
+ {LINKS.map((link, i) => (
+
+ {link}
+
+ ))}
+ >
+ );
+}
diff --git a/src/components/Header/components/LogoTitle.tsx b/src/components/Header/components/LogoTitle.tsx
new file mode 100644
index 00000000..c669daca
--- /dev/null
+++ b/src/components/Header/components/LogoTitle.tsx
@@ -0,0 +1,27 @@
+import Image from 'next/image';
+import Link from 'next/link';
+
+export default function LogoTitle({
+ titleColor,
+ className,
+ onClick,
+}: {
+ titleColor: 'text-grey' | 'text-white';
+ className?: string;
+ onClick?: () => void;
+}) {
+ return (
+
+
+
+ CS CLUB
+
+
+ );
+}
diff --git a/src/components/Header/components/ScrollShader.tsx b/src/components/Header/components/ScrollShader.tsx
new file mode 100644
index 00000000..463984fe
--- /dev/null
+++ b/src/components/Header/components/ScrollShader.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { useState } from 'react';
+import { useEventListener } from 'usehooks-ts';
+
+export default function ScrollShader({ className }: { className?: string }) {
+ const [isScrolled, setIsScrolled] = useState(false);
+ useEventListener('scroll', () => {
+ const scrollPosition = window.scrollY;
+ setIsScrolled(scrollPosition > 0);
+ });
+
+ return (
+
+ );
+}
diff --git a/src/components/Header/components/SignInJoin.tsx b/src/components/Header/components/SignInJoin.tsx
new file mode 100644
index 00000000..51daa3f2
--- /dev/null
+++ b/src/components/Header/components/SignInJoin.tsx
@@ -0,0 +1,14 @@
+import Button from '../../Button';
+
+export default function SignInJoin() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
new file mode 100644
index 00000000..5b96a22e
--- /dev/null
+++ b/src/components/Header/index.tsx
@@ -0,0 +1,43 @@
+import { checkUserExists } from '@/server/check-user-exists';
+import { verifyMembershipPayment } from '@/server/verify-membership-payment';
+import { currentUser } from '@clerk/nextjs';
+import HeaderClient from './HeaderClient';
+import HeaderMobileClient from './HeaderMobileClient';
+
+const getHeaderData = async () => {
+ const user = await currentUser();
+ if (!user) {
+ return { isSignedIn: false as const };
+ }
+
+ let nextStep: 'signup' | 'payment' | null = null;
+ const exists = await checkUserExists(user.id);
+ if (exists) {
+ const membershipPayment = await verifyMembershipPayment(user.id);
+ if (!membershipPayment.paid) {
+ nextStep = 'payment';
+ }
+ } else {
+ nextStep = 'signup';
+ }
+
+ return {
+ isSignedIn: true as const,
+ avatar: user.imageUrl,
+ isAdmin: (user.publicMetadata.isAdmin as boolean | undefined) ?? false,
+ nextStep,
+ isMember: nextStep === null,
+ };
+};
+export type HeaderData = Awaited>;
+
+export default async function Header() {
+ const headerData = await getHeaderData();
+
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/components/UserButton.tsx b/src/components/UserButton.tsx
deleted file mode 100644
index e5c80281..00000000
--- a/src/components/UserButton.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import Button from '@/components/Button';
-import { env } from '@/env.mjs';
-import { useClerk, useUser } from '@clerk/clerk-react';
-import Image from 'next/image';
-import Link from 'next/link';
-import { useState } from 'react';
-import FancyRectangle from './FancyRectangle';
-
-export default function UserButton({
- userExists,
- userPaid,
-}: {
- userExists: boolean;
- userPaid: boolean;
-}) {
- const { user } = useUser();
- const [isPopupOpen, setPopupOpen] = useState(false);
- const { signOut } = useClerk();
-
- const handleButtonClick = () => {
- setPopupOpen(!isPopupOpen);
- };
-
- const handleSignOut = async () => {
- await signOut();
- };
-
- if (!user) return <>>;
-
- return (
-
-
-
-
- {/* Popup menu */}
- {isPopupOpen && (
-
- {/* Only show settings if finished sign up and show drive link if membership paid */}
- {userExists && (
- <>
- {userPaid && (
-
- CS Club Drive
-
- )}
-
- Settings
-
- {user.publicMetadata.isAdmin && (
-
- Admin Panel
-
- )}
- >
- )}
- {/* Sign Out */}
-
-
- )}
-
-
- );
-}
From bcc8e71fd3860d05ca6ed8b5bd663e355a8ac9d2 Mon Sep 17 00:00:00 2001
From: jsun969
Date: Tue, 20 Feb 2024 02:14:10 +1030
Subject: [PATCH 2/7] feat(api): remove unnecessary apis
---
src/app/api/payment/route.ts | 12 ------------
src/app/api/user-existence/route.ts | 12 ------------
2 files changed, 24 deletions(-)
delete mode 100644 src/app/api/user-existence/route.ts
diff --git a/src/app/api/payment/route.ts b/src/app/api/payment/route.ts
index aac9da3c..2f000969 100644
--- a/src/app/api/payment/route.ts
+++ b/src/app/api/payment/route.ts
@@ -11,7 +11,6 @@ import { env } from '@/env.mjs';
import { redisClient } from '@/lib/redis';
import { squareClient } from '@/lib/square';
import { updateMemberExpiryDate } from '@/server/update-member-expiry-date';
-import { verifyMembershipPayment } from '@/server/verify-membership-payment';
import { currentUser } from '@clerk/nextjs';
import { eq } from 'drizzle-orm';
import type { CreatePaymentLinkRequest } from 'square';
@@ -115,14 +114,3 @@ export async function PUT(request: Request) {
}
return Response.json({ success: true });
}
-
-// Get membership payment status
-export async function GET() {
- const user = await currentUser();
- if (!user) {
- return new Response(null, { status: 401 });
- }
-
- const { paid } = await verifyMembershipPayment(user.id);
- return Response.json({ paid });
-}
diff --git a/src/app/api/user-existence/route.ts b/src/app/api/user-existence/route.ts
deleted file mode 100644
index 26a0a228..00000000
--- a/src/app/api/user-existence/route.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { checkUserExists } from '@/server/check-user-exists';
-import { currentUser } from '@clerk/nextjs';
-
-export async function GET() {
- const user = await currentUser();
- if (!user) {
- return new Response(null, { status: 401 });
- }
-
- const exists = await checkUserExists(user.id);
- return Response.json({ exists });
-}
From 64806b3aa0d22ef7c00baf6b64c4858a4169e1a0 Mon Sep 17 00:00:00 2001
From: Ray Okamoto <22254748+rayokamoto@users.noreply.github.com>
Date: Tue, 20 Feb 2024 02:17:34 +1030
Subject: [PATCH 3/7] chore: add loading for pay online button (#92)
---
src/app/(account)/settings/tabs/MembershipSettings.tsx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/app/(account)/settings/tabs/MembershipSettings.tsx b/src/app/(account)/settings/tabs/MembershipSettings.tsx
index dadb894f..e257bed9 100644
--- a/src/app/(account)/settings/tabs/MembershipSettings.tsx
+++ b/src/app/(account)/settings/tabs/MembershipSettings.tsx
@@ -52,7 +52,12 @@ export default function MembershipSettings({
Pay Membership Fee
-