1
1
mirror of https://github.com/theoludwig/p61-project.git synced 2024-07-17 07:00:12 +02:00
p61-project/presentation/react-native/components/HabitForm/HabitCreateForm.tsx

347 lines
9.6 KiB
TypeScript
Raw Permalink Normal View History

2024-04-12 23:13:38 +02:00
import type { IconName } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
2024-04-04 17:06:42 +02:00
import { zodResolver } from "@hookform/resolvers/zod"
2024-04-12 23:13:38 +02:00
import { useState } from "react"
2024-04-04 17:06:42 +02:00
import { Controller, useForm } from "react-hook-form"
2024-04-12 23:13:38 +02:00
import { ScrollView, StyleSheet, View } from "react-native"
2024-04-04 17:26:48 +02:00
import {
Button,
HelperText,
SegmentedButtons,
2024-04-05 00:08:40 +02:00
Snackbar,
2024-04-04 17:33:51 +02:00
Text,
2024-04-04 17:26:48 +02:00
TextInput,
} from "react-native-paper"
2024-04-04 12:31:56 +02:00
import { SafeAreaView } from "react-native-safe-area-context"
2024-04-04 17:06:42 +02:00
import ColorPicker, {
HueSlider,
Panel1,
Preview,
} from "reanimated-color-picker"
2024-04-04 12:31:56 +02:00
2024-04-04 17:33:51 +02:00
import type { GoalFrequency, GoalType } from "@/domain/entities/Goal"
import { GOAL_FREQUENCIES, GOAL_TYPES } from "@/domain/entities/Goal"
2024-04-04 12:31:56 +02:00
import type { HabitCreateData } from "@/domain/entities/Habit"
2024-04-04 17:06:42 +02:00
import { HabitCreateSchema } from "@/domain/entities/Habit"
2024-04-04 12:31:56 +02:00
import type { User } from "@/domain/entities/User"
2024-05-02 01:08:27 +02:00
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
2024-04-12 23:13:38 +02:00
import { IconSelectorModal } from "./IconSelectorModal"
2024-04-04 12:31:56 +02:00
export interface HabitCreateFormProps {
user: User
}
export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
2024-04-05 00:08:40 +02:00
const { habitCreate, habitsTrackerPresenter } = useHabitsTracker()
2024-04-04 17:26:48 +02:00
2024-04-04 17:06:42 +02:00
const {
control,
2024-05-02 01:08:27 +02:00
formState: { errors, isValid },
2024-04-04 17:06:42 +02:00
handleSubmit,
2024-04-05 00:08:40 +02:00
reset,
watch,
2024-04-04 17:06:42 +02:00
} = useForm<HabitCreateData>({
mode: "onChange",
resolver: zodResolver(HabitCreateSchema),
2024-04-04 12:31:56 +02:00
defaultValues: {
userId: user.id,
name: "",
color: "#006CFF",
icon: "circle-question",
2024-04-04 12:31:56 +02:00
goal: {
frequency: "daily",
target: {
type: "boolean",
},
},
},
})
const watchGoalType = watch("goal.target.type")
2024-04-05 00:08:40 +02:00
const [isVisibleSnackbar, setIsVisibleSnackbar] = useState(false)
2024-04-12 23:13:38 +02:00
const {
value: isModalIconSelectorVisible,
setTrue: openModalIconSelector,
setFalse: closeModalIconSelector,
} = useBoolean()
2024-04-05 00:08:40 +02:00
const onDismissSnackbar = (): void => {
setIsVisibleSnackbar(false)
}
const onSubmit = async (data: HabitCreateData): Promise<void> => {
await habitsTrackerPresenter.habitCreate(data)
setIsVisibleSnackbar(true)
2024-04-12 23:13:38 +02:00
closeModalIconSelector()
2024-04-05 00:08:40 +02:00
reset()
}
const habitFrequenciesTranslations: {
[key in GoalFrequency]: { label: string; icon: string }
} = {
daily: {
label: "Daily",
icon: "calendar",
},
weekly: {
label: "Weekly",
icon: "calendar-week",
},
monthly: {
label: "Monthly",
icon: "calendar-month",
},
}
const habitTypesTranslations: {
[key in GoalType]: { label: string; icon: string }
} = {
boolean: {
label: "Routine",
icon: "clock",
},
numeric: {
label: "Target",
icon: "target",
},
}
2024-04-04 12:31:56 +02:00
return (
<SafeAreaView>
2024-04-05 00:08:40 +02:00
<ScrollView
contentContainerStyle={{
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 20,
}}
>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => {
return (
<>
<TextInput
placeholder="Name"
onBlur={onBlur}
onChangeText={onChange}
value={value}
style={[
styles.spacing,
{
width: "96%",
2024-04-05 00:08:40 +02:00
},
]}
mode="outlined"
/>
{errors.name != null ? (
<HelperText type="error" visible style={[{ paddingTop: 0 }]}>
{errors.name.type === "too_big"
? "Name is too long"
: "Name is required"}
</HelperText>
) : null}
</>
)
}}
name="name"
/>
<Controller
control={control}
render={({ field: { onChange, value } }) => {
return (
<>
<Text style={[styles.spacing]}>Habit Frequency</Text>
<SegmentedButtons
style={[{ width: "96%" }]}
2024-04-05 00:08:40 +02:00
onValueChange={onChange}
value={value}
buttons={GOAL_FREQUENCIES.map((frequency) => {
return {
label: habitFrequenciesTranslations[frequency].label,
value: frequency,
icon: habitFrequenciesTranslations[frequency].icon,
}
})}
/>
</>
)
2024-04-04 12:31:56 +02:00
}}
2024-04-05 00:08:40 +02:00
name="goal.frequency"
2024-04-04 12:31:56 +02:00
/>
2024-04-04 17:06:42 +02:00
2024-04-05 00:08:40 +02:00
<Controller
control={control}
render={({ field: { onChange, value } }) => {
return (
<>
<Text
style={[
styles.spacing,
{ justifyContent: "center", alignContent: "center" },
]}
>
Habit Type
</Text>
2024-04-05 00:08:40 +02:00
<SegmentedButtons
style={[{ width: "96%" }]}
2024-04-05 00:08:40 +02:00
onValueChange={onChange}
value={value}
buttons={GOAL_TYPES.map((type) => {
return {
label: habitTypesTranslations[type].label,
value: type,
icon: habitTypesTranslations[type].icon,
}
})}
/>
</>
)
}}
name="goal.target.type"
/>
{watchGoalType === "numeric" ? (
<View
style={{
marginTop: 10,
flexDirection: "row",
gap: 10,
width: "96%",
}}
>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => {
return (
<TextInput
placeholder="Target (e.g: 5 000)"
onBlur={onBlur}
onChangeText={(text) => {
if (text.length <= 0) {
onChange("")
return
}
onChange(Number.parseInt(text, 10))
}}
value={value?.toString()}
style={[
styles.spacing,
{
width: "50%",
},
]}
mode="outlined"
keyboardType="numeric"
/>
)
}}
name="goal.target.value"
/>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => {
return (
<TextInput
placeholder="Unit (e.g: Steps)"
onBlur={onBlur}
onChangeText={onChange}
value={value}
style={[
styles.spacing,
{
width: "50%",
},
]}
mode="outlined"
/>
)
}}
name="goal.target.unit"
/>
</View>
) : null}
2024-04-05 00:08:40 +02:00
<Controller
control={control}
render={({ field: { onChange, value } }) => {
return (
<ColorPicker
style={[{ marginVertical: 15, width: "96%" }]}
2024-04-05 00:08:40 +02:00
value={value}
onComplete={(value) => {
onChange(value.hex)
}}
>
<Preview hideInitialColor />
<Panel1 />
<HueSlider />
</ColorPicker>
)
}}
name="color"
/>
<Controller
control={control}
2024-04-12 10:29:23 +02:00
render={({ field: { onChange, value } }) => {
2024-04-05 00:08:40 +02:00
return (
2024-04-12 23:13:38 +02:00
<View
style={{
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
gap: 20,
marginVertical: 5,
2024-04-12 23:13:38 +02:00
}}
>
<FontAwesomeIcon size={36} icon={value as IconName} />
<Button mode="contained" onPress={openModalIconSelector}>
Choose an icon
</Button>
<IconSelectorModal
key={isModalIconSelectorVisible ? "visible" : "hidden"}
isVisible={isModalIconSelectorVisible}
selectedIcon={value}
handleCloseModal={closeModalIconSelector}
onIconSelect={onChange}
/>
</View>
2024-04-05 00:08:40 +02:00
)
}}
name="icon"
/>
<Button
mode="contained"
onPress={handleSubmit(onSubmit)}
loading={habitCreate.state === "loading"}
disabled={habitCreate.state === "loading" || !isValid}
style={[{ width: "100%", marginVertical: 15 }]}
2024-04-05 00:08:40 +02:00
>
Create your habit! 🚀
</Button>
</ScrollView>
<Snackbar
visible={isVisibleSnackbar}
onDismiss={onDismissSnackbar}
duration={2_000}
2024-04-04 17:06:42 +02:00
>
2024-04-05 00:08:40 +02:00
Habit created successfully!
</Snackbar>
2024-04-04 12:31:56 +02:00
</SafeAreaView>
)
}
2024-04-04 17:06:42 +02:00
2024-04-05 00:08:40 +02:00
const styles = StyleSheet.create({
spacing: {
marginVertical: 10,
2024-04-04 17:06:42 +02:00
},
2024-04-05 00:08:40 +02:00
})