feat: edit habit working version
This commit is contained in:
parent
3fa3681c9b
commit
246cbe918a
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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),
|
||||||
})
|
})
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user