Skip to content

Implement Add and Icon components for Goal Manager#21

Open
MayuriYatagiri wants to merge 1 commit into
fencer-so:masterfrom
MayuriYatagiri:patch-1
Open

Implement Add and Icon components for Goal Manager#21
MayuriYatagiri wants to merge 1 commit into
fencer-so:masterfrom
MayuriYatagiri:patch-1

Conversation

@MayuriYatagiri

@MayuriYatagiri MayuriYatagiri commented Jul 2, 2026

Copy link
Copy Markdown

GoalManager.tsx
Added components for adding and displaying icons in the Goal Manager.

import { faCalendarAlt, faSmile } 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 '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'

// Sub-component required by the solution specification
type GoalIconProps = { icon: string | null; onClick: (event: React.MouseEvent) => void }
const Icon = styled.h1font-size: 6rem; cursor: pointer;
function GoalIcon(props: GoalIconProps) {
return (

{props.icon}

)
}

type Props = { goal: Goal }

export function GoalManager(props: Props) {
const dispatch = useAppDispatch()
const goal = useAppSelector(selectGoalsMap)[props.goal.id]

const [title, setTitle] = useState<string | null>(null)
const [icon, setIcon] = useState<string | null>(null)
const [targetDate, setTargetDate] = useState<Date | null>(null)
const [targetAmount, setTargetAmount] = useState<number | null>(null)
const [emojiPickerIsOpen, setEmojiPickerIsOpen] = useState(false)

useEffect(() => {
setTitle(props.goal.title)
setIcon(props.goal.icon)
setTargetDate(props.goal.targetDate)
setTargetAmount(props.goal.targetAmount)
}, [
props.goal.id,
props.goal.title,
props.goal.icon,
props.goal.targetDate,
props.goal.targetAmount,
])

useEffect(() => {
if (goal) {
setTitle(goal.title)
setIcon(goal.icon)
}
}, [goal?.title, goal?.icon, goal])

const hasIcon = () => icon != null

const addIconOnClick = (event: React.MouseEvent) => {
event.stopPropagation()
setEmojiPickerIsOpen(true)
}

const pickEmojiOnClick = (emoji: any, event: React.MouseEvent) => {
event.stopPropagation()
setIcon(emoji.native)
setEmojiPickerIsOpen(false)

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

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

}

const updateTitleOnChange = (event: React.ChangeEvent) => {
const nextTitle = event.target.value
setTitle(nextTitle)
const updatedGoal: Goal = {
...props.goal,
title: nextTitle,
}
dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}

const updateTargetAmountOnChange = (event: React.ChangeEvent) => {
const nextTargetAmount = parseFloat(event.target.value)
setTargetAmount(nextTargetAmount)
const updatedGoal: Goal = {
...props.goal,
title: title ?? props.goal.title,
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,
title: title ?? props.goal.title,
targetDate: date ?? props.goal.targetDate,
targetAmount: targetAmount ?? props.goal.targetAmount,
}
dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}
}

return (


{/* Requirement: Only visible when there is no icon */}



Add icon

    {/* Requirement: Only visible when there is an icon */}
    <GoalIconContainer shouldShow={hasIcon()}>
      <GoalIcon icon={goal?.icon ?? icon} onClick={addIconOnClick} />
    </GoalIconContainer>

    <NameInput value={title ?? ''} onChange={updateTitleOnChange} placeholder="Goal Title" />
  </HeaderGroup>

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

  <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="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>
  </Group>
</GoalManagerContainer>

)
}

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

const Field = (props: FieldProps) => (


{props.name}

)

// --- Styled Components ---

const TransparentButton = styled.buttonbackground: transparent; border: none; outline: none; cursor: pointer; display: flex; align-items: center; padding: 0; color: inherit;

const AddIconButtonContainer = styled.divdisplay: ${(props) => (props.hasIcon ? 'none' : 'flex')}; align-items: center;

const AddIconButtonText = styled.spanmargin-left: 0.5rem; font-size: 1.4rem;

const GoalIconContainer = styled.divdisplay: ${(props) => (props.shouldShow ? 'flex' : 'none')};

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

const HeaderGroup = styled.divdisplay: flex; flex-direction: row; align-items: center; margin-bottom: 2rem; gap: 1.5rem; width: 100%;

const EmojiPickerContainer = styled.divdisplay: ${(props) => (props.isOpen ? 'flex' : 'none')}; position: absolute; top: 7rem; left: 0; z-index: 999;

const Group = styled.divdisplay: flex; flex-direction: row; width: 100%; margin-top: 1.25rem; margin-bottom: 1.25rem;

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

&::placeholder {
color: rgba(128, 128, 128, 0.5);
}
`

const FieldName = styled.h1font-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.h1font-size: 1.8rem; font-weight: bold;

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

const Value = styled.divmargin-left: 2rem;

Added components for adding and displaying icons in the Goal Manager.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant