From 20b984720e4e7b7ac4b06fabfa0b65eafd6f5fef Mon Sep 17 00:00:00 2001
From: Rohin Juneja
Date: Thu, 5 Dec 2024 13:05:39 -0800
Subject: [PATCH 1/9] [feat] new success and verification pages with full
functionality EXCEPT automatic rerouting from verification to success is not
yet functional
---
api/supabase/queries/auth.ts | 56 ++++++++++++
app/(auth)/signup/page.tsx | 2 +-
app/(auth)/success/page.tsx | 38 ++++++++
app/(auth)/verification/page.tsx | 128 ++++++++++++++++++++++++++
app/onboarding/finalize/page.tsx | 4 +-
public/images/bud.svg | 8 ++
public/images/email.svg | 4 +
public/images/rose-greenbg.svg | 8 ++
styles/styles.ts | 152 +++++++++++++++++++++++++++++++
utils/AuthProvider.tsx | 17 +++-
10 files changed, 410 insertions(+), 7 deletions(-)
create mode 100644 app/(auth)/success/page.tsx
create mode 100644 app/(auth)/verification/page.tsx
create mode 100644 public/images/bud.svg
create mode 100644 public/images/email.svg
create mode 100644 public/images/rose-greenbg.svg
create mode 100644 styles/styles.ts
diff --git a/api/supabase/queries/auth.ts b/api/supabase/queries/auth.ts
index bd16eef..f1d42ea 100644
--- a/api/supabase/queries/auth.ts
+++ b/api/supabase/queries/auth.ts
@@ -5,6 +5,7 @@ export async function handleSignUp(
password: string,
): Promise<{ success: boolean; message: string }> {
try {
+ await ensureLoggedOutForNewUser(email);
const { data, error } = await supabase.auth.signUp({ email, password });
if (error) {
@@ -37,6 +38,8 @@ export async function handleSignUp(
};
}
+ localStorage.setItem('tempEmail', email);
+
return { success: true, message: 'Sign-up successful!' };
} catch (err) {
if (err instanceof Error) {
@@ -54,6 +57,7 @@ export async function handleSignIn(
password: string,
): Promise<{ success: boolean; message: string }> {
try {
+ await ensureLoggedOutForNewUser(email);
const { error: signInError } = await supabase.auth.signInWithPassword({
email,
password,
@@ -96,6 +100,37 @@ export async function handleSignIn(
}
}
+export const handleSignOut = async () => {
+ const { error } = await supabase.auth.signOut();
+ if (error) {
+ console.error('Error during logout:', error.message);
+ return;
+ }
+};
+
+export async function ensureLoggedOutForNewUser(
+ newEmail: string,
+): Promise {
+ const { data, error } = await supabase.auth.getSession();
+
+ if (error) {
+ console.error('Error fetching session:', error.message);
+ throw new Error('Failed to fetch session.');
+ }
+
+ const session = data.session;
+
+ if (
+ session &&
+ session.user &&
+ session.user.email &&
+ session.user.email !== newEmail
+ ) {
+ console.log(`Logging out current user: ${session.user.email}`);
+ await handleSignOut();
+ }
+}
+
export async function checkUserExists(
userId: string,
userType: 'volunteer' | 'facility',
@@ -118,3 +153,24 @@ export async function checkUserExists(
return false;
}
}
+
+export async function resendVerificationEmail(email: string): Promise {
+ try {
+ const { error } = await supabase.auth.resend({
+ type: 'signup',
+ email: email,
+ });
+
+ if (error) {
+ return `Error: ${error.message}`;
+ }
+
+ return 'Verification email resent successfully!';
+ } catch (err) {
+ return 'Unexpected error while resending verification email.';
+ }
+}
+
+export function getTempEmail(): string | null {
+ return localStorage.getItem('tempEmail');
+}
diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx
index f600f19..a47b75f 100644
--- a/app/(auth)/signup/page.tsx
+++ b/app/(auth)/signup/page.tsx
@@ -44,7 +44,7 @@ export default function SignUp() {
setIsError(!success);
if (success) {
- router.push('/onboarding/role-selection');
+ router.push('/verification');
}
};
diff --git a/app/(auth)/success/page.tsx b/app/(auth)/success/page.tsx
new file mode 100644
index 0000000..adf1e7e
--- /dev/null
+++ b/app/(auth)/success/page.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import Rose from '@/public/images/rose-greenbg.svg';
+import {
+ Background,
+ Image,
+ InlineContainer,
+ ReviewContainer,
+ RoundedCornerButton,
+ Title,
+} from '../../../styles/styles';
+
+export default function Success() {
+ const router = useRouter(); // Initialize useRouter
+
+ const handleContinue = () => {
+ router.push('/onboarding/general'); // Navigate to the onboarding/general page
+ };
+
+ return (
+
+
+
+
+ Successfully verified!
+
+ Your email has been verified. Please use this email address to login
+ in the future.
+
+
+ Continue
+
+
+
+
+ );
+}
diff --git a/app/(auth)/verification/page.tsx b/app/(auth)/verification/page.tsx
new file mode 100644
index 0000000..61b4b93
--- /dev/null
+++ b/app/(auth)/verification/page.tsx
@@ -0,0 +1,128 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { useRouter } from 'next/navigation';
+import supabase from '@/api/supabase/createClient';
+import {
+ getTempEmail,
+ resendVerificationEmail,
+} from '@/api/supabase/queries/auth';
+import Bud from '@/public/images/bud.svg';
+import EmailIcon from '@/public/images/email.svg';
+import {
+ Background,
+ EmailContainer,
+ EmailIconStyled,
+ EmailText,
+ Footer,
+ Image,
+ InlineContainer,
+ Link,
+ ResendMessage,
+ ReviewContainer,
+ RoundedCornerButton,
+ Title,
+} from '../../../styles/styles';
+
+export default function Verification() {
+ const router = useRouter(); // Initialize useRouter
+ const [tempEmail, setTempEmail] = useState(null);
+ const [resendStatus, setResendStatus] = useState('');
+ const [isError, setIsError] = useState(false);
+
+ useEffect(() => {
+ // TODO: checking if user is verified every 5 seconds until user is verified or if page is reloaded/closed.
+ // Not sure if this is the most efficient approach
+
+ // TODO: this still isn't working rn because session is null until sign-in
+ const checkVerificationStatus = async () => {
+ const {
+ data: { session },
+ } = await supabase.auth.getSession();
+ console.log(session);
+
+ if (session?.user?.user_metadata.email_verified) {
+ router.push('/success');
+ }
+ };
+
+ const interval = setInterval(checkVerificationStatus, 5000);
+
+ return () => clearInterval(interval);
+ }, [router]);
+
+ useEffect(() => {
+ const email = getTempEmail();
+ setTempEmail(email);
+ }, []);
+
+ const handleResendLink = async () => {
+ if (tempEmail) {
+ const message = await resendVerificationEmail(tempEmail);
+ setIsError(message.includes('Error'));
+ setResendStatus(message);
+ } else {
+ setIsError(true);
+ }
+ };
+
+ const handleUseAnotherAccount = () => {
+ router.push('/signin');
+ localStorage.removeItem('tempEmail');
+ };
+
+ // TODO: Restyle error message on lines 95-100 (the message containing link back to sign-up)
+
+ return (
+
+
+
+
+ Verification Needed
+ Thanks for signing up!
+
+ A verification link has been sent to the email you specified, please
+ check your inbox for next steps.
+
+
+
+
+
+ {tempEmail ? tempEmail : 'Email address not found'}
+
+
+
+
+ Use another account
+
+
+
+
+ {isError && !tempEmail && (
+
+ Email address not found!{' '}
+ router.push('/signup')}>
+ Return to the sign-up page
+ {' '}
+ to restart the sign-up process.
+
+ )}
+
+ {resendStatus && tempEmail && (
+ {resendStatus}
+ )}
+
+
+
+ );
+}
diff --git a/app/onboarding/finalize/page.tsx b/app/onboarding/finalize/page.tsx
index 416fefc..41e9c01 100644
--- a/app/onboarding/finalize/page.tsx
+++ b/app/onboarding/finalize/page.tsx
@@ -3,14 +3,14 @@
import { useRouter } from 'next/navigation';
import Rose from '@/public/images/rose.svg';
import { SMALL } from '@/styles/text';
-import { Background } from '../styles';
import {
ContinueButton,
Image,
InlineContainer,
ReviewContainer,
Title,
-} from './styles';
+} from '../../../styles/styles';
+import { Background } from '../styles';
export default function Onboarding() {
const router = useRouter(); // Initialize useRouter
diff --git a/public/images/bud.svg b/public/images/bud.svg
new file mode 100644
index 0000000..7adb2f4
--- /dev/null
+++ b/public/images/bud.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/images/email.svg b/public/images/email.svg
new file mode 100644
index 0000000..f591575
--- /dev/null
+++ b/public/images/email.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/images/rose-greenbg.svg b/public/images/rose-greenbg.svg
new file mode 100644
index 0000000..4e20d89
--- /dev/null
+++ b/public/images/rose-greenbg.svg
@@ -0,0 +1,8 @@
+
diff --git a/styles/styles.ts b/styles/styles.ts
new file mode 100644
index 0000000..9f7f10c
--- /dev/null
+++ b/styles/styles.ts
@@ -0,0 +1,152 @@
+'use client';
+
+import NextImage from 'next/image';
+import styled from 'styled-components';
+import { Sans } from '@/styles/fonts';
+import { SMALL } from '@/styles/text';
+import COLORS from './colors';
+
+export const Background = styled.main`
+ flex-direction: column;
+ min-width: 100%;
+ min-height: 100svh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+`;
+
+export const Title = styled.h1`
+ font-size: 24px;
+ text-align: start;
+ color: ${COLORS.gray11};
+ font-weight: 500;
+`;
+
+export const InlineContainer = styled.main`
+ width: 25%;
+ flex-direction: column;
+ margin-top: 2%;
+ margin-bottom: 2%;
+ @media (max-width: 1200px) {
+ width: 30%;
+ }
+ @media (max-width: 768px) {
+ width: 80%;
+ }
+`;
+
+export const Image = styled(NextImage)`
+ width: 85px;
+ height: 85px;
+ flex-shrink: 0;
+ position: relative;
+ top: 60px;
+ z-index: 1;
+`;
+
+export const Circle = styled.main`
+ width: 85px;
+ height: 85px;
+ flex-shrink: 0;
+ background-color: #f7c1bd;
+ border-radius: 100%;
+ position: relative;
+ top: 60px;
+ z-index: 1;
+`;
+
+export const ReviewContainer = styled.main`
+ position: relative; /* This makes the Circle position relative to this container */
+ display: flex;
+ padding: 60px 32px 32px 32px;
+ flex-direction: column;
+ justify-content: center;
+ align-items: start;
+ gap: 20px;
+ border-radius: 16px;
+ background: var(--bread-1, #fefdfc);
+ box-shadow: 4px 4px 4px 0px rgba(0, 0, 0, 0.15);
+`;
+
+export const ContinueButton = styled.button`
+ display: flex;
+ margin-top: 0.75rem;
+ padding: 12px 16px;
+ justify-content: center;
+ align-items: center;
+ align-self: stretch;
+ border-radius: 99999px;
+ background: ${COLORS.pomegranate};
+ border-style: solid;
+ border-color: ${COLORS.gray12};
+ cursor: pointer;
+`;
+
+interface RoundedCornerButtonProps {
+ width?: string;
+ bgColor?: string;
+ textColor?: string;
+}
+
+export const RoundedCornerButton = styled.button`
+ font-family: ${Sans.style.fontFamily};
+ background-color: ${props => props.bgColor || COLORS.pomegranate};
+ color: ${props => props.textColor || 'white'};
+ font-size: 1rem;
+ padding: 0.55rem;
+ border: 1.5px solid black;
+ border-radius: 8px;
+
+ cursor: pointer;
+ margin-top: 0.5rem;
+ width: ${props => props.width || '100%'};
+ margin-left: auto;
+ margin-right: auto;
+`;
+
+export const Footer = styled.div`
+ font-family: ${Sans.style.fontFamily};
+ text-align: center;
+ margin-top: -0.75rem;
+ width: 100%;
+ padding: 0.5rem;
+`;
+
+export const Link = styled.a`
+ color: ${COLORS.lilac9};
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+export const EmailContainer = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: -0.9rem;
+ gap: 8px;
+`;
+
+export const EmailIconStyled = styled(NextImage)`
+ width: 24px;
+ height: 24px;
+ margin-right: -4px;
+`;
+
+export const EmailText = styled.p`
+ font-size: 1rem;
+ color: ${COLORS.gray11};
+ line-height: 1.5;
+ display: inline-block;
+`;
+
+export const ResendMessage = styled(SMALL)<{ $isError: boolean }>`
+ color: ${({ $isError }) => ($isError ? 'red' : 'green')};
+ text-align: center;
+ font-weight: 400;
+ margin-top: -0.75rem;
+ font-size: 1rem;
+ display: block;
+`;
diff --git a/utils/AuthProvider.tsx b/utils/AuthProvider.tsx
index 97549d9..22057a5 100644
--- a/utils/AuthProvider.tsx
+++ b/utils/AuthProvider.tsx
@@ -48,18 +48,27 @@ export function AuthContextProvider({
setSession(newSession);
};
- const signInWithEmail = async (email: string, password: string) =>
- supabase.auth.signInWithPassword({
+ const signInWithEmail = async (email: string, password: string) => {
+ const response = supabase.auth.signInWithPassword({
email,
password,
}); // will trigger the use effect to update the session
- const signUp = async (email: string, password: string) =>
- supabase.auth.signUp({
+
+ return response;
+ };
+
+ const signUp = async (email: string, password: string) => {
+ const response = await supabase.auth.signUp({
email,
password,
}); // will trigger the use effect to update the session
+
+ return response;
+ };
+
const signOut = () => {
supabase.auth.signOut();
+ localStorage.removeItem('tempEmail');
setSession(null);
};
From fd937c01e968139bdb44f3b917aaecc14a2922d0 Mon Sep 17 00:00:00 2001
From: Rohin Juneja
Date: Sat, 7 Dec 2024 12:22:39 -0800
Subject: [PATCH 2/9] [fix] added automatic routing from verification to
success page
---
api/supabase/queries/auth.ts | 5 ++++-
app/(auth)/success/page.tsx | 4 +++-
app/(auth)/verification/page.tsx | 27 +++++++--------------------
3 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/api/supabase/queries/auth.ts b/api/supabase/queries/auth.ts
index f1d42ea..c4683f3 100644
--- a/api/supabase/queries/auth.ts
+++ b/api/supabase/queries/auth.ts
@@ -6,7 +6,10 @@ export async function handleSignUp(
): Promise<{ success: boolean; message: string }> {
try {
await ensureLoggedOutForNewUser(email);
- const { data, error } = await supabase.auth.signUp({ email, password });
+ const { data, error } = await supabase.auth.signUp({
+ email,
+ password,
+ });
if (error) {
return { success: false, message: `Sign-up failed: ${error.message}` };
diff --git a/app/(auth)/success/page.tsx b/app/(auth)/success/page.tsx
index adf1e7e..cddae6b 100644
--- a/app/(auth)/success/page.tsx
+++ b/app/(auth)/success/page.tsx
@@ -1,6 +1,8 @@
'use client';
+import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
+import supabase from '@/api/supabase/createClient';
import Rose from '@/public/images/rose-greenbg.svg';
import {
Background,
@@ -15,7 +17,7 @@ export default function Success() {
const router = useRouter(); // Initialize useRouter
const handleContinue = () => {
- router.push('/onboarding/general'); // Navigate to the onboarding/general page
+ router.push('/onboarding/role-selection'); // Navigate to the onboarding/general page
};
return (
diff --git a/app/(auth)/verification/page.tsx b/app/(auth)/verification/page.tsx
index 61b4b93..7216f86 100644
--- a/app/(auth)/verification/page.tsx
+++ b/app/(auth)/verification/page.tsx
@@ -2,13 +2,14 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
-import supabase from '@/api/supabase/createClient';
import {
getTempEmail,
resendVerificationEmail,
} from '@/api/supabase/queries/auth';
import Bud from '@/public/images/bud.svg';
import EmailIcon from '@/public/images/email.svg';
+// import supabase from '@/api/supabase/createClient';
+import { useSession } from '@/utils/AuthProvider';
import {
Background,
EmailContainer,
@@ -29,27 +30,13 @@ export default function Verification() {
const [tempEmail, setTempEmail] = useState(null);
const [resendStatus, setResendStatus] = useState('');
const [isError, setIsError] = useState(false);
+ const { session } = useSession();
useEffect(() => {
- // TODO: checking if user is verified every 5 seconds until user is verified or if page is reloaded/closed.
- // Not sure if this is the most efficient approach
-
- // TODO: this still isn't working rn because session is null until sign-in
- const checkVerificationStatus = async () => {
- const {
- data: { session },
- } = await supabase.auth.getSession();
- console.log(session);
-
- if (session?.user?.user_metadata.email_verified) {
- router.push('/success');
- }
- };
-
- const interval = setInterval(checkVerificationStatus, 5000);
-
- return () => clearInterval(interval);
- }, [router]);
+ if (session) {
+ router.push('/success');
+ }
+ }, [session, router]);
useEffect(() => {
const email = getTempEmail();
From 4549cc0df75724648a09f5d9b09b36d376dba685 Mon Sep 17 00:00:00 2001
From: Rohin Juneja
Date: Sat, 7 Dec 2024 12:23:01 -0800
Subject: [PATCH 3/9] [fix] added automatic routing from verification to
success page
---
app/(auth)/success/page.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/(auth)/success/page.tsx b/app/(auth)/success/page.tsx
index cddae6b..30210a7 100644
--- a/app/(auth)/success/page.tsx
+++ b/app/(auth)/success/page.tsx
@@ -1,8 +1,6 @@
'use client';
-import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
-import supabase from '@/api/supabase/createClient';
import Rose from '@/public/images/rose-greenbg.svg';
import {
Background,
From fb0872595722616b877431516de32f3543f79e7e Mon Sep 17 00:00:00 2001
From: Rohin Juneja
Date: Sat, 7 Dec 2024 14:27:07 -0800
Subject: [PATCH 4/9] [fix] addressed comments: removed unused imports,
reformatted import path directories, and removed local styles from global
styles file
---
app/(auth)/verification/page.tsx | 22 +++++++------
.../verification/verification-styles.ts | 33 +++++++++++++++++++
styles/styles.ts | 30 -----------------
3 files changed, 45 insertions(+), 40 deletions(-)
create mode 100644 app/(auth)/verification/verification-styles.ts
diff --git a/app/(auth)/verification/page.tsx b/app/(auth)/verification/page.tsx
index 7216f86..4793ea5 100644
--- a/app/(auth)/verification/page.tsx
+++ b/app/(auth)/verification/page.tsx
@@ -8,22 +8,24 @@ import {
} from '@/api/supabase/queries/auth';
import Bud from '@/public/images/bud.svg';
import EmailIcon from '@/public/images/email.svg';
-// import supabase from '@/api/supabase/createClient';
-import { useSession } from '@/utils/AuthProvider';
import {
Background,
- EmailContainer,
- EmailIconStyled,
- EmailText,
Footer,
Image,
InlineContainer,
Link,
- ResendMessage,
ReviewContainer,
RoundedCornerButton,
Title,
-} from '../../../styles/styles';
+} from '@/styles/styles';
+import { P } from '@/styles/text';
+import { useSession } from '@/utils/AuthProvider';
+import {
+ EmailContainer,
+ EmailIconStyled,
+ EmailText,
+ ResendMessage,
+} from './verification-styles';
export default function Verification() {
const router = useRouter(); // Initialize useRouter
@@ -66,11 +68,11 @@ export default function Verification() {
Verification Needed
- Thanks for signing up!
-
+ Thanks for signing up!
+
A verification link has been sent to the email you specified, please
check your inbox for next steps.
-
+
diff --git a/app/(auth)/verification/verification-styles.ts b/app/(auth)/verification/verification-styles.ts
new file mode 100644
index 0000000..72c7b67
--- /dev/null
+++ b/app/(auth)/verification/verification-styles.ts
@@ -0,0 +1,33 @@
+import NextImage from 'next/image';
+import styled from 'styled-components';
+import COLORS from '@/styles/colors';
+import { SMALL } from '@/styles/text';
+
+export const EmailContainer = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: -0.9rem;
+ gap: 8px;
+`;
+
+export const EmailIconStyled = styled(NextImage)`
+ width: 24px;
+ height: 24px;
+ margin-right: -4px;
+`;
+
+export const EmailText = styled.p`
+ font-size: 1rem;
+ color: ${COLORS.gray11};
+ line-height: 1.5;
+ display: inline-block;
+`;
+
+export const ResendMessage = styled(SMALL)<{ $isError: boolean }>`
+ color: ${({ $isError }) => ($isError ? 'red' : 'green')};
+ text-align: center;
+ font-weight: 400;
+ margin-top: -0.75rem;
+ font-size: 1rem;
+ display: block;
+`;
diff --git a/styles/styles.ts b/styles/styles.ts
index 9f7f10c..18ec141 100644
--- a/styles/styles.ts
+++ b/styles/styles.ts
@@ -3,7 +3,6 @@
import NextImage from 'next/image';
import styled from 'styled-components';
import { Sans } from '@/styles/fonts';
-import { SMALL } from '@/styles/text';
import COLORS from './colors';
export const Background = styled.main`
@@ -121,32 +120,3 @@ export const Link = styled.a`
text-decoration: underline;
}
`;
-
-export const EmailContainer = styled.div`
- display: flex;
- align-items: center;
- margin-top: -0.9rem;
- gap: 8px;
-`;
-
-export const EmailIconStyled = styled(NextImage)`
- width: 24px;
- height: 24px;
- margin-right: -4px;
-`;
-
-export const EmailText = styled.p`
- font-size: 1rem;
- color: ${COLORS.gray11};
- line-height: 1.5;
- display: inline-block;
-`;
-
-export const ResendMessage = styled(SMALL)<{ $isError: boolean }>`
- color: ${({ $isError }) => ($isError ? 'red' : 'green')};
- text-align: center;
- font-weight: 400;
- margin-top: -0.75rem;
- font-size: 1rem;
- display: block;
-`;
From 682949c545577b3c77b5f95011cc67f6ce44794c Mon Sep 17 00:00:00 2001
From: aidenm1 <65800707+aidenm1@users.noreply.github.com>
Date: Sat, 7 Dec 2024 14:11:36 -0800
Subject: [PATCH 5/9] feat: availability card component and display (#38)
* created querying functions
* created AvailabilityCard component and added to general page
* changed styling and updates colors file to include new pomegranate color
* resolved ESLint errors
* added message for no availabilities
* updated to reflect new design and implemented content moving when menubar is expanded
* changed design for date range on availability card
---
api/supabase/queries/availability.ts | 40 +++++
app/(auth)/auth-styles.ts | 2 +-
app/availability/general/page.tsx | 138 ++++++++++++++++++
app/availability/general/styles.ts | 45 ++++++
app/onboarding/finalize/styles.ts | 7 +-
app/onboarding/styles.ts | 4 +-
.../AvailabilityCard/AvailabilityCard.tsx | 86 +++++++++++
components/AvailabilityCard/styles.ts | 65 +++++++++
components/MenuBar/MenuBar.tsx | 11 +-
components/MenuBar/styles.ts | 5 +-
public/images/add.svg | 5 +
public/images/additionalinfo.svg | 5 +
public/images/clock.svg | 8 +
styles/colors.ts | 3 +-
types/schema.d.ts | 2 +
15 files changed, 416 insertions(+), 10 deletions(-)
create mode 100644 app/availability/general/page.tsx
create mode 100644 app/availability/general/styles.ts
create mode 100644 components/AvailabilityCard/AvailabilityCard.tsx
create mode 100644 components/AvailabilityCard/styles.ts
create mode 100644 public/images/add.svg
create mode 100644 public/images/additionalinfo.svg
create mode 100644 public/images/clock.svg
diff --git a/api/supabase/queries/availability.ts b/api/supabase/queries/availability.ts
index 4d57d5d..9ac5a78 100644
--- a/api/supabase/queries/availability.ts
+++ b/api/supabase/queries/availability.ts
@@ -23,3 +23,43 @@ export async function fetchAvailabilitiesByFacilityId(facility_id: UUID) {
return null; // Return null on unexpected error
}
}
+
+export async function fetchAllAvailabilities() {
+ try {
+ const { data, error } = await supabase.from('availabilities').select('*');
+ if (error) {
+ throw new Error(error.message);
+ }
+ if (data && data.length == 0) {
+ console.log('No availabilities found');
+ return [];
+ }
+ return data;
+ } catch (error) {
+ console.error('An unexpected error occured:', error);
+ return null;
+ }
+}
+
+export async function fetchDatesByAvailabilityID(availability_id: UUID) {
+ try {
+ const { data, error } = await supabase
+ .from('available_dates')
+ .select('*')
+ .eq('availability_id', availability_id);
+ if (error) {
+ throw new Error(error.message);
+ }
+ if (data && data.length == 0) {
+ console.log(
+ 'No dates found for this availability id or availability_id is undefined',
+ availability_id,
+ );
+ return [];
+ }
+ return data;
+ } catch (error) {
+ console.error('An unexpected error occured:', error);
+ return null;
+ }
+}
diff --git a/app/(auth)/auth-styles.ts b/app/(auth)/auth-styles.ts
index 617990e..b10782a 100644
--- a/app/(auth)/auth-styles.ts
+++ b/app/(auth)/auth-styles.ts
@@ -79,7 +79,7 @@ export const Input = styled.input`
export const Button = styled.button`
font-family: ${Sans.style.fontFamily};
- background-color: ${COLORS.pomegranate};
+ background-color: ${COLORS.pomegranate12};
color: white;
font-size: 1rem;
padding: 0.75rem;
diff --git a/app/availability/general/page.tsx b/app/availability/general/page.tsx
new file mode 100644
index 0000000..0c65c42
--- /dev/null
+++ b/app/availability/general/page.tsx
@@ -0,0 +1,138 @@
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import {
+ fetchAllAvailabilities,
+ fetchDatesByAvailabilityID,
+} from '@/api/supabase/queries/availability';
+import AvailabilityCard from '@/components/AvailabilityCard/AvailabilityCard';
+import MenuBar from '@/components/MenuBar/MenuBar';
+import Add from '@/public/images/add.svg';
+import COLORS from '@/styles/colors';
+import { H3 } from '@/styles/text';
+import { Availabilities, AvailableDates } from '@/types/schema';
+import * as styles from './styles';
+
+type AvailabilitiesByYear = {
+ [year: string]: {
+ availability: Availabilities;
+ available_dates: AvailableDates[];
+ }[];
+};
+
+export default function AvailabilityPage() {
+ const [groupedByYear, setGroupedByYear] = useState({});
+ const [isLoading, setIsLoading] = useState(true);
+ const [menuExpanded, setMenuExpanded] = useState(false); // Track the expanded state of the menu
+
+ useEffect(() => {
+ async function fetchAndGroupData() {
+ try {
+ // Step 1: Fetch all availabilities
+ const availabilities = await fetchAllAvailabilities();
+
+ if (!availabilities) {
+ return;
+ }
+
+ // Step 2: Group by year while fetching associated dates
+ const grouped: AvailabilitiesByYear = {};
+ for (const availability of availabilities) {
+ const availableDates = await fetchDatesByAvailabilityID(
+ availability.availability_id,
+ );
+
+ const year = availableDates?.[0]?.available_date
+ ? new Date(availableDates[0].available_date)
+ .getFullYear()
+ .toString()
+ : null;
+
+ //don't display availability cards that have no availabilities associated
+ if (year == null) {
+ continue;
+ }
+
+ if (!grouped[year]) {
+ grouped[year] = [];
+ }
+
+ grouped[year].push({
+ availability,
+ available_dates: availableDates ?? [],
+ });
+ }
+ for (const year in grouped) {
+ grouped[year].sort((a, b) => {
+ const firstDateA = new Date(
+ a.available_dates[0]?.available_date ?? 0,
+ ).getTime();
+ const firstDateB = new Date(
+ b.available_dates[0]?.available_date ?? 0,
+ ).getTime();
+ return firstDateA - firstDateB;
+ });
+ }
+
+ setGroupedByYear(grouped);
+ setIsLoading(false); // Stop loading after data fetch
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }
+
+ fetchAndGroupData();
+ }, []);
+
+ return (
+
+
{' '}
+ {/* Pass function to update state */}
+
+
+
+
+ Availabilities
+
+
+
+ {/* Check if there are no availabilities */}
+ {isLoading ? (
+
+ Loading availabilities...
+
+ ) : Object.keys(groupedByYear).length === 0 ? (
+
+ No availabilities yet,
+
+ add one with the plus sign!
+
+ ) : (
+ Object.entries(groupedByYear).map(([year, availabilities]) => (
+
+
+ {year}
+
+ {availabilities.map(({ availability, available_dates }) => (
+
+ ))}
+
+ ))
+ )}
+
+
+
+ );
+}
diff --git a/app/availability/general/styles.ts b/app/availability/general/styles.ts
new file mode 100644
index 0000000..e219b00
--- /dev/null
+++ b/app/availability/general/styles.ts
@@ -0,0 +1,45 @@
+'use client';
+
+import NextImage from 'next/image';
+import styled from 'styled-components';
+import { H6, P } from '@/styles/text';
+
+export const Page = styled.main<{ $menuExpanded: boolean }>`
+ display: flex;
+ min-width: 100%;
+ min-height: 100vh;
+ justify-content: center;
+ overflow: hidden;
+ margin-left: ${({ $menuExpanded }) =>
+ $menuExpanded ? '10%' : '0'}; /* Fixed margin for the expanded menu */
+ transition: margin-left 0.3s ease; /* Smooth transition */
+`;
+
+export const AllAvailabilitiesHolder = styled.main`
+ display: flex;
+ width: 28.75%;
+ flex-direction: column;
+`;
+
+export const TitleContainer = styled.main`
+ display: flex;
+ margin-top: 4.43rem;
+ margin-bottom: 2.62rem;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+`;
+
+export const YearText = styled(H6)`
+ margin-bottom: 1.5rem;
+`;
+
+export const AddImage = styled(NextImage)`
+ width: 1.5rem;
+ height: 1.5rem;
+`;
+
+export const message = styled(P)`
+ text-align: center;
+ margin-top: 5.75rem;
+`;
diff --git a/app/onboarding/finalize/styles.ts b/app/onboarding/finalize/styles.ts
index 759e7ad..34635a6 100644
--- a/app/onboarding/finalize/styles.ts
+++ b/app/onboarding/finalize/styles.ts
@@ -64,8 +64,13 @@ export const ContinueButton = styled.button`
justify-content: center;
align-items: center;
align-self: stretch;
+<<<<<<< HEAD:app/onboarding/yay/styles.ts
+ border-radius: 99999px;
+ background: ${COLORS.pomegranate12};
+=======
border-radius: 8px;
- background: ${COLORS.pomegranate};
+ background: ${COLORS.pomegranate12};
+>>>>>>> c33d5a0fb4596e1d2e004014b99e33e17cee93e9:app/onboarding/finalize/styles.ts
border-style: solid;
border-color: ${COLORS.gray12};
cursor: pointer;
diff --git a/app/onboarding/styles.ts b/app/onboarding/styles.ts
index 2eefc8b..659830e 100644
--- a/app/onboarding/styles.ts
+++ b/app/onboarding/styles.ts
@@ -150,9 +150,9 @@ export const Button = styled.button<{ disabled?: boolean }>`
width: 30%;
height: 2.75rem;
background-color: ${({ disabled }) =>
- disabled ? COLORS.pomegranate10 : COLORS.pomegranate};
+ disabled ? COLORS.pomegranate10 : COLORS.pomegranate12};
border-color: ${({ disabled }) =>
- disabled ? COLORS.pomegranate10 : COLORS.pomegranate};
+ disabled ? COLORS.pomegranate10 : COLORS.pomegranate12};
border-style: solid;
border-radius: 8px;
display: inline-flex;
diff --git a/components/AvailabilityCard/AvailabilityCard.tsx b/components/AvailabilityCard/AvailabilityCard.tsx
new file mode 100644
index 0000000..c13da6c
--- /dev/null
+++ b/components/AvailabilityCard/AvailabilityCard.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import Arrow from '@/public/images/white_back.svg';
+import COLORS from '@/styles/colors';
+import { H5, P } from '@/styles/text';
+import { Availabilities, AvailableDates } from '@/types/schema';
+import * as styles from './styles';
+
+interface AvailabilityCardProps {
+ availability: Availabilities;
+ availableDates: AvailableDates[];
+}
+
+export default function AvailabilityCard({
+ availability,
+ availableDates,
+}: AvailabilityCardProps) {
+ const dateRange =
+ availableDates.length > 0
+ ? {
+ earliest: new Date(
+ Math.min(
+ ...availableDates.map(date =>
+ new Date(date.available_date).getTime(),
+ ),
+ ),
+ ),
+ latest: new Date(
+ Math.max(
+ ...availableDates.map(date =>
+ new Date(date.available_date).getTime(),
+ ),
+ ),
+ ),
+ }
+ : null;
+
+ // Format the dates
+ let formattedRange = dateRange
+ ? `${dateRange.earliest.toLocaleString('default', {
+ month: 'short',
+ day: 'numeric',
+ })} - ${dateRange.latest.toLocaleString('default', {
+ month: 'short',
+ day: 'numeric',
+ })}`
+ : 'No dates available';
+
+ if (dateRange && dateRange.earliest.getDate() == dateRange.latest.getDate()) {
+ formattedRange = `${dateRange.earliest.toLocaleString('default', {
+ month: 'short',
+ day: 'numeric',
+ })}`;
+ }
+
+ return (
+
+
+
+
+ {availability.name}
+
+
+ {formattedRange}
+
+
+
+
+
+
+
+
+ Description
+
+
+ {availability.additional_info}
+
+
+
+
+
+ );
+}
diff --git a/components/AvailabilityCard/styles.ts b/components/AvailabilityCard/styles.ts
new file mode 100644
index 0000000..be08d1f
--- /dev/null
+++ b/components/AvailabilityCard/styles.ts
@@ -0,0 +1,65 @@
+'use client';
+
+import NextImage from 'next/image';
+import styled from 'styled-components';
+import COLORS from '@/styles/colors';
+import { SMALL } from '@/styles/text';
+
+export const AvailabilityContainer = styled.main`
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ gap: 1.5rem;
+ border-radius: 16px;
+ width: 100%;
+ background: ${COLORS.bread1};
+ margin-bottom: 3rem;
+ box-shadow: 0px 6px 15px -2px rgba(0, 0, 0, 0.08);
+`;
+
+export const AvailabilityHeader = styled.main`
+ display: flex;
+ padding: 16px 24px;
+ justify-content: space-between;
+ align-items: center;
+ background: ${COLORS.pomegranate11};
+ border-radius: 16px 16px 0 0;
+ width: 100%;
+`;
+
+export const AvailabilityTitle = styled.main`
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+`;
+
+export const Content = styled.main`
+ padding: 0px 24px 24px 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+`;
+
+export const SubHeader = styled.main`
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ justify-content: start;
+ margin-bottom: 0.25rem;
+`;
+
+export const Arrow = styled(NextImage)`
+ width: 24px;
+ height: 24px;
+ transform: rotate(180deg);
+`;
+
+export const TruncatedText = styled(SMALL)`
+ display: -webkit-box;
+ -webkit-line-clamp: 2; /* Limit to 2 lines */
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: normal;
+`;
diff --git a/components/MenuBar/MenuBar.tsx b/components/MenuBar/MenuBar.tsx
index 44813d0..aad6b6e 100644
--- a/components/MenuBar/MenuBar.tsx
+++ b/components/MenuBar/MenuBar.tsx
@@ -12,14 +12,19 @@ import {
ToggleButton,
} from './styles';
-const MenuBar: React.FC = () => {
+const MenuBar: React.FC<{ setMenuExpanded?: (expanded: boolean) => void }> = ({
+ setMenuExpanded = () => {}, // Default to a no-op function
+}) => {
const [expanded, setExpanded] = useState(false);
const [activeItem, setActiveItem] = useState(null);
const router = useRouter();
- const toggleMenu = () => setExpanded(!expanded);
+ const toggleMenu = () => {
+ const newExpanded = !expanded;
+ setExpanded(newExpanded);
+ setMenuExpanded(newExpanded); // Update parent component about expanded state
+ };
- // TODO: add navigation by passing in path prop
const handleClick = (item: string, path: string) => {
setActiveItem(item);
router.push(path);
diff --git a/components/MenuBar/styles.ts b/components/MenuBar/styles.ts
index cf8e665..173e705 100644
--- a/components/MenuBar/styles.ts
+++ b/components/MenuBar/styles.ts
@@ -8,7 +8,7 @@ export const MenuContainer = styled.div<{ $expanded: boolean }>`
height: 100vh;
z-index: 9999;
background-color: ${({ $expanded }) =>
- $expanded ? COLORS.pomegranate : 'transparent'};
+ $expanded ? COLORS.pomegranate12 : 'transparent'};
display: flex;
flex-direction: column;
padding-left: 1.25rem;
@@ -47,7 +47,8 @@ export const MenuIconWrapper = styled.div<{ $expanded: boolean }>`
width: 20px;
height: 20px;
& svg path {
- fill: ${({ $expanded }) => ($expanded ? COLORS.gray3 : COLORS.pomegranate)};
+ fill: ${({ $expanded }) =>
+ $expanded ? COLORS.gray3 : COLORS.pomegranate12};
}
`;
diff --git a/public/images/add.svg b/public/images/add.svg
new file mode 100644
index 0000000..8aa80d3
--- /dev/null
+++ b/public/images/add.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/additionalinfo.svg b/public/images/additionalinfo.svg
new file mode 100644
index 0000000..0811a52
--- /dev/null
+++ b/public/images/additionalinfo.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/clock.svg b/public/images/clock.svg
new file mode 100644
index 0000000..e911704
--- /dev/null
+++ b/public/images/clock.svg
@@ -0,0 +1,8 @@
+
diff --git a/styles/colors.ts b/styles/colors.ts
index 1f35755..0f9b245 100644
--- a/styles/colors.ts
+++ b/styles/colors.ts
@@ -51,8 +51,9 @@ const COLORS = {
lilac11: '#423EAF',
lilac12: '#383975',
- pomegranate: '#342A2F',
pomegranate10: '#6F585E',
+ pomegranate11: '#633A4F',
+ pomegranate12: '#342A2F',
};
export default COLORS;
diff --git a/types/schema.d.ts b/types/schema.d.ts
index 6e6c04e..533adb6 100644
--- a/types/schema.d.ts
+++ b/types/schema.d.ts
@@ -76,6 +76,8 @@ export interface AvailableDates {
date_id: UUID;
availability_id: UUID;
available_date: string; //date
+ test_col: string; //timestamptz
+ test_col2: string; //timestamptz
}
export interface Event {
From 8a8e48ee08bba5b7c57d6b8ed4ce1079363c9f40 Mon Sep 17 00:00:00 2001
From: Celine Ji-Won Choi <107969914+celinechoiii@users.noreply.github.com>
Date: Sat, 7 Dec 2024 14:13:31 -0800
Subject: [PATCH 6/9] fix: update continue button style
---
app/onboarding/finalize/styles.ts | 5 -----
1 file changed, 5 deletions(-)
diff --git a/app/onboarding/finalize/styles.ts b/app/onboarding/finalize/styles.ts
index 34635a6..f0678ad 100644
--- a/app/onboarding/finalize/styles.ts
+++ b/app/onboarding/finalize/styles.ts
@@ -64,13 +64,8 @@ export const ContinueButton = styled.button`
justify-content: center;
align-items: center;
align-self: stretch;
-<<<<<<< HEAD:app/onboarding/yay/styles.ts
- border-radius: 99999px;
- background: ${COLORS.pomegranate12};
-=======
border-radius: 8px;
background: ${COLORS.pomegranate12};
->>>>>>> c33d5a0fb4596e1d2e004014b99e33e17cee93e9:app/onboarding/finalize/styles.ts
border-style: solid;
border-color: ${COLORS.gray12};
cursor: pointer;
From 68cd5eee3723b8ac6979c51df62e91b1a8d9447c Mon Sep 17 00:00:00 2001
From: Rohin Juneja
Date: Sat, 7 Dec 2024 14:32:58 -0800
Subject: [PATCH 7/9] [fix] addressed comments: removed unused imports,
reformatted import path directories, and removed local styles from global
styles file
---
styles/styles.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/styles/styles.ts b/styles/styles.ts
index 18ec141..82399b3 100644
--- a/styles/styles.ts
+++ b/styles/styles.ts
@@ -76,7 +76,7 @@ export const ContinueButton = styled.button`
align-items: center;
align-self: stretch;
border-radius: 99999px;
- background: ${COLORS.pomegranate};
+ background: ${COLORS.pomegranate12};
border-style: solid;
border-color: ${COLORS.gray12};
cursor: pointer;
@@ -90,7 +90,7 @@ interface RoundedCornerButtonProps {
export const RoundedCornerButton = styled.button`
font-family: ${Sans.style.fontFamily};
- background-color: ${props => props.bgColor || COLORS.pomegranate};
+ background-color: ${props => props.bgColor || COLORS.pomegranate12};
color: ${props => props.textColor || 'white'};
font-size: 1rem;
padding: 0.55rem;
From 9f5e3a7bcbf6c57feb046495029c10a607c96fee Mon Sep 17 00:00:00 2001
From: Celine Choi
Date: Mon, 30 Dec 2024 02:47:54 -0800
Subject: [PATCH 8/9] fix: redirection fixes and separate inserting user flow
---
api/supabase/queries/auth.ts | 63 ++++++++++++++++++--------------
app/(auth)/verification/page.tsx | 55 +++++++++++++++++++++++-----
2 files changed, 81 insertions(+), 37 deletions(-)
diff --git a/api/supabase/queries/auth.ts b/api/supabase/queries/auth.ts
index c4683f3..ba351f6 100644
--- a/api/supabase/queries/auth.ts
+++ b/api/supabase/queries/auth.ts
@@ -6,41 +6,18 @@ export async function handleSignUp(
): Promise<{ success: boolean; message: string }> {
try {
await ensureLoggedOutForNewUser(email);
- const { data, error } = await supabase.auth.signUp({
+ const { error } = await supabase.auth.signUp({
email,
password,
+ options: {
+ emailRedirectTo: 'http://localhost:3000/verification',
+ },
});
if (error) {
return { success: false, message: `Sign-up failed: ${error.message}` };
}
- const user = data.user;
- if (!user) {
- return {
- success: false,
- message: 'Sign-up failed: User was not created.',
- };
- }
-
- const { error: insertError } = await supabase.from('volunteers').insert([
- {
- user_id: user.id,
- email,
- first_name: '',
- last_name: '',
- phone_number: '',
- notifications_opt_in: true, // default value
- },
- ]);
-
- if (insertError) {
- return {
- success: false,
- message: `Error storing user data: ${insertError.message}`,
- };
- }
-
localStorage.setItem('tempEmail', email);
return { success: true, message: 'Sign-up successful!' };
@@ -55,6 +32,38 @@ export async function handleSignUp(
}
}
+export const insertVolunteer = async (user: { id: string; email: string }) => {
+ if (!user) {
+ return {
+ success: false,
+ message: 'User data is missing. Cannot insert into volunteers table.',
+ };
+ }
+
+ const { error: insertError } = await supabase.from('volunteers').insert([
+ {
+ user_id: user.id,
+ email: user.email,
+ first_name: '',
+ last_name: '',
+ phone_number: '',
+ notifications_opt_in: true,
+ },
+ ]);
+
+ if (insertError) {
+ return {
+ success: false,
+ message: `Error storing user data: ${insertError.message}`,
+ };
+ }
+
+ return {
+ success: true,
+ message: 'User successfully added to volunteers table.',
+ };
+};
+
export async function handleSignIn(
email: string,
password: string,
diff --git a/app/(auth)/verification/page.tsx b/app/(auth)/verification/page.tsx
index 4793ea5..f59d35e 100644
--- a/app/(auth)/verification/page.tsx
+++ b/app/(auth)/verification/page.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import {
getTempEmail,
+ insertVolunteer,
resendVerificationEmail,
} from '@/api/supabase/queries/auth';
import Bud from '@/public/images/bud.svg';
@@ -28,23 +29,59 @@ import {
} from './verification-styles';
export default function Verification() {
- const router = useRouter(); // Initialize useRouter
+ const router = useRouter();
+ const { session } = useSession();
const [tempEmail, setTempEmail] = useState(null);
const [resendStatus, setResendStatus] = useState('');
const [isError, setIsError] = useState(false);
- const { session } = useSession();
-
- useEffect(() => {
- if (session) {
- router.push('/success');
- }
- }, [session, router]);
+ const [isEmailConfirmed, setIsEmailConfirmed] = useState(false);
useEffect(() => {
const email = getTempEmail();
setTempEmail(email);
}, []);
+ // Using session to check if the email is confirmed
+ useEffect(() => {
+ if (session?.user) {
+ const isVerified = session.user.email_confirmed_at !== null;
+ setIsEmailConfirmed(isVerified);
+ }
+ }, [session]);
+
+ useEffect(() => {
+ if (isEmailConfirmed && session?.user) {
+ const addUserToVolunteers = async () => {
+ const email = session.user.email;
+
+ if (!email) {
+ setIsError(true);
+ setResendStatus('Email is undefined. Please try again.');
+ return;
+ }
+
+ try {
+ const result = await insertVolunteer({
+ id: session.user.id,
+ email,
+ });
+ if (result.success) {
+ router.push('/success');
+ } else {
+ setIsError(true);
+ setResendStatus(result.message);
+ }
+ } catch (error) {
+ console.error('Error adding user to volunteers:', error);
+ setIsError(true);
+ setResendStatus('An error occurred while processing your request.');
+ }
+ };
+
+ addUserToVolunteers();
+ }
+ }, [isEmailConfirmed, session, router]);
+
const handleResendLink = async () => {
if (tempEmail) {
const message = await resendVerificationEmail(tempEmail);
@@ -60,8 +97,6 @@ export default function Verification() {
localStorage.removeItem('tempEmail');
};
- // TODO: Restyle error message on lines 95-100 (the message containing link back to sign-up)
-
return (
From 69852fdbc511f5e52b81d15284fb3732a6f88cb3 Mon Sep 17 00:00:00 2001
From: Celine Choi
Date: Mon, 30 Dec 2024 02:51:39 -0800
Subject: [PATCH 9/9] fix: linting issues
---
app/availability/general/styles.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/availability/general/styles.ts b/app/availability/general/styles.ts
index c348dff..687bcdd 100644
--- a/app/availability/general/styles.ts
+++ b/app/availability/general/styles.ts
@@ -22,7 +22,7 @@ export const AllAvailabilitiesHolder = styled.main`
display: flex;
width: 28.75%;
flex-direction: column;
-
+
@media (max-width: 900px) {
width: 80%;
}