feat: edit habit working version

This commit is contained in:
Théo LUDWIG 2024-04-11 13:07:17 +02:00
parent 3fa3681c9b
commit 246cbe918a
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
9 changed files with 59 additions and 72 deletions

View File

@ -1,39 +1,20 @@
import { Redirect, useLocalSearchParams } from "expo-router" import { Redirect, useLocalSearchParams } from "expo-router"
import { Text } from "react-native-paper"
import { SafeAreaView } from "react-native-safe-area-context"
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
import { HabitEditForm } from "@/presentation/react/components/HabitEditForm/HabitEditForm" import { HabitEditForm } from "@/presentation/react/components/HabitEditForm/HabitEditForm"
import { useAuthentication } from "@/presentation/react/contexts/Authentication" import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
const HabitPage: React.FC = () => { const HabitPage: React.FC = () => {
const { habitId } = useLocalSearchParams() const { habitId } = useLocalSearchParams()
const { habitsTracker } = useHabitsTracker() const { habitsTracker } = useHabitsTracker()
const { user } = useAuthentication()
if (user === null) {
return null
}
const habitHistory = habitsTracker.getHabitHistoryById(habitId as string) const habitHistory = habitsTracker.getHabitHistoryById(habitId as string)
if (habitHistory == null) { if (habitHistory == null) {
return <Redirect href="/application/habits/" /> return <Redirect href="/application/habits/" />
} }
return ( return (
<SafeAreaView <HabitEditForm habit={habitHistory.habit} key={habitHistory.habit.id} />
style={[
{
flex: 1,
alignItems: "center",
},
]}
>
<Text>
Habit Page {habitId} {habitHistory.habit.name}
</Text>
<HabitEditForm user={user} habit={habitHistory.habit} />
</SafeAreaView>
) )
} }

View File

@ -4,7 +4,7 @@ import { useAuthentication } from "@/presentation/react/contexts/Authentication"
const NewHabitPage: React.FC = () => { const NewHabitPage: React.FC = () => {
const { user } = useAuthentication() const { user } = useAuthentication()
if (user === null) { if (user == null) {
return null return null
} }

View File

@ -33,22 +33,8 @@ export const GoalCreateSchema = z.object({
]), ]),
}) })
export const GoalEditSchema = z.object({
frequency: goalFrequencyZod,
target: z.discriminatedUnion("type", [
z.object({ type: z.literal("boolean") }),
z.object({
type: z.literal("numeric"),
value: z.number().int().min(0),
unit: z.string().min(1),
}),
]),
})
export type GoalCreateData = z.infer<typeof GoalCreateSchema> export type GoalCreateData = z.infer<typeof GoalCreateSchema>
export type GoalEditData = z.infer<typeof GoalEditSchema>
interface GoalBase { interface GoalBase {
frequency: GoalFrequency frequency: GoalFrequency
} }

View File

@ -15,9 +15,7 @@ export const HabitCreateSchema = HabitSchema.extend({
}).omit({ id: true }) }).omit({ id: true })
export type HabitCreateData = z.infer<typeof HabitCreateSchema> export type HabitCreateData = z.infer<typeof HabitCreateSchema>
export const HabitEditSchema = HabitSchema.extend({ export const HabitEditSchema = HabitSchema.extend({})
goal: GoalCreateSchema,
})
export type HabitEditData = z.infer<typeof HabitEditSchema> export type HabitEditData = z.infer<typeof HabitEditSchema>
type HabitBase = z.infer<typeof HabitSchema> type HabitBase = z.infer<typeof HabitSchema>

View File

@ -35,6 +35,14 @@ export class HabitsTracker implements HabitsTrackerData {
) )
} }
public editHabit(habit: Habit): void {
const habitHistory = this.getHabitHistoryById(habit.id)
if (habitHistory == null) {
return
}
habitHistory.habit = habit
}
public getAllHabitsHistory(): HabitHistory[] { public getAllHabitsHistory(): HabitHistory[] {
return [ return [
...this.habitsHistory.daily, ...this.habitsHistory.daily,

View File

@ -8,6 +8,8 @@ import { supabaseClient } from "./supabase/supabase"
import { AuthenticationPresenter } from "@/presentation/presenters/Authentication" import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate" import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate"
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate" import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
/** /**
* Repositories * Repositories
@ -25,6 +27,9 @@ const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
const habitCreateRepository = new HabitCreateSupabaseRepository({ const habitCreateRepository = new HabitCreateSupabaseRepository({
supabaseClient, supabaseClient,
}) })
const habitEditRepository = new HabitEditSupabaseRepository({
supabaseClient,
})
/** /**
* Use Cases * Use Cases
@ -39,6 +44,9 @@ const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
getHabitProgressHistoryRepository: getHabitProgressesRepository, getHabitProgressHistoryRepository: getHabitProgressesRepository,
getHabitsByUserIdRepository, getHabitsByUserIdRepository,
}) })
const habitEditUseCase = new HabitEditUseCase({
habitEditRepository,
})
/** /**
* Presenters * Presenters
@ -49,4 +57,5 @@ export const authenticationPresenter = new AuthenticationPresenter({
export const habitsTrackerPresenter = new HabitsTrackerPresenter({ export const habitsTrackerPresenter = new HabitsTrackerPresenter({
retrieveHabitsTrackerUseCase, retrieveHabitsTrackerUseCase,
habitCreateUseCase, habitCreateUseCase,
habitEditUseCase,
}) })

View File

@ -12,18 +12,11 @@ export class HabitEditSupabaseRepository
const { data, error } = await this.supabaseClient const { data, error } = await this.supabaseClient
.from("habits") .from("habits")
.update({ .update({
id: Number(habitEditData.id),
name: habitEditData.name, name: habitEditData.name,
color: habitEditData.color, color: habitEditData.color,
icon: habitEditData.icon, icon: habitEditData.icon,
goal_frequency: habitEditData.goal.frequency,
...(habitEditData.goal.target.type === "numeric"
? {
goal_target: habitEditData.goal.target.value,
goal_target_unit: habitEditData.goal.target.unit,
}
: {}),
}) })
.eq("id", habitEditData.id)
.select("*") .select("*")
const updatedHabit = data?.[0] const updatedHabit = data?.[0]
if (error != null || updatedHabit == null) { if (error != null || updatedHabit == null) {
@ -34,7 +27,20 @@ export class HabitEditSupabaseRepository
userId: updatedHabit.user_id.toString(), userId: updatedHabit.user_id.toString(),
name: updatedHabit.name, name: updatedHabit.name,
icon: updatedHabit.icon, icon: updatedHabit.icon,
goal: Goal.create(habitEditData.goal), goal: Goal.create({
frequency: updatedHabit.goal_frequency,
target:
updatedHabit.goal_target != null &&
updatedHabit.goal_target_unit != null
? {
type: "numeric",
value: updatedHabit.goal_target,
unit: updatedHabit.goal_target_unit,
}
: {
type: "boolean",
},
}),
color: updatedHabit.color, color: updatedHabit.color,
startDate: new Date(updatedHabit.start_date), startDate: new Date(updatedHabit.start_date),
}) })

View File

@ -7,9 +7,10 @@ import type {
RetrieveHabitsTrackerUseCase, RetrieveHabitsTrackerUseCase,
RetrieveHabitsTrackerUseCaseOptions, RetrieveHabitsTrackerUseCaseOptions,
} from "@/domain/use-cases/RetrieveHabitsTracker" } from "@/domain/use-cases/RetrieveHabitsTracker"
import type { HabitCreateData } from "@/domain/entities/Habit" import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
import { getErrorsFieldsFromZodError } from "../../utils/zod" import { getErrorsFieldsFromZodError } from "../../utils/zod"
import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate" import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
export interface HabitsTrackerPresenterState { export interface HabitsTrackerPresenterState {
habitsTracker: HabitsTracker habitsTracker: HabitsTracker
@ -29,7 +30,7 @@ export interface HabitsTrackerPresenterState {
habitEdit: { habitEdit: {
state: FetchState state: FetchState
errors: { errors: {
fields: Array<keyof HabitCreateData> fields: Array<keyof HabitEditData>
global: ErrorGlobal global: ErrorGlobal
} }
} }
@ -38,6 +39,7 @@ export interface HabitsTrackerPresenterState {
export interface HabitsTrackerPresenterOptions { export interface HabitsTrackerPresenterOptions {
retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
habitCreateUseCase: HabitCreateUseCase habitCreateUseCase: HabitCreateUseCase
habitEditUseCase: HabitEditUseCase
} }
export class HabitsTrackerPresenter export class HabitsTrackerPresenter
@ -46,9 +48,14 @@ export class HabitsTrackerPresenter
{ {
public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
public habitCreateUseCase: HabitCreateUseCase public habitCreateUseCase: HabitCreateUseCase
public habitEditUseCase: HabitEditUseCase
public constructor(options: HabitsTrackerPresenterOptions) { public constructor(options: HabitsTrackerPresenterOptions) {
const { retrieveHabitsTrackerUseCase, habitCreateUseCase } = options const {
retrieveHabitsTrackerUseCase,
habitCreateUseCase,
habitEditUseCase,
} = options
const habitsTracker = HabitsTracker.default() const habitsTracker = HabitsTracker.default()
super({ super({
habitsTracker, habitsTracker,
@ -70,6 +77,7 @@ export class HabitsTrackerPresenter
}) })
this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase
this.habitCreateUseCase = habitCreateUseCase this.habitCreateUseCase = habitCreateUseCase
this.habitEditUseCase = habitEditUseCase
} }
public async habitCreate(data: unknown): Promise<FetchState> { public async habitCreate(data: unknown): Promise<FetchState> {
@ -110,10 +118,10 @@ export class HabitsTrackerPresenter
global: null, global: null,
} }
}) })
const habit = await this.habitCreateUseCase.execute(data) const habit = await this.habitEditUseCase.execute(data)
this.setState((state) => { this.setState((state) => {
state.habitEdit.state = "success" state.habitEdit.state = "success"
state.habitsTracker.addHabit(habit) state.habitsTracker.editHabit(habit)
}) })
return "success" return "success"
} catch (error) { } catch (error) {
@ -121,7 +129,7 @@ export class HabitsTrackerPresenter
state.habitEdit.state = "error" state.habitEdit.state = "error"
if (error instanceof ZodError) { if (error instanceof ZodError) {
state.habitEdit.errors.fields = state.habitEdit.errors.fields =
getErrorsFieldsFromZodError<HabitCreateData>(error) getErrorsFieldsFromZodError<HabitEditData>(error)
} else { } else {
state.habitEdit.errors.global = "unknown" state.habitEdit.errors.global = "unknown"
} }

View File

@ -12,36 +12,28 @@ import ColorPicker, {
import type { Habit, HabitEditData } from "@/domain/entities/Habit" import type { Habit, HabitEditData } from "@/domain/entities/Habit"
import { HabitEditSchema } from "@/domain/entities/Habit" import { HabitEditSchema } from "@/domain/entities/Habit"
import type { User } from "@/domain/entities/User"
import { useHabitsTracker } from "../../contexts/HabitsTracker" import { useHabitsTracker } from "../../contexts/HabitsTracker"
export interface HabitEditFormProps { export interface HabitEditFormProps {
user: User
habit: Habit habit: Habit
} }
export const HabitEditForm: React.FC<HabitEditFormProps> = ({ user }) => { export const HabitEditForm: React.FC<HabitEditFormProps> = ({ habit }) => {
const { habitEdit, habitsTrackerPresenter } = useHabitsTracker() const { habitEdit, habitsTrackerPresenter } = useHabitsTracker()
const { const {
control, control,
handleSubmit, handleSubmit,
reset,
formState: { errors }, formState: { errors },
} = useForm<HabitEditData>({ } = useForm<HabitEditData>({
mode: "onChange", mode: "onChange",
resolver: zodResolver(HabitEditSchema), resolver: zodResolver(HabitEditSchema),
defaultValues: { defaultValues: {
userId: user.id, id: habit.id,
name: "", userId: habit.userId,
color: "#006CFF", name: habit.name,
icon: "lightbulb", color: habit.color,
goal: { icon: habit.icon,
frequency: "daily",
target: {
type: "boolean",
},
},
}, },
}) })
@ -54,7 +46,6 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ user }) => {
const onSubmit = async (data: HabitEditData): Promise<void> => { const onSubmit = async (data: HabitEditData): Promise<void> => {
await habitsTrackerPresenter.habitEdit(data) await habitsTrackerPresenter.habitEdit(data)
setIsVisibleSnackbar(true) setIsVisibleSnackbar(true)
reset()
} }
return ( return (
@ -141,7 +132,7 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ user }) => {
disabled={habitEdit.state === "loading"} disabled={habitEdit.state === "loading"}
style={[styles.spacing, { width: "90%" }]} style={[styles.spacing, { width: "90%" }]}
> >
Create your habit! 🚀 Save
</Button> </Button>
</ScrollView> </ScrollView>
@ -150,7 +141,7 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ user }) => {
onDismiss={onDismissSnackbar} onDismiss={onDismissSnackbar}
duration={2_000} duration={2_000}
> >
Habit created successfully! Habit Saved successfully!
</Snackbar> </Snackbar>
</SafeAreaView> </SafeAreaView>
) )