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-04-05 00:08:40 +02:00
import { useHabitsTracker } from "../../contexts/HabitsTracker"
2024-04-12 23:13:38 +02:00
import { useBoolean } from "../../hooks/useBoolean"
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 ,
handleSubmit ,
2024-04-05 00:08:40 +02:00
reset ,
2024-04-13 17:57:58 +02:00
watch ,
formState : { errors , isValid } ,
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" ,
2024-04-11 15:26:20 +02:00
icon : "circle-question" ,
2024-04-04 12:31:56 +02:00
goal : {
frequency : "daily" ,
target : {
type : "boolean" ,
} ,
} ,
} ,
} )
2024-04-13 17:57:58 +02:00
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 ,
{
2024-04-13 17:57:58 +02:00
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
2024-04-13 17:57:58 +02:00
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 (
< >
2024-04-11 15:26:20 +02:00
< Text
style = { [
styles . spacing ,
{ justifyContent : "center" , alignContent : "center" } ,
] }
>
Habit Type
2024-04-12 23:13:38 +02:00
{ / * < T o o l t i p
2024-04-11 15:26:20 +02:00
title = "Routine habits are activities performed regularly, while Target habits involve setting specific objectives to be achieved through repeated actions."
enterTouchDelay = { 50 }
leaveTouchDelay = { 25 }
>
< IconButton
icon = "chat-question-outline"
selected
size = { 24 }
onPress = { ( ) = > { } }
style = { { alignSelf : "center" } }
/ >
2024-04-12 23:13:38 +02:00
< / Tooltip > * / }
2024-04-11 15:26:20 +02:00
< / Text >
2024-04-05 00:08:40 +02:00
< SegmentedButtons
2024-04-13 17:57:58 +02:00
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"
/ >
2024-04-13 17:57:58 +02:00
{ 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
2024-04-13 17:57:58 +02:00
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 ,
2024-04-13 17:57:58 +02:00
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" }
2024-04-13 17:57:58 +02:00
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 : {
2024-04-13 17:57:58 +02:00
marginVertical : 10 ,
2024-04-04 17:06:42 +02:00
} ,
2024-04-05 00:08:40 +02:00
} )