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
17 changes: 9 additions & 8 deletions package-lock.json

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

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"@material-ui/core": "^4.12.4",
"@material-ui/pickers": "^3.3.10",
"@reduxjs/toolkit": "^1.5.1",
"@types/emoji-mart": "^3.0.5",
"@types/node": "^17.0.41",
"@types/react": "^16.9.0",
"@types/react-redux": "^7.1.7",
Expand Down Expand Up @@ -55,8 +54,8 @@
]
},
"devDependencies": {
"@types/emoji-mart": "^3.0.9",
"@types/emoji-mart": "^3.0.14",
"@types/react-dom": "^18.0.5",
"@types/styled-components": "^5.1.25"
}
}
}
9 changes: 4 additions & 5 deletions src/api/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@ export async function createGoal(): Promise<Goal | null> {
}
}

export async function updateGoal(goalId: string, updatedGoal: Goal): Promise<boolean> {
export async function updateGoal(id: string, goal: Goal): Promise<void> {
try {
await axios.put(`${API_ROOT}/api/Goal/${goalId}`, updatedGoal)
return true
await axios.put(`${API_ROOT}/api/Goal/${id}`, goal)
} catch (error: any) {
return false
console.error("Failed to update goal:", error)
}
}
}
1 change: 1 addition & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Goal {
accountId: string
transactionIds: string[]
tagIds: string[]
icon?: string; // <--- You are adding this line
}

export interface Tag {
Expand Down
84 changes: 69 additions & 15 deletions src/ui/features/goalmanager/GoalManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,38 @@ import { useAppDispatch, useAppSelector } from '../../../store/hooks'
import DatePicker from '../../components/DatePicker'
import { Theme } from '../../components/Theme'

// Import the Emoji Picker components
import { Picker } from 'emoji-mart'
// @ts-ignore - This prevents the TypeScript warning for the CSS file
import 'emoji-mart/css/emoji-mart.css'

type Props = { goal: Goal }

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

const goal = useAppSelector(selectGoalsMap)[props.goal.id]
const goalsMap = useAppSelector(selectGoalsMap)
const goal = goalsMap[props.goal.id]

const [name, setName] = useState<string | null>(null)
const [targetDate, setTargetDate] = useState<Date | null>(null)
const [targetAmount, setTargetAmount] = useState<number | null>(null)

// State to control if the emoji picker is visible
const [showEmojiPicker, setShowEmojiPicker] = useState(false)

useEffect(() => {
// Safety check in case the goal isn't loaded yet
if (!props.goal) return;
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])

useEffect(() => {
if (!goal) return;
setName(goal.name)
}, [goal.name])
}, [goal])

const updateNameOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const nextName = event.target.value
Expand Down Expand Up @@ -67,18 +75,47 @@ export function GoalManager(props: Props) {
const updatedGoal: Goal = {
...props.goal,
name: name ?? props.goal.name,
targetDate: date ?? props.goal.targetDate,
targetDate: date,
targetAmount: targetAmount ?? props.goal.targetAmount,
}
dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
}
}

// Function to handle when an emoji is clicked
const updateIcon = (emoji: any) => {
const updatedGoal: Goal = {
...props.goal,
name: name ?? props.goal.name,
targetDate: targetDate ?? props.goal.targetDate,
targetAmount: targetAmount ?? props.goal.targetAmount,
icon: emoji.native, // Save the selected emoji
}
dispatch(updateGoalRedux(updatedGoal))
updateGoalApi(props.goal.id, updatedGoal)
setShowEmojiPicker(false) // Close the picker after selection
}

// If goal data is missing, don't crash
if (!goal) return null;

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

{/* NEW SECTION: The Emoji Picker Button */}
<Group>
<EmojiButton onClick={() => setShowEmojiPicker(!showEmojiPicker)}>
{goal.icon ? goal.icon : '😀 Select Icon'}
</EmojiButton>
{showEmojiPicker && (
<div style={{ position: 'absolute', zIndex: 10, marginTop: '50px' }}>
<Picker onSelect={updateIcon} theme="dark" />
</div>
)}
</Group>

<Group>
<Field name="Target Date" icon={faCalendarAlt} />
<Value>
Expand Down Expand Up @@ -110,12 +147,24 @@ 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 }
// --- STYLED COMPONENTS ---

const EmojiButton = styled.button`
background-color: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
padding: 10px 15px;
border-radius: 8px;
font-size: 1.2rem;
cursor: pointer;
transition: background-color 0.2s;

&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
`

const Field = (props: FieldProps) => (
const Field = (props: { name: string; icon: IconDefinition }) => (
<FieldContainer>
<FontAwesomeIcon icon={props.icon} size="2x" />
<FieldName>{props.name}</FieldName>
Expand All @@ -138,7 +187,9 @@ const Group = styled.div`
width: 100%;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
position: relative;
`

const NameInput = styled.input`
display: flex;
background-color: transparent;
Expand All @@ -155,6 +206,7 @@ const FieldName = styled.h1`
color: rgba(174, 174, 174, 1);
font-weight: normal;
`

const FieldContainer = styled.div`
display: flex;
flex-direction: row;
Expand All @@ -165,10 +217,12 @@ const FieldContainer = styled.div`
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;
Expand All @@ -181,4 +235,4 @@ const StringInput = styled.input`

const Value = styled.div`
margin-left: 2rem;
`
`
20 changes: 18 additions & 2 deletions src/ui/pages/Main/goals/GoalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function GoalCard(props: Props) {

const goal = useAppSelector(selectGoalsMap)[props.id]

// THIS FIXES THE BLANK DASHBOARD CRASH:
// If the goal hasn't loaded yet, don't try to draw the card
if (!goal) return null;

const onClick = (event: React.MouseEvent) => {
event.stopPropagation()
dispatch(setContentRedux(goal))
Expand All @@ -27,6 +31,9 @@ export default function GoalCard(props: Props) {

return (
<Container key={goal.id} onClick={onClick}>
{/* THIS IS YOUR NEW ICON DISPLAY */}
<GoalIcon>{goal.icon ? goal.icon : '🎯'}</GoalIcon>

<TargetAmount>${goal.targetAmount}</TargetAmount>
<TargetDate>{asLocaleDateString(goal.targetDate)}</TargetDate>
</Container>
Expand All @@ -36,21 +43,30 @@ export default function GoalCard(props: Props) {
const Container = styled(Card)`
display: flex;
flex-direction: column;
justify-content: center;
min-height: 140px;
min-width: 140px;
width: 33%;
cursor: pointer;
margin-left: 2rem;
margin-right: 2rem;
border-radius: 2rem;

align-items: center;
`

// NEW STYLING FOR YOUR EMOJI
const GoalIcon = styled.div`
font-size: 3.5rem;
margin-bottom: 0.5rem;
`

const TargetAmount = styled.h2`
font-size: 2rem;
margin: 0;
`

const TargetDate = styled.h4`
color: rgba(174, 174, 174, 1);
font-size: 1rem;
`
margin-top: 0.5rem;
`