Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface Goal {
accountId: string
transactionIds: string[]
tagIds: string[]

// Task 2
icon?: string
}

export interface Tag {
Expand Down Expand Up @@ -65,4 +68,4 @@ export enum ApplicationStatus {
}

export type ModalContent = Goal
export type ModalType = 'Goal'
export type ModalType = 'Goal'
230 changes: 202 additions & 28 deletions src/ui/features/goalmanager/GoalManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@ import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons'
import { faDollarSign, IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date'
import { BaseEmoji } from 'emoji-mart'
import 'date-fns'
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'

import { updateGoal as updateGoalApi } from '../../../api/lib'
import { Goal } from '../../../api/types'
import { selectGoalsMap, updateGoal as updateGoalRedux } from '../../../store/goalsSlice'
import {
selectGoalsMap,
updateGoal as updateGoalRedux,
} from '../../../store/goalsSlice'
import { useAppDispatch, useAppSelector } from '../../../store/hooks'

import DatePicker from '../../components/DatePicker'
import EmojiPicker from '../../components/EmojiPicker'
import { Theme } from '../../components/Theme'

import GoalIcon from './GoalIcon'

type Props = { goal: Goal }

export function GoalManager(props: Props) {
const dispatch = useAppDispatch()

Expand All @@ -22,104 +32,255 @@ export function GoalManager(props: Props) {
const [targetDate, setTargetDate] = useState<Date | null>(null)
const [targetAmount, setTargetAmount] = useState<number | null>(null)

const [icon, setIcon] = useState<string | null>(null)

const [isPickerOpen, setIsPickerOpen] = useState(false)

useEffect(() => {
setName(props.goal.name)
setTargetDate(props.goal.targetDate)
setTargetAmount(props.goal.targetAmount)

setIcon(props.goal.icon ?? null)
}, [
props.goal.id,
props.goal.name,
props.goal.targetDate,
props.goal.targetAmount,
props.goal.icon,
])

useEffect(() => {
setName(goal.name)
}, [goal.name])
setIcon(goal.icon ?? null)
}, [goal.name, goal.icon])

const updateNameOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const updateNameOnChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const nextName = event.target.value

setName(nextName)

const updatedGoal: Goal = {
...props.goal,
name: nextName,
}

dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}

const updateTargetAmountOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const updateTargetAmountOnChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const nextTargetAmount = parseFloat(event.target.value)

setTargetAmount(nextTargetAmount)

const updatedGoal: Goal = {
...props.goal,
name: name ?? props.goal.name,
targetDate: targetDate ?? props.goal.targetDate,
targetAmount: nextTargetAmount,
icon: icon ?? undefined,
}

dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}

const pickDateOnChange = (date: MaterialUiPickersDate) => {
const pickDateOnChange = (
date: MaterialUiPickersDate
) => {
if (date != null) {

setTargetDate(date)

const updatedGoal: Goal = {
...props.goal,

name: name ?? props.goal.name,
targetDate: date ?? props.goal.targetDate,
targetAmount: targetAmount ?? props.goal.targetAmount,

targetDate:
date ?? props.goal.targetDate,

targetAmount:
targetAmount ??
props.goal.targetAmount,

icon: icon ?? undefined,
}

dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}
}

const updateIcon = (
emoji: BaseEmoji
) => {

setIcon(emoji.native)

const updatedGoal: Goal = {

...props.goal,

name: name ?? props.goal.name,

targetDate:
targetDate ??
props.goal.targetDate,

targetAmount:
targetAmount ??
props.goal.targetAmount,

icon: emoji.native,
}

dispatch(updateGoalRedux(updatedGoal))

updateGoalApi(
props.goal.id,
updatedGoal
)

setIsPickerOpen(false)
}

return (
<GoalManagerContainer>
<NameInput value={name ?? ''} onChange={updateNameOnChange} />

<NameInput
value={name ?? ''}
onChange={updateNameOnChange}
/>

<GoalIcon
icon={icon}
onClick={() =>
setIsPickerOpen(
!isPickerOpen
)
}
/>

{isPickerOpen && (

<EmojiPicker
onClick={updateIcon}
/>

)}

<Group>
<Field name="Target Date" icon={faCalendarAlt} />

<Field
name="Target Date"
icon={faCalendarAlt}
/>

<Value>
<DatePicker value={targetDate} onChange={pickDateOnChange} />

<DatePicker
value={targetDate}
onChange={pickDateOnChange}
/>

</Value>

</Group>

<Group>
<Field name="Target Amount" icon={faDollarSign} />

<Field
name="Target Amount"
icon={faDollarSign}
/>

<Value>
<StringInput value={targetAmount ?? ''} onChange={updateTargetAmountOnChange} />

<StringInput
value={targetAmount ?? ''}
onChange={
updateTargetAmountOnChange
}
/>

</Value>

</Group>

<Group>
<Field name="Balance" icon={faDollarSign} />

<Field
name="Balance"
icon={faDollarSign}
/>

<Value>
<StringValue>{props.goal.balance}</StringValue>

<StringValue>

{props.goal.balance}

</StringValue>

</Value>

</Group>

<Group>
<Field name="Date Created" icon={faCalendarAlt} />

<Field
name="Date Created"
icon={faCalendarAlt}
/>

<Value>
<StringValue>{new Date(props.goal.created).toLocaleDateString()}</StringValue>

<StringValue>

{new Date(
props.goal.created
).toLocaleDateString()}

</StringValue>

</Value>

</Group>

</GoalManagerContainer>
)
}

type FieldProps = { name: string; icon: IconDefinition }
type AddIconButtonContainerProps = { shouldShow: boolean }
type GoalIconContainerProps = { shouldShow: boolean }
type EmojiPickerContainerProps = { isOpen: boolean; hasIcon: boolean }
type FieldProps = {
name: string
icon: IconDefinition
}

const Field = (
props: FieldProps
) => (

const Field = (props: FieldProps) => (
<FieldContainer>
<FontAwesomeIcon icon={props.icon} size="2x" />
<FieldName>{props.name}</FieldName>

<FontAwesomeIcon
icon={props.icon}
size="2x"
/>

<FieldName>

{props.name}

</FieldName>

</FieldContainer>

)

const GoalManagerContainer = styled.div`
Expand All @@ -139,46 +300,59 @@ const Group = styled.div`
margin-top: 1.25rem;
margin-bottom: 1.25rem;
`

const NameInput = styled.input`
display: flex;
background-color: transparent;
outline: none;
border: none;
font-size: 4rem;
font-weight: bold;
color: ${({ theme }: { theme: Theme }) => theme.text};

color: ${({ theme }: { theme: Theme }) =>
theme.text};
`

const FieldName = styled.h1`
font-size: 1.8rem;
margin-left: 1rem;
color: rgba(174, 174, 174, 1);
color: rgba(174,174,174,1);
font-weight: normal;
`

const FieldContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
width: 20rem;

svg {
color: rgba(174, 174, 174, 1);
color: rgba(174,174,174,1);
}
`

const StringValue = styled.h1`
font-size: 1.8rem;
font-weight: bold;
`

const StringInput = styled.input`
display: flex;

background-color: transparent;

outline: none;

border: none;

font-size: 1.8rem;

font-weight: bold;
color: ${({ theme }: { theme: Theme }) => theme.text};

color: ${({ theme }: { theme: Theme }) =>
theme.text};
`

const Value = styled.div`
margin-left: 2rem;
`
`