From c668fb28156c6dcc68e74305728cb57bf5a107e3 Mon Sep 17 00:00:00 2001
From: Richard Fisher <39341551+richfish85@users.noreply.github.com>
Date: Thu, 31 Jul 2025 01:52:44 +1000
Subject: [PATCH 1/2] Task 2 FrontEnd Modification - Job Sim
---
package-lock.json | 2 +
package.json | 15 +++-
src/api/types.ts | 1 +
src/ui/components/EmojiPicker.tsx | 8 +-
src/ui/features/goalmanager/GoalIcon.tsx | 12 +--
src/ui/features/goalmanager/GoalManager.tsx | 95 ++++++++++++++++++++-
src/ui/pages/Main/goals/GoalCard.tsx | 3 +
7 files changed, 122 insertions(+), 14 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 240aecf..6d63931 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"": {
"name": "commbank-web",
"version": "0.1.0",
+ "license": "ISC",
"dependencies": {
"@date-io/date-fns": "^1.3.13",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
@@ -6611,6 +6612,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.1.tgz",
"integrity": "sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==",
+ "license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.6.0"
diff --git a/package.json b/package.json
index 87958ce..4dd0e4b 100644
--- a/package.json
+++ b/package.json
@@ -58,5 +58,18 @@
"@types/emoji-mart": "^3.0.9",
"@types/react-dom": "^18.0.5",
"@types/styled-components": "^5.1.25"
+ },
+ "description": "",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/richfish85/CommBank-Web.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "commonjs",
+ "bugs": {
+ "url": "https://github.com/richfish85/CommBank-Web/issues"
}
-}
\ No newline at end of file
+}
diff --git a/src/api/types.ts b/src/api/types.ts
index f75edad..8cbabc0 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -27,6 +27,7 @@ export interface Goal {
accountId: string
transactionIds: string[]
tagIds: string[]
+ icon: string | null
}
export interface Tag {
diff --git a/src/ui/components/EmojiPicker.tsx b/src/ui/components/EmojiPicker.tsx
index 00bb54d..4b0d972 100644
--- a/src/ui/components/EmojiPicker.tsx
+++ b/src/ui/components/EmojiPicker.tsx
@@ -3,17 +3,17 @@ import 'emoji-mart/css/emoji-mart.css'
import { useAppSelector } from '../../store/hooks'
import { selectMode } from '../../store/themeSlice'
-type Props = { onClick: (emoji: BaseEmoji, event: React.MouseEvent) => void }
+type Props = { onClick: (emoji: BaseEmoji, event: React.MouseEvent) => void; }
-export default function EmojiPicker(props: Props) {
- const theme = useAppSelector(selectMode)
+export default function EmojiPicker({ onClick }: Props) {
+ const theme = useAppSelector(selectMode);
return (
)
diff --git a/src/ui/features/goalmanager/GoalIcon.tsx b/src/ui/features/goalmanager/GoalIcon.tsx
index b5a0d75..bb69983 100644
--- a/src/ui/features/goalmanager/GoalIcon.tsx
+++ b/src/ui/features/goalmanager/GoalIcon.tsx
@@ -5,15 +5,15 @@ import { TransparentButton } from '../../components/TransparentButton'
type Props = { icon: string | null; onClick: (e: React.MouseEvent) => void }
+const Icon = styled.h1`
+ font-size: 6rem;
+ cursor: pointer;
+`
+
export default function GoalIcon(props: Props) {
return (
{props.icon}
)
-}
-
-const Icon = styled.h1`
- font-size: 6rem;
- cursor: pointer;
-`
+}
\ No newline at end of file
diff --git a/src/ui/features/goalmanager/GoalManager.tsx b/src/ui/features/goalmanager/GoalManager.tsx
index 0779dda..32b9e98 100644
--- a/src/ui/features/goalmanager/GoalManager.tsx
+++ b/src/ui/features/goalmanager/GoalManager.tsx
@@ -1,3 +1,4 @@
+import { BaseEmoji, Picker } from 'emoji-mart'
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons'
import { faDollarSign, IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@@ -11,17 +12,55 @@ import { selectGoalsMap, updateGoal as updateGoalRedux } from '../../../store/go
import { useAppDispatch, useAppSelector } from '../../../store/hooks'
import DatePicker from '../../components/DatePicker'
import { Theme } from '../../components/Theme'
+import EmojiPicker from '../../components/EmojiPicker'
+import GoalIcon from './GoalIcon'
+import { TransparentButton } from '../../components/TransparentButton'
+import { faSmile } from '@fortawesome/free-regular-svg-icons'
+type GoalIconContainerProps = { shouldShow: boolean }
+
+const GoalIconContainer = styled.div`
+ display: ${(props) => (props.shouldShow ? 'flex' : 'none')};
+`
type Props = { goal: Goal }
export function GoalManager(props: Props) {
const dispatch = useAppDispatch()
- const goal = useAppSelector(selectGoalsMap)[props.goal.id]
-
+ const [emojiPickerIsOpen, setEmojiPickerIsOpen] = useState(false)
+ const [icon, setIcon] = useState(null)
+ // const goal = useAppSelector(selectGoalsMap)[props.goal.id]
const [name, setName] = useState(null)
const [targetDate, setTargetDate] = useState(null)
const [targetAmount, setTargetAmount] = useState(null)
+ const hasIcon = () => icon != null
+
+ const goal = useAppSelector(selectGoalsMap)[props.goal.id]
+
+ const addIconOnClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ setEmojiPickerIsOpen(!emojiPickerIsOpen);
+ }
+ 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));
+ }
+ useEffect(() => {
+ setIcon(props.goal.icon)
+ }, [props.goal.id, props.goal.icon])
+
useEffect(() => {
setName(props.goal.name)
setTargetDate(props.goal.targetDate)
@@ -79,6 +118,25 @@ export function GoalManager(props: Props) {
+
+
+
+
+ event.stopPropagation()}
+ >
+
+
+
+
+
+
+ Add icon
+
+
+
@@ -112,9 +170,15 @@ 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 }
+const EmojiPickerContainer = styled.div`
+ display: ${(props) => (props.isOpen ? 'flex' : 'none')};
+ position: absolute;
+ top: ${(props) => (props.hasIcon ? '10rem' : '2rem')};
+ left: 0;
+ z-index: 100; // ensure it overlays cleanly
+`
const Field = (props: FieldProps) => (
@@ -182,3 +246,28 @@ const StringInput = styled.input`
const Value = styled.div`
margin-left: 2rem;
`
+// -------------------------------------------------------------------------------
+const AddIconButtonContainer = styled.div`
+ display: ${(p) => (p.shouldShow ? 'flex' : 'none')};
+ flex-direction: column;
+ align-items: center;
+ margin-top: 1rem;
+`;
+
+// const TransparentButton = styled.button`
+// background: transparent;
+// border: none;
+// cursor: pointer;
+// display: flex;
+// flex-direction: column;
+// align-items: center;
+// color: ${({ theme }: { theme: Theme }) => theme.text};
+// `;
+
+const AddIconButtonText = styled.span`
+ margin-top: 0.25rem;
+ font-size: 0.85rem;
+ font-weight: 500;
+`;
+// -------------------------------------------------------------------------------
+;
\ No newline at end of file
diff --git a/src/ui/pages/Main/goals/GoalCard.tsx b/src/ui/pages/Main/goals/GoalCard.tsx
index e8f6d0a..bff7f00 100644
--- a/src/ui/pages/Main/goals/GoalCard.tsx
+++ b/src/ui/pages/Main/goals/GoalCard.tsx
@@ -10,6 +10,8 @@ import {
import { Card } from '../../../components/Card'
type Props = { id: string }
+ const Icon = styled.h1`
+ font-size: 5.5rem;`
export default function GoalCard(props: Props) {
const dispatch = useAppDispatch()
@@ -29,6 +31,7 @@ export default function GoalCard(props: Props) {
${goal.targetAmount}
{asLocaleDateString(goal.targetDate)}
+ {goal.icon}
)
}
From 67bf55b14da4b78489bcb04d3ffeee6d60491f37 Mon Sep 17 00:00:00 2001
From: Richard Fisher <39341551+richfish85@users.noreply.github.com>
Date: Thu, 31 Jul 2025 19:11:54 +1000
Subject: [PATCH 2/2] feat emoji picker, Redux + PUT integration
---
src/api/lib.ts | 18 ++++++++++++------
src/ui/features/goalmanager/GoalManager.tsx | 13 +++++++++++--
2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/src/api/lib.ts b/src/api/lib.ts
index 3c593ca..5d80c0c 100644
--- a/src/api/lib.ts
+++ b/src/api/lib.ts
@@ -2,7 +2,9 @@ import axios from 'axios'
import { user } from '../data/user'
import { Goal, Transaction, User } from './types'
-export const API_ROOT = 'https://fencer-commbank.azurewebsites.net'
+// export const API_ROOT = 'https://fencer-commbank.azurewebsites.net'
+export const API_ROOT =
+ process.env.REACT_APP_API_ROOT ?? 'http://localhost:5203';
export async function getUser(): Promise {
try {
@@ -43,11 +45,15 @@ export async function createGoal(): Promise {
}
}
-export async function updateGoal(goalId: string, updatedGoal: Goal): Promise {
+export async function updateGoal(
+ goalId: string,
+ updatedGoal: Goal
+): Promise {
try {
- await axios.put(`${API_ROOT}/api/Goal/${goalId}`, updatedGoal)
- return true
- } catch (error: any) {
- return false
+ await axios.put(`${API_ROOT}/api/Goal/${goalId}`, updatedGoal);
+ return true;
+ } catch (e) {
+ console.error('PUT /api/Goal failed', e);
+ return false;
}
}
diff --git a/src/ui/features/goalmanager/GoalManager.tsx b/src/ui/features/goalmanager/GoalManager.tsx
index 32b9e98..611c074 100644
--- a/src/ui/features/goalmanager/GoalManager.tsx
+++ b/src/ui/features/goalmanager/GoalManager.tsx
@@ -41,8 +41,8 @@ export function GoalManager(props: Props) {
event.stopPropagation();
setEmojiPickerIsOpen(!emojiPickerIsOpen);
}
- const pickEmojiOnClick = (emoji: BaseEmoji, event: React.MouseEvent) => {
- event.stopPropagation();
+const pickEmojiOnClick = async (emoji: BaseEmoji, event: React.MouseEvent) => {
+ event.stopPropagation();
setIcon(emoji.native);
setEmojiPickerIsOpen(false);
@@ -56,7 +56,16 @@ export function GoalManager(props: Props) {
};
dispatch(updateGoalRedux(updatedGoal));
+
+ const ok = await updateGoalApi(props.goal.id, updatedGoal);
+ if (!ok) {
+ console.error('Failed to update goal with new icon');
+ dispatch(updateGoalRedux(props.goal)); // revert to original goal if update fails
+ setIcon(props.goal.icon ?? null); // reset icon to original
+ setEmojiPickerIsOpen(false); // close emoji picker
+ }
}
+
useEffect(() => {
setIcon(props.goal.icon)
}, [props.goal.id, props.goal.icon])