diff --git a/.gitignore b/.gitignore
index 4d29575..0636399 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,11 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+.vs/CommBank-Web/FileContentIndex/8d2c356b-6d55-4bc1-b87f-f946bbc7b78d.vsidx
+.vs/CommBank-Web/FileContentIndex/9249bc86-b3ba-4f44-b8e5-ec4717f093a5.vsidx
+.vs/CommBank-Web/FileContentIndex/9a5f6c10-30e5-4065-bf50-dc266c0ea273.vsidx
+.vs/CommBank-Web/FileContentIndex/b18f3d19-3f42-463c-a5e3-e9f8fcc8379e.vsidx
+.vs/CommBank-Web/FileContentIndex/561213b1-588c-465b-8922-a283db3d4612.vsidx
+.vs/CommBank-Web/FileContentIndex/ef1c54a5-c862-4eb4-81f7-7d1f36b54753.vsidx
+.vs/CommBank-Web/FileContentIndex/5d1ec056-5fd5-49a8-b029-33a18581789e.vsidx
+.vs/CommBank-Web/FileContentIndex/65c1dc03-737d-47f2-9a75-e28d0eb22ce4.vsidx
diff --git a/.vs/CommBank-Web/FileContentIndex/3e7f5092-41fa-4b2c-b07a-268730b5fca3.vsidx b/.vs/CommBank-Web/FileContentIndex/3e7f5092-41fa-4b2c-b07a-268730b5fca3.vsidx
new file mode 100644
index 0000000..37e0d36
Binary files /dev/null and b/.vs/CommBank-Web/FileContentIndex/3e7f5092-41fa-4b2c-b07a-268730b5fca3.vsidx differ
diff --git a/.vs/CommBank-Web/FileContentIndex/548d7493-f6f8-4f26-9905-9e87f41b003a.vsidx b/.vs/CommBank-Web/FileContentIndex/548d7493-f6f8-4f26-9905-9e87f41b003a.vsidx
new file mode 100644
index 0000000..dc52c04
Binary files /dev/null and b/.vs/CommBank-Web/FileContentIndex/548d7493-f6f8-4f26-9905-9e87f41b003a.vsidx differ
diff --git a/.vs/CommBank-Web/FileContentIndex/9a5f6c10-30e5-4065-bf50-dc266c0ea273.vsidx b/.vs/CommBank-Web/FileContentIndex/9a5f6c10-30e5-4065-bf50-dc266c0ea273.vsidx
new file mode 100644
index 0000000..a7d76f1
Binary files /dev/null and b/.vs/CommBank-Web/FileContentIndex/9a5f6c10-30e5-4065-bf50-dc266c0ea273.vsidx differ
diff --git a/.vs/CommBank-Web/FileContentIndex/c39b0b38-2d89-450c-9998-6842f79d01aa.vsidx b/.vs/CommBank-Web/FileContentIndex/c39b0b38-2d89-450c-9998-6842f79d01aa.vsidx
new file mode 100644
index 0000000..a7d76f1
Binary files /dev/null and b/.vs/CommBank-Web/FileContentIndex/c39b0b38-2d89-450c-9998-6842f79d01aa.vsidx differ
diff --git a/.vs/CommBank-Web/FileContentIndex/c3dd5793-81fd-4385-b601-ee2cfd68cde3.vsidx b/.vs/CommBank-Web/FileContentIndex/c3dd5793-81fd-4385-b601-ee2cfd68cde3.vsidx
new file mode 100644
index 0000000..7d45a12
Binary files /dev/null and b/.vs/CommBank-Web/FileContentIndex/c3dd5793-81fd-4385-b601-ee2cfd68cde3.vsidx differ
diff --git a/.vs/CommBank-Web/config/applicationhost.config b/.vs/CommBank-Web/config/applicationhost.config
new file mode 100644
index 0000000..269dc55
--- /dev/null
+++ b/.vs/CommBank-Web/config/applicationhost.config
@@ -0,0 +1,1021 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.vs/CommBank-Web/v17/.wsuo b/.vs/CommBank-Web/v17/.wsuo
new file mode 100644
index 0000000..0ee45a4
Binary files /dev/null and b/.vs/CommBank-Web/v17/.wsuo differ
diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json
new file mode 100644
index 0000000..f8b4888
--- /dev/null
+++ b/.vs/ProjectSettings.json
@@ -0,0 +1,3 @@
+{
+ "CurrentProjectSetting": null
+}
\ No newline at end of file
diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite
new file mode 100644
index 0000000..a451a10
Binary files /dev/null and b/.vs/slnx.sqlite differ
diff --git a/README.md b/README.md
index b7bff5b..54db531 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-# CommBank Goal Tracker
\ No newline at end of file
+# CommBank Goal Tracker
+#https://github.com/rezhang1128/CommBank-Web.git
\ No newline at end of file
diff --git a/Task3_link.html b/Task3_link.html
new file mode 100644
index 0000000..ee78af0
--- /dev/null
+++ b/Task3_link.html
@@ -0,0 +1,3 @@
+I don't know why I can't submit tsx file
+So I have to submit the link of the github to show my work
+https://github.com/rezhang1128/CommBank-Web.git
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 240aecf..94349f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6611,6 +6611,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/src/api/types.ts b/src/api/types.ts
index f75edad..7e3f015 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -26,7 +26,8 @@ export interface Goal {
created: Date
accountId: string
transactionIds: string[]
- tagIds: string[]
+ tagIds: string[]
+ icon: string | null
}
export interface Tag {
diff --git a/src/test/GoalControllerTests.cs b/src/test/GoalControllerTests.cs
new file mode 100644
index 0000000..4cda237
--- /dev/null
+++ b/src/test/GoalControllerTests.cs
@@ -0,0 +1,40 @@
+using System.Threading.Tasks;
+using Xunit;
+using Microsoft.AspNetCore.Http;
+
+public class GoalControllerTests
+{
+ private readonly FakeCollections collections;
+
+ public GoalControllerTests()
+ {
+ collections = new();
+ }
+
+ [Fact]
+ public async Task GetForUser()
+ {
+ // Arrange
+ var goals = collections.GetGoals();
+ var users = collections.GetUsers();
+ IGoalsService goalsService = new FakeGoalsService(goals, goals[0]);
+ IUsersService usersService = new FakeUsersService(users, users[0]);
+ GoalController controller = new(goalsService, usersService);
+
+ var httpContext = new DefaultHttpContext();
+ controller.ControllerContext.HttpContext = httpContext;
+
+ // Act
+ var result = await controller.GetForUser(goals[0].UserId!);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotEmpty(result); // Ensure goals exist
+
+ Assert.All(result!, goal =>
+ {
+ Assert.IsAssignableFrom(goal);
+ Assert.Equal(goals[0].UserId, goal.UserId);
+ });
+ }
+}
diff --git a/src/ui/features/goalmanager/GoalManager.tsx b/src/ui/features/goalmanager/GoalManager.tsx
index 0779dda..1c7aa56 100644
--- a/src/ui/features/goalmanager/GoalManager.tsx
+++ b/src/ui/features/goalmanager/GoalManager.tsx
@@ -1,4 +1,3 @@
-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'
@@ -11,16 +10,30 @@ 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 { BaseEmoji, Picker } from 'emoji-mart';
+import { selectMode } from '../../../store/themeSlice';
+import GoalIcon from './GoalIcon';
+import { TransparentButton } from '../../components/TransparentButton';
+import { faCalendarAlt, faSmile } from '@fortawesome/free-regular-svg-icons';
+
type Props = { goal: Goal }
+const EmojiPicker = ({ onClick }: { onClick: (emoji: BaseEmoji, event: React.MouseEvent) => void }) => {
+ const theme = useAppSelector(selectMode);
+ return (
+
+ );
+};
+
export function GoalManager(props: Props) {
const dispatch = useAppDispatch()
-
const goal = useAppSelector(selectGoalsMap)[props.goal.id]
-
const [name, setName] = useState(null)
const [targetDate, setTargetDate] = useState(null)
- const [targetAmount, setTargetAmount] = useState(null)
+ const [targetAmount, setTargetAmount] = useState(null)
+ const [icon, setIcon] = useState(props.goal.icon ?? null);
+const [emojiPickerIsOpen, setEmojiPickerIsOpen] = useState(false)
+
useEffect(() => {
setName(props.goal.name)
@@ -36,6 +49,27 @@ export function GoalManager(props: Props) {
useEffect(() => {
setName(goal.name)
}, [goal.name])
+ const hasIcon = () => icon != null;
+ const addIconOnClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ setEmojiPickerIsOpen(true);
+ };
+ 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);
+ };
const updateNameOnChange = (event: React.ChangeEvent) => {
const nextName = event.target.value
@@ -105,7 +139,20 @@ export function GoalManager(props: Props) {
{new Date(props.goal.created).toLocaleDateString()}
-
+
+
+
+
+
+
+
+
+ Add icon
+
+
+ e.stopPropagation()}>
+
+
)
}
@@ -122,6 +169,19 @@ const Field = (props: FieldProps) => (
)
+
+const GoalIconContainer = styled.div<{ shouldShow: boolean }>`
+ display: ${(props) => (props.shouldShow ? 'flex' : 'none')};
+`;
+
+const AddIconButtonContainer = styled.div<{ hasIcon: boolean }>`
+ display: ${(props) => (props.hasIcon ? 'none' : 'flex')};
+`;
+
+const AddIconButtonText = styled.span`
+ margin-left: 0.5rem;
+`;
+
const GoalManagerContainer = styled.div`
display: flex;
flex-direction: column;
@@ -182,3 +242,10 @@ const StringInput = styled.input`
const Value = styled.div`
margin-left: 2rem;
`
+
+const EmojiPickerContainer = styled.div`
+ display: ${(props) => (props.isOpen ? 'flex' : 'none')};
+ position: absolute;
+ top: ${(props) => (props.hasIcon ? '10rem' : '2rem')};
+ left: 0;
+`
diff --git a/src/ui/pages/Main/goals/GoalCard.tsx b/src/ui/pages/Main/goals/GoalCard.tsx
index e8f6d0a..03b7105 100644
--- a/src/ui/pages/Main/goals/GoalCard.tsx
+++ b/src/ui/pages/Main/goals/GoalCard.tsx
@@ -10,7 +10,9 @@ 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()
@@ -28,7 +30,8 @@ export default function GoalCard(props: Props) {
return (
${goal.targetAmount}
- {asLocaleDateString(goal.targetDate)}
+ {asLocaleDateString(goal.targetDate)}
+ {goal.icon}
)
}