From 2b18c15566e624dc25af8f364e318681ee937f4d Mon Sep 17 00:00:00 2001 From: kevin3656 Date: Sun, 6 Oct 2024 17:08:19 -0700 Subject: [PATCH 1/5] plot status and state pages --- app/onboarding/page.tsx | 0 app/onboarding/plotstatus/page.tsx | 31 ++++++++++++++++++++++++++++ app/onboarding/state/page.tsx | 32 +++++++++++++++++++++++++++++ app/onboarding/styles.ts | 33 ++++++++++++++++++++++++++++++ hooks/useProfile.ts | 31 ++++++++++++++++++++++++++++ styles/colors.ts | 9 ++++++++ types/schema.d.ts | 7 +++++++ 7 files changed, 143 insertions(+) create mode 100644 app/onboarding/page.tsx create mode 100644 app/onboarding/plotstatus/page.tsx create mode 100644 app/onboarding/state/page.tsx create mode 100644 app/onboarding/styles.ts create mode 100644 hooks/useProfile.ts create mode 100644 styles/colors.ts diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/onboarding/plotstatus/page.tsx b/app/onboarding/plotstatus/page.tsx new file mode 100644 index 0000000..5cd9a20 --- /dev/null +++ b/app/onboarding/plotstatus/page.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useProfile } from '@/hooks/useProfile'; +import { + Button, + ButtonContainer, + ContentContainer, + PageContainer, +} from '../styles'; + +export default function Page() { + const { addProfile } = useProfile(); + + const handleButtonClick = (state: string) => { + const newProfile = { state, email: '', phone_num: '', user_type: '' }; + addProfile(newProfile); + console.log('test'); + }; + return ( + + +

Plot Status

+

Do you own a plot to plant in?

+ + + + +
+
+ ); +} diff --git a/app/onboarding/state/page.tsx b/app/onboarding/state/page.tsx new file mode 100644 index 0000000..ae2bb07 --- /dev/null +++ b/app/onboarding/state/page.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { useProfile } from '@/hooks/useProfile'; +import { + Button, + ButtonContainer, + ContentContainer, + PageContainer, +} from '../styles'; + +export default function Page() { + const { addProfile } = useProfile(); + + const handleButtonClick = (state: string) => { + const newProfile = { state, email: '', phone_num: '', user_type: '' }; + addProfile(newProfile); + console.log('test'); + }; + return ( + + +

Choose Your state

+ + + + +
+
+ ); +} diff --git a/app/onboarding/styles.ts b/app/onboarding/styles.ts new file mode 100644 index 0000000..1d985e7 --- /dev/null +++ b/app/onboarding/styles.ts @@ -0,0 +1,33 @@ +import styled from 'styled-components'; +import COLORS from '@/styles/colors'; + +export const PageContainer = styled.div` + width: 100%; + height: 100svh; + background-color: ${COLORS.seed}; +`; +export const ContentContainer = styled.div` + display: flex; /* Enable flexbox */ + flex-direction: column; + align-items: center; + justify-content: center; + + height: 100vh; +`; + +export const ButtonContainer = styled.div` + display: flex; /* Enable flexbox */ + flex-direction: row; + align-items: center; + justify-content: center; + gap: 10px; +`; + +export const Button = styled.button` + width: 9.375rem; + height: 3.125rem; + border-radius: 25rem; + border-width: 0px; + background-color: ${COLORS.sprout}; + color: white; +`; diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts new file mode 100644 index 0000000..3543922 --- /dev/null +++ b/hooks/useProfile.ts @@ -0,0 +1,31 @@ +import { useState } from 'react'; +import { Profile } from '../types/schema'; + +const initialProfiles: Profile[] = []; + +export const useProfile = () => { + const [profiles, setProfiles] = useState(initialProfiles); + + const addProfile = (newProfile: Profile) => { + setProfiles(prev => [...prev, newProfile]); + }; + + const updateProfile = (index: number, updates: Partial) => { + setProfiles(prev => + prev.map((profile, i) => + i === index ? { ...profile, ...updates } : profile, + ), + ); + }; + + const removeProfile = (index: number) => { + setProfiles(prev => prev.filter((_, i) => i !== index)); + }; + + return { + profiles, + addProfile, + updateProfile, + removeProfile, + }; +}; diff --git a/styles/colors.ts b/styles/colors.ts new file mode 100644 index 0000000..86875a4 --- /dev/null +++ b/styles/colors.ts @@ -0,0 +1,9 @@ +const COLORS = { + // background white + seed: '#FFFBF2', + + //greens + shrub: '#1F5A2A', + sprout: '#94B506', +}; +export default COLORS; diff --git a/types/schema.d.ts b/types/schema.d.ts index 94641ad..89c9435 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -4,6 +4,13 @@ export type SeasonEnum = 'SPRING' | 'SUMMER' | 'FALL' | 'WINTER'; export type DifficultyLevelEnum = 'EASY' | 'MODERATE' | 'HARD'; +export interface Profile { + state: string; + email: string; + phone_num: string; + user_type: string; +} + export interface Plant { id: UUID; plant_name: string; From 58de0ad8585427dcd225a2d6ec729ec1ef69ff67 Mon Sep 17 00:00:00 2001 From: kevin3656 Date: Sun, 13 Oct 2024 15:37:33 -0700 Subject: [PATCH 2/5] added page on onboarding, profile.ts for supabase queries --- api/supabase/queries/profiles.ts | 13 +++ app/onboarding/page.tsx | 179 +++++++++++++++++++++++++++++++ types/schema.d.ts | 2 + 3 files changed, 194 insertions(+) create mode 100644 api/supabase/queries/profiles.ts diff --git a/api/supabase/queries/profiles.ts b/api/supabase/queries/profiles.ts new file mode 100644 index 0000000..628bf66 --- /dev/null +++ b/api/supabase/queries/profiles.ts @@ -0,0 +1,13 @@ +import { Profile } from '@/types/schema'; +import supabase from '../createClient'; + +export async function upsertProfile(profile: Profile) { + const { data, error } = await supabase + .from('profiles') + .upsert(profile) + .select(); + + if (error) throw new Error(`Error upserting profile data: ${error.message}`); + + return data[0]; +} diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index e69de29..2a93dd0 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -0,0 +1,179 @@ +'use client'; + +import React, { useState } from 'react'; +import { upsertProfile } from '@/api/supabase/queries/profiles'; +import { Profile } from '@/types/schema'; + +type UUID = `${string}-${string}-${string}-${string}-${string}`; +const generateUUID = (): UUID => { + return crypto.randomUUID() as UUID; +}; +const id = generateUUID(); + +// Define the possible options for each question +const states = ['Tennessee', 'Missouri']; +const gardenTypes = ['Individual', 'Community', 'School']; +const plotOptions = [ + { label: 'yes', value: true }, + { label: 'no', value: false }, +]; +//Interfaces and props to avoid typ errors on Lint +interface StateSelectionProps { + selectedState: string; + setSelectedState: React.Dispatch>; +} + +interface GardenTypeSelectionProps { + selectedGardenType: string; + setSelectedGardenType: React.Dispatch>; +} + +interface PlotSelectionProps { + selectedPlot: boolean | null; + setSelectedPlot: React.Dispatch>; +} + +// Select State +const StateSelection: React.FC = ({ + selectedState, + setSelectedState, +}) => { + return ( +
+

Which state do you live in?

+ +
+ ); +}; + +// Step 2: Select garden type + +const GardenTypeSelection: React.FC = ({ + selectedGardenType, + setSelectedGardenType, +}) => { + return ( +
+

What type of garden do you want to create?

+ {gardenTypes.map(type => ( + + ))} +
+ ); +}; + +// Step 3: Do you have a plot? +const PlotSelection: React.FC = ({ + selectedPlot, + setSelectedPlot, +}) => { + return ( +
+

Do you already have a plot?

+ {plotOptions.map(option => ( + + ))} +
+ ); +}; + +// Main Onboarding Component +const OnboardingFlow = () => { + const [step, setStep] = useState(1); + const [selectedState, setSelectedState] = useState(''); + const [selectedGardenType, setSelectedGardenType] = useState(''); + const [selectedPlot, setSelectedPlot] = useState(false); + + const handleNext = () => { + setStep(step + 1); + }; + + const handleBack = () => { + setStep(step - 1); + }; + + const handleSubmit = async () => { + const profile: Profile = { + user_id: id, + state: selectedState, + email: '', + phone_num: '', + user_type: selectedGardenType, + has_plot: selectedPlot, + }; + try { + const updatedProfile = await upsertProfile(profile); + console.log('Profile successfully upserted:', updatedProfile); + } catch (err) { + console.error('Error upserting profile:', err); + } finally { + } + console.log('Submitted data: ', profile); + // Handle form submission, e.g., send to a server or display a confirmation + }; + + return ( +
+ {step === 1 && ( + + )} + {step === 2 && ( + + )} + {step === 3 && ( + + )} + +
+ {step > 1 && } + {step < 3 && ( + + )} + {step === 3 && } +
+
+ ); +}; + +export default OnboardingFlow; diff --git a/types/schema.d.ts b/types/schema.d.ts index 89c9435..49122a1 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -5,10 +5,12 @@ export type SeasonEnum = 'SPRING' | 'SUMMER' | 'FALL' | 'WINTER'; export type DifficultyLevelEnum = 'EASY' | 'MODERATE' | 'HARD'; export interface Profile { + user_id: UUID; state: string; email: string; phone_num: string; user_type: string; + has_plot: boolean; } export interface Plant { From a70395c62986bf36f703b703545b3152d5357dbe Mon Sep 17 00:00:00 2001 From: kevin3656 Date: Sun, 13 Oct 2024 15:47:03 -0700 Subject: [PATCH 3/5] removed profile functions from plot status and state page for lint --- app/onboarding/plotstatus/page.tsx | 2 -- app/onboarding/state/page.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/onboarding/plotstatus/page.tsx b/app/onboarding/plotstatus/page.tsx index 5cd9a20..bf01d8e 100644 --- a/app/onboarding/plotstatus/page.tsx +++ b/app/onboarding/plotstatus/page.tsx @@ -12,8 +12,6 @@ export default function Page() { const { addProfile } = useProfile(); const handleButtonClick = (state: string) => { - const newProfile = { state, email: '', phone_num: '', user_type: '' }; - addProfile(newProfile); console.log('test'); }; return ( diff --git a/app/onboarding/state/page.tsx b/app/onboarding/state/page.tsx index ae2bb07..2ad74f0 100644 --- a/app/onboarding/state/page.tsx +++ b/app/onboarding/state/page.tsx @@ -12,8 +12,6 @@ export default function Page() { const { addProfile } = useProfile(); const handleButtonClick = (state: string) => { - const newProfile = { state, email: '', phone_num: '', user_type: '' }; - addProfile(newProfile); console.log('test'); }; return ( From c1a6aa0761640cde28a7cf13dd9665d47eb5529e Mon Sep 17 00:00:00 2001 From: kevin3656 Date: Sun, 13 Oct 2024 15:53:28 -0700 Subject: [PATCH 4/5] removed folders for individual pages, primarily for lint --- app/onboarding/plotstatus/page.tsx | 29 ----------------------------- app/onboarding/state/page.tsx | 30 ------------------------------ 2 files changed, 59 deletions(-) delete mode 100644 app/onboarding/plotstatus/page.tsx delete mode 100644 app/onboarding/state/page.tsx diff --git a/app/onboarding/plotstatus/page.tsx b/app/onboarding/plotstatus/page.tsx deleted file mode 100644 index bf01d8e..0000000 --- a/app/onboarding/plotstatus/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { useProfile } from '@/hooks/useProfile'; -import { - Button, - ButtonContainer, - ContentContainer, - PageContainer, -} from '../styles'; - -export default function Page() { - const { addProfile } = useProfile(); - - const handleButtonClick = (state: string) => { - console.log('test'); - }; - return ( - - -

Plot Status

-

Do you own a plot to plant in?

- - - - -
-
- ); -} diff --git a/app/onboarding/state/page.tsx b/app/onboarding/state/page.tsx deleted file mode 100644 index 2ad74f0..0000000 --- a/app/onboarding/state/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import { useProfile } from '@/hooks/useProfile'; -import { - Button, - ButtonContainer, - ContentContainer, - PageContainer, -} from '../styles'; - -export default function Page() { - const { addProfile } = useProfile(); - - const handleButtonClick = (state: string) => { - console.log('test'); - }; - return ( - - -

Choose Your state

- - - - -
-
- ); -} From 96d938f0c68f5eeb06658b7d16dfc9baac96c5ba Mon Sep 17 00:00:00 2001 From: kevin3656 Date: Sat, 26 Oct 2024 14:42:30 -0700 Subject: [PATCH 5/5] [feat] ProfileProvider implementation, lint + prettier fixes --- app/layout.tsx | 5 +- app/onboarding/page.tsx | 95 +++++++++++++++++-------------------- app/page.tsx | 10 ++-- types/schema.d.ts | 2 - utils/ProfileProvider.tsx | 99 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 59 deletions(-) create mode 100644 utils/ProfileProvider.tsx diff --git a/app/layout.tsx b/app/layout.tsx index c0101bc..5e9ea9c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import StyledComponentsRegistry from '@/lib/registry'; +import { ProfileProvider } from '@/utils/ProfileProvider'; // font definitions const sans = Inter({ @@ -22,7 +23,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx index 2a93dd0..4c418dc 100644 --- a/app/onboarding/page.tsx +++ b/app/onboarding/page.tsx @@ -1,14 +1,7 @@ 'use client'; import React, { useState } from 'react'; -import { upsertProfile } from '@/api/supabase/queries/profiles'; -import { Profile } from '@/types/schema'; - -type UUID = `${string}-${string}-${string}-${string}-${string}`; -const generateUUID = (): UUID => { - return crypto.randomUUID() as UUID; -}; -const id = generateUUID(); +import { ProfileProvider, useProfile } from '@/utils/ProfileProvider'; // Define the possible options for each question const states = ['Tennessee', 'Missouri']; @@ -121,59 +114,57 @@ const OnboardingFlow = () => { const handleBack = () => { setStep(step - 1); }; - + const { setProfile } = useProfile(); const handleSubmit = async () => { - const profile: Profile = { - user_id: id, - state: selectedState, - email: '', - phone_num: '', - user_type: selectedGardenType, - has_plot: selectedPlot, - }; try { - const updatedProfile = await upsertProfile(profile); - console.log('Profile successfully upserted:', updatedProfile); - } catch (err) { - console.error('Error upserting profile:', err); - } finally { + await setProfile({ + state: selectedState, + user_type: selectedGardenType, + has_plot: selectedPlot, + }); + console.log('Profile updated successfully'); + } catch (error) { + console.error('Error submitting profile:', error); } - console.log('Submitted data: ', profile); - // Handle form submission, e.g., send to a server or display a confirmation }; + // Handle form submission, e.g., send to a server or display a confirmation return ( -
- {step === 1 && ( - - )} - {step === 2 && ( - - )} - {step === 3 && ( - - )} - +
- {step > 1 && } - {step < 3 && ( - + {step === 1 && ( + )} - {step === 3 && } + {step === 2 && ( + + )} + {step === 3 && ( + + )} + +
+ {step > 1 && } + {step < 3 && ( + + )} + {step === 3 && } +
-
+ ); }; - export default OnboardingFlow; diff --git a/app/page.tsx b/app/page.tsx index d88a5b5..2795f62 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,11 +1,13 @@ -import BPLogo from '@/assets/images/bp-logo.png'; -import { Container, Image } from './page.style'; +import { ProfileProvider } from '@/utils/ProfileProvider'; +import OnboardingFlow from './onboarding/page'; +import { Container } from './page.style'; export default function Home() { return ( - Blueprint Logo -

Open up app/page.tsx to get started!

+ + +
); } diff --git a/types/schema.d.ts b/types/schema.d.ts index 49122a1..709cb9b 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -7,8 +7,6 @@ export type DifficultyLevelEnum = 'EASY' | 'MODERATE' | 'HARD'; export interface Profile { user_id: UUID; state: string; - email: string; - phone_num: string; user_type: string; has_plot: boolean; } diff --git a/utils/ProfileProvider.tsx b/utils/ProfileProvider.tsx new file mode 100644 index 0000000..65dc1cf --- /dev/null +++ b/utils/ProfileProvider.tsx @@ -0,0 +1,99 @@ +'use client'; + +import React, { + createContext, + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; +import { upsertProfile } from '@/api/supabase/queries/profiles'; +import { Profile } from '@/types/schema'; + +// Define a placeholder user ID for development purposes +const placeholderUserId = '2abd7296-374a-42d1-bb4f-b813da1615ae'; + +interface ProfileContextType { + profileData: Profile | null; + profileReady: boolean; + setProfile: (newProfileData: Partial) => Promise; + loadProfile: () => Promise; +} + +const ProfileContext = createContext(undefined); + +export const useProfile = () => { + const context = useContext(ProfileContext); + if (!context) { + throw new Error('useProfile must be used within a ProfileProvider'); + } + return context; +}; + +export const ProfileProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { + const [profileData, setProfileData] = useState(null); + const [profileReady, setProfileReady] = useState(false); + + const loadProfile = useCallback(async () => { + setProfileReady(false); + + try { + const profile: Profile = { + user_id: placeholderUserId, + state: '', + user_type: '', + has_plot: false, + }; + // Fetch or upsert the profile for the placeholder user ID + const fetchedProfile = await upsertProfile(profile); + setProfileData(fetchedProfile); + } catch (error) { + console.error('Error loading profile:', error); + } finally { + setProfileReady(true); + } + }, []); + + const setProfile = useCallback( + async (newProfileData: Partial) => { + const profileToUpdate: Profile = { + ...profileData!, + ...newProfileData, + user_id: placeholderUserId, // Using placeholder user ID for now + }; + + try { + const updatedProfile = await upsertProfile(profileToUpdate); + setProfileData(updatedProfile); + } catch (error) { + console.error('Error updating profile:', error); + throw new Error('Error updating profile'); + } + }, + [profileData], + ); + + useEffect(() => { + loadProfile(); + }, [loadProfile]); + + const providerValue = useMemo( + () => ({ + profileData, + profileReady, + setProfile, + loadProfile, + }), + [profileData, profileReady, setProfile, loadProfile], + ); + + return ( + + {children} + + ); +};