Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] create the profile context #24

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
41201f3
plot status and state pages
kevin3656 Oct 7, 2024
08a3545
removed profile functions from plot status and state page for lint
kevin3656 Oct 13, 2024
a799072
removed folders for individual pages, primarily for lint
kevin3656 Oct 13, 2024
b76c698
[feat] ProfileProvider implementation, lint + prettier fixes
kevin3656 Oct 26, 2024
1921cf8
plot status and state pages
kevin3656 Oct 7, 2024
258d8c0
removed profile functions from plot status and state page for lint
kevin3656 Oct 13, 2024
2f9b8ab
removed folders for individual pages, primarily for lint
kevin3656 Oct 13, 2024
72ddac6
[refactor], updated profile UserTypeEnum, separate logic to handle ha…
kevin3656 Nov 3, 2024
8704604
addressed lint issues
kevin3656 Nov 4, 2024
1f9cc70
plot status and state pages
kevin3656 Oct 7, 2024
96e315b
removed profile functions from plot status and state page for lint
kevin3656 Oct 13, 2024
e0cc58d
removed folders for individual pages, primarily for lint
kevin3656 Oct 13, 2024
41bb89b
[feat] ProfileProvider implementation, lint + prettier fixes
kevin3656 Oct 26, 2024
57fb90b
plot status and state pages
kevin3656 Oct 7, 2024
5caadc4
removed profile functions from plot status and state page for lint
kevin3656 Oct 13, 2024
d461ca4
removed folders for individual pages, primarily for lint
kevin3656 Oct 13, 2024
ceed721
[refactor], updated profile UserTypeEnum, separate logic to handle ha…
kevin3656 Nov 3, 2024
20fa743
addressed lint issues
kevin3656 Nov 4, 2024
a2df6d7
disable next, refactor props and rename to us_state on onboarding
ccatherinetan Nov 12, 2024
1e8f9f7
disable next, refactor props and rename to us_state on onboarding
ccatherinetan Nov 12, 2024
189f444
add fetchProfile query; rename to setHasPlot
ccatherinetan Nov 12, 2024
850dc4f
add useEffect to loadProfile
ccatherinetan Nov 12, 2024
ffb2b73
[feat] pnboarding profile structure updated to remove default value a…
kevin3656 Nov 16, 2024
87f92fd
Merge branch '16-create-the-profile-context' of https://github.com/ca…
kevin3656 Nov 16, 2024
58bb001
removed default user, made options of type <T>
kevin3656 Nov 20, 2024
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
14 changes: 14 additions & 0 deletions api/supabase/queries/profiles.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UUID } from 'crypto';
import { Profile } from '@/types/schema';
import supabase from '../createClient';

Expand All @@ -12,3 +13,16 @@ export async function upsertProfile(profile: Profile) {

return data;
}

export async function fetchProfileById(userId: UUID) {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('user_id', userId)
.single();

if (error)
throw new Error(`Error fetching profile id ${userId}: ${error.message}`);

return data;
}
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -22,7 +23,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className={sans.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
<ProfileProvider>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</ProfileProvider>
</body>
</html>
);
Expand Down
129 changes: 76 additions & 53 deletions app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
'use client';

import React, { useState } from 'react';
import { upsertProfile } from '@/api/supabase/queries/profiles';
import { Profile } from '@/types/schema';
import { UUID } from 'crypto';
import { Profile, UserTypeEnum } from '@/types/schema';
import { useProfile } from '@/utils/ProfileProvider';

type Options<T> = {
label: string; // Represents the text for UI
value: T; // Represents the value stored in the DB
};

// Define the possible options for each question
const states = ['Tennessee', 'Missouri'];
const gardenTypes = ['Individual', 'Community', 'School'];
const plotOptions = [
const states: Options<string>[] = [
{ label: 'Tennesee', value: 'TENNESSEE' },
{ label: 'Missouri', value: 'MISSOUR' },
];
const gardenTypes: Options<UserTypeEnum>[] = [
{ label: 'Individual', value: 'INDIV' },
{ label: 'School', value: 'SCHOOL' },
{ label: 'Organization', value: 'ORG' },
];
const plotOptions: Options<boolean>[] = [
{ label: 'yes', value: true },
{ label: 'no', value: false },
];
const placeholderUserId: UUID = '0802d796-ace8-480d-851b-d16293c74a21';

//Interfaces and props to avoid typ errors on Lint
interface StateSelectionProps {
selectedState: string;
setSelectedState: React.Dispatch<React.SetStateAction<string>>;
selectedState?: string;
setSelectedState: (selected: string) => void;
}

interface GardenTypeSelectionProps {
selectedGardenType: string;
setSelectedGardenType: React.Dispatch<React.SetStateAction<string>>;
interface UserTypeSelectionProps {
selectedUserType?: UserTypeEnum;
setSelectedUserType: (selected: UserTypeEnum) => void;
}

interface PlotSelectionProps {
selectedPlot: boolean | null;
setSelectedPlot: React.Dispatch<React.SetStateAction<boolean>>;
selectedPlot?: boolean;
setSelectedPlot: (selected: boolean) => void;
}

// Select State
const StateSelection: React.FC<StateSelectionProps> = ({
const StateSelection = ({
selectedState,
setSelectedState,
}) => {
}: StateSelectionProps) => {
return (
<div>
<h2>Which state do you live in?</h2>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if you can move the heading out of this component and just put it in the retuned HTML? since it's present in every step of this flow

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo yeah i think it might be cleaner to make these reusable components, which take in the props:

{
headerText: string, 
value: string,
setValue: (string) => void,
options: string[],
disabledOption: string,
name: string, 
type: string,
}

But that change can be included in the next sprint! Also, since we're hardcoding the value and options to be strings, this structure might only work for StateSelection and GardenTypeSelection, but not PlotSelection? (unless we pass in an optional optionValues?: boolean[] prop?)
But even then we also have to map the values of states to all caps, (i.e. it should be 'TENNESSEE' in the db) and gardenTypes to UserTypeEnum

where export type UserTypeEnum = 'INDIV' | 'SCHOOL' | 'ORG'; should be in the schema

Expand All @@ -43,45 +58,45 @@ const StateSelection: React.FC<StateSelectionProps> = ({
Select a state
</option>
{states.map(state => (
<option key={state} value={state}>
{state}
<option key={state.label} value={String(state.value)}>
{state.label}
</option>
))}
</select>
</div>
);
};

// Step 2: Select garden type
// Step 2: Select user type

const GardenTypeSelection: React.FC<GardenTypeSelectionProps> = ({
selectedGardenType,
setSelectedGardenType,
}) => {
const UserTypeSelection = ({
selectedUserType,
setSelectedUserType,
}: UserTypeSelectionProps) => {
return (
<div>
<h2>What type of garden do you want to create?</h2>
{gardenTypes.map(type => (
<label key={type}>
<label key={type.label}>
<input
type="radio"
name="gardenType"
value={type}
checked={selectedGardenType === type}
onChange={e => setSelectedGardenType(e.target.value)}
value={type.value}
checked={selectedUserType === type.value}
onChange={e => setSelectedUserType(e.target.value as UserTypeEnum)}
/>
{type}
{type.label}
</label>
))}
</div>
);
};

// Step 3: Do you have a plot?
const PlotSelection: React.FC<PlotSelectionProps> = ({
const PlotSelection = ({
selectedPlot,
setSelectedPlot,
}) => {
}: PlotSelectionProps) => {
return (
<div>
<h2>Do you already have a plot?</h2>
Expand All @@ -91,7 +106,11 @@ const PlotSelection: React.FC<PlotSelectionProps> = ({
type="radio"
name="plot"
value={String(option.value)}
checked={selectedPlot === option.value}
checked={
typeof selectedPlot === 'undefined'
? false
: selectedPlot === option.value
}
onChange={() => setSelectedPlot(option.value)}
/>
{option.label}
Expand All @@ -102,11 +121,16 @@ const PlotSelection: React.FC<PlotSelectionProps> = ({
};

// Main Onboarding Component
const OnboardingFlow = () => {
export default function OnboardingFlow() {
const { setProfile, setHasPlot } = useProfile();
const [step, setStep] = useState(1);
const [selectedState, setSelectedState] = useState<string>('');
const [selectedGardenType, setSelectedGardenType] = useState<string>('');
const [selectedPlot, setSelectedPlot] = useState<boolean>(false);
const [selectedUserType, setSelectedUserType] = useState<
UserTypeEnum | undefined
>(undefined);
const [selectedPlot, setSelectedPlot] = useState<boolean | undefined>(
undefined,
);

const handleNext = () => {
setStep(step + 1);
Expand All @@ -115,24 +139,25 @@ const OnboardingFlow = () => {
const handleBack = () => {
setStep(step - 1);
};

const handleSubmit = async () => {
const profile: Profile = {
user_id: '2abd7296-374a-42d1-bb4f-b813da1615ae',
state: selectedState,
user_type: selectedGardenType,
has_plot: selectedPlot,
};
try {
upsertProfile(profile);
const profileToUpload: Profile = {
user_id: placeholderUserId,
us_state: selectedState,
user_type: selectedUserType!,
};

await setProfile(profileToUpload);
await setHasPlot(selectedPlot!); // Update has_plot
} catch (error) {
console.error('Error upserting profile:', error);
throw new Error('Error upserting profile');
} finally {
//TODO: Remove console log.
console.log('Submitted data: ', profile);
console.error('Error submitting profile:', error);
}
// Handle form submission, e.g., send to a server or display a confirmation
};

const disableNext = () => {
if (step === 1) return !selectedState;
if (step === 2) return !selectedUserType;
if (step === 3) return !(typeof selectedPlot === 'undefined');
};

return (
Expand All @@ -144,9 +169,9 @@ const OnboardingFlow = () => {
/>
)}
{step === 2 && (
<GardenTypeSelection
selectedGardenType={selectedGardenType}
setSelectedGardenType={setSelectedGardenType}
<UserTypeSelection
selectedUserType={selectedUserType}
setSelectedUserType={setSelectedUserType}
/>
)}
{step === 3 && (
Expand All @@ -159,14 +184,12 @@ const OnboardingFlow = () => {
<div>
{step > 1 && <button onClick={handleBack}>Back</button>}
{step < 3 && (
<button onClick={handleNext} disabled={!selectedState && step === 1}>
<button onClick={handleNext} disabled={disableNext()}>
Next
</button>
)}
{step === 3 && <button onClick={handleSubmit}>Submit</button>}
</div>
</div>
);
};

export default OnboardingFlow;
}
7 changes: 3 additions & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import BPLogo from '@/assets/images/bp-logo.png';
import { Container, Image } from './page.style';
import OnboardingFlow from './onboarding/page';
import { Container } from './page.style';

export default function Home() {
return (
<Container>
<Image src={BPLogo} alt="Blueprint Logo" />
<p>Open up app/page.tsx to get started!</p>
<OnboardingFlow />
</Container>
kevin3656 marked this conversation as resolved.
Show resolved Hide resolved
);
}
31 changes: 31 additions & 0 deletions hooks/useProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useState } from 'react';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this file carried over from the 1st sprint? if so, we may have to remove this file. (see above comment about removing useProfile from the app/onboarding/page.tsx as well)

import { Profile } from '../types/schema';

const initialProfiles: Profile[] = [];

export const useProfile = () => {
const [profiles, setProfiles] = useState<Profile[]>(initialProfiles);

const addProfile = (newProfile: Profile) => {
setProfiles(prev => [...prev, newProfile]);
};

const updateProfile = (index: number, updates: Partial<Profile>) => {
setProfiles(prev =>
prev.map((profile, i) =>
i === index ? { ...profile, ...updates } : profile,
),
);
};

const removeProfile = (index: number) => {
setProfiles(prev => prev.filter((_, i) => i !== index));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe can you write a comment explaining what's going on here

};

return {
profiles,
addProfile,
updateProfile,
removeProfile,
};
};
7 changes: 3 additions & 4 deletions types/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { UUID } from 'crypto';

export type SeasonEnum = 'SPRING' | 'SUMMER' | 'FALL' | 'WINTER';

export type UserTypeEnum = 'INDIV' | 'SCHOOL' | 'ORG';
export type DifficultyLevelEnum = 'EASY' | 'MODERATE' | 'HARD';

export interface Profile {
user_id: UUID;
state: string;
user_type: string;
has_plot: boolean;
us_state: string;
user_type: UserTypeEnum;
}

export interface Plant {
Expand Down
Loading
Loading