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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/api/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export async function createGoal(): Promise<Goal | null> {

export async function updateGoal(goalId: string, updatedGoal: Goal): Promise<boolean> {
try {
await axios.put(`${API_ROOT}/api/Goal/${goalId}`, updatedGoal)
await axios.put(`${API_ROOT}/api/Goal/${goalId}`, updatedGoal, {
headers: {
'Content-Type': 'application/json',
},
})

return true
} catch (error: any) {
return false
Expand Down
1 change: 1 addition & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface Goal {
targetAmount: number
balance: number
targetDate: Date
icon: string | null
created: Date
accountId: string
transactionIds: string[]
Expand Down
139 changes: 96 additions & 43 deletions src/ui/features/goalmanager/GoalManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date'
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 { useAppDispatch, useAppSelector } from '../../../store/hooks'

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

import { BaseEmoji } from 'emoji-mart/dist-es/utils/emoji-index/nimble-emoji-index'

type Props = { goal: Goal }

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

Expand All @@ -22,86 +28,142 @@ 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>(props.goal.icon ?? null)

const [emojiPickerIsOpen, setEmojiPickerIsOpen] = useState(false)

const hasIcon = () => icon != null

useEffect(() => {
setName(props.goal.name)
setTargetDate(props.goal.targetDate)
setTargetAmount(props.goal.targetAmount)
}, [
props.goal.id,
props.goal.name,
props.goal.targetDate,
props.goal.targetAmount,
])
}, [props.goal.id, props.goal.name, props.goal.targetDate, props.goal.targetAmount])

useEffect(() => {
setName(goal.name)
}, [goal.name])

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 nextTargetAmount = parseFloat(event.target.value)

setTargetAmount(nextTargetAmount)

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

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

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,
}

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

const pickEmojiOnClick = (emoji: BaseEmoji, event: React.MouseEvent) => {
event.stopPropagation()

setIcon(emoji.native)

setEmojiPickerIsOpen(false)

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

dispatch(updateGoalRedux(updatedGoal))

updateGoalApi(props.goal.id, updatedGoal)
}

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

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

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

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

<Value>
<StringInput value={targetAmount ?? ''} onChange={updateTargetAmountOnChange} />
</Value>
</Group>
<Group>
<Field name="Icon" icon={faDollarSign} />

<Value>
<StringValue
onClick={(event) => {
event.stopPropagation()
setEmojiPickerIsOpen(true)
}}
style={{ cursor: 'pointer' }}
>
{icon ?? 'Click to add icon'}
</StringValue>
</Value>
</Group>

<EmojiPickerContainer
isOpen={emojiPickerIsOpen}
hasIcon={hasIcon()}
onClick={(event) => event.stopPropagation()}
>
<EmojiPicker onClick={pickEmojiOnClick} />
</EmojiPickerContainer>

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

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

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

<Value>
<StringValue>{new Date(props.goal.created).toLocaleDateString()}</StringValue>
</Value>
Expand All @@ -110,75 +172,66 @@ export function GoalManager(props: Props) {
)
}

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
}

type EmojiPickerContainerProps = {
isOpen: boolean
hasIcon: boolean
}

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

<FieldName>{props.name}</FieldName>
</FieldContainer>
)

const GoalManagerContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
height: 100%;
width: 100%;
position: relative;
`

const Group = styled.div`
display: flex;
flex-direction: row;
width: 100%;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
margin-top: 1rem;
`

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

const FieldName = styled.h1`
font-size: 1.8rem;
margin-left: 1rem;
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);
}
`

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

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

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

const EmojiPickerContainer = styled.div<EmojiPickerContainerProps>`
display: ${(props) => (props.isOpen ? 'flex' : 'none')};
position: absolute;
top: ${(props) => (props.hasIcon ? '10rem' : '2rem')};
left: 0;
`
12 changes: 12 additions & 0 deletions src/ui/pages/Main/goals/GoalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
setType as setTypeRedux
} from '../../../../store/modalSlice'
import { Card } from '../../../components/Card'
import Icon from '@material-ui/core/Icon/Icon'

type Props = { id: string }

Expand All @@ -29,6 +30,7 @@ export default function GoalCard(props: Props) {
<Container key={goal.id} onClick={onClick}>
<TargetAmount>${goal.targetAmount}</TargetAmount>
<TargetDate>{asLocaleDateString(goal.targetDate)}</TargetDate>
<GoalIcon>{goal.icon ?? '➕'}</GoalIcon>
</Container>
)
}
Expand All @@ -54,3 +56,13 @@ const TargetDate = styled.h4`
color: rgba(174, 174, 174, 1);
font-size: 1rem;
`
const GoalIcon = styled.div`
font-size: 4rem;

display: flex;
justify-content: center;
align-items: center;

margin-top: 0.5rem;
margin-bottom: 0.5rem;
`