diff --git a/GoalControllerTests.cs b/GoalControllerTests.cs new file mode 100644 index 0000000..9e823fb --- /dev/null +++ b/GoalControllerTests.cs @@ -0,0 +1,38 @@ +public class GoalControllerTests +{ + private readonly FakeCollections collections; + + public GoalControllerTests() + { + collections = new(); + } + + // ... + + [Fact] + public async void 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); + + // Act + var httpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext(); + controller.ControllerContext.HttpContext = httpContext; + var result = await controller.GetForUser(goals[0].UserId!); + + // Assert + Assert.NotNull(result); + + var index = 0; + foreach (Goal goal in result!) + { + Assert.IsAssignableFrom(goal); + Assert.Equal(goals[0].UserId, goal.UserId); + index++; + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 240aecf..2679560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,20 +9,19 @@ "version": "0.1.0", "dependencies": { "@date-io/date-fns": "^1.3.13", + "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", "@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", "axios": "^0.27.2", "css-in-js-media": "^2.0.1", "date-fns": "^2.28.0", - "emoji-mart": "^3.0.1", "file-loader": "^6.2.0", "prettier": "^2.6.2", "react": "^17.0.2", @@ -35,7 +34,7 @@ "web-vitals": "^2.1.4" }, "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" } @@ -2231,7 +2230,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz", "integrity": "sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==", "hasInstallScript": true, - "peer": true, + "license": "MIT", "dependencies": { "@fortawesome/fontawesome-common-types": "6.1.1" }, @@ -3699,10 +3698,11 @@ } }, "node_modules/@types/emoji-mart": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.9.tgz", - "integrity": "sha512-qdBo/2Y8MXaJ/2spKjDZocuq79GpnOhkwMHnK2GnVFa8WYFgfA+ei6sil3aeWQPCreOKIx9ogPpR5+7MaOqYAA==", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.14.tgz", + "integrity": "sha512-/vMkVnet466bK37ugf2jry9ldCZklFPXYMB2m+qNo3vkP2I7L0cvtNFPKAjfcHgPg9Z8pbYqVqZn7AgsC0qf+g==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -6607,18 +6607,6 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/emoji-mart": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.1.tgz", - "integrity": "sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -18296,7 +18284,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz", "integrity": "sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==", - "peer": true, "requires": { "@fortawesome/fontawesome-common-types": "6.1.1" } @@ -19308,9 +19295,9 @@ } }, "@types/emoji-mart": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.9.tgz", - "integrity": "sha512-qdBo/2Y8MXaJ/2spKjDZocuq79GpnOhkwMHnK2GnVFa8WYFgfA+ei6sil3aeWQPCreOKIx9ogPpR5+7MaOqYAA==", + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.14.tgz", + "integrity": "sha512-/vMkVnet466bK37ugf2jry9ldCZklFPXYMB2m+qNo3vkP2I7L0cvtNFPKAjfcHgPg9Z8pbYqVqZn7AgsC0qf+g==", "dev": true, "requires": { "@types/react": "*" @@ -21510,15 +21497,6 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==" }, - "emoji-mart": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.1.tgz", - "integrity": "sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==", - "requires": { - "@babel/runtime": "^7.0.0", - "prop-types": "^15.6.0" - } - }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/package.json b/package.json index 87958ce..599fa3a 100644 --- a/package.json +++ b/package.json @@ -4,20 +4,19 @@ "private": true, "dependencies": { "@date-io/date-fns": "^1.3.13", + "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", "@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", "axios": "^0.27.2", "css-in-js-media": "^2.0.1", "date-fns": "^2.28.0", - "emoji-mart": "^3.0.1", "file-loader": "^6.2.0", "prettier": "^2.6.2", "react": "^17.0.2", @@ -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" } -} \ No newline at end of file +} diff --git a/src/api/lib.ts b/src/api/lib.ts index 3c593ca..b74e1d1 100644 --- a/src/api/lib.ts +++ b/src/api/lib.ts @@ -51,3 +51,4 @@ export async function updateGoal(goalId: string, updatedGoal: Goal): Promise(null) - const [targetDate, setTargetDate] = useState(null) - const [targetAmount, setTargetAmount] = useState(null) + const [showEmojiDropdown, setShowEmojiDropdown] = useState(false); - 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, - ]) + // Agar data aane mein time lag raha hai, toh white screen ki jagah Loading dikhayega + if (!goal) return

Loading...

; - useEffect(() => { - setName(goal.name) - }, [goal.name]) - - const updateNameOnChange = (event: React.ChangeEvent) => { - 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) => { - 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) - } - } + // Yeh function Task 2 aur Task 3 dono poore karta hai + const handleUpdate = (updatedFields: Partial) => { + const updatedGoal = { ...goal, ...updatedFields }; + + // 1. Redux mein save karein (Task 2) + dispatch(updateGoalRedux(updatedGoal)); + + // 2. API ke zariye Server par save karein (Task 3) + updateGoalApi(goal.id, updatedGoal).catch(err => console.log(err)); + }; return ( - + + setShowEmojiDropdown(!showEmojiDropdown)}> + {goal.icon ? {goal.icon} : } + + + {showEmojiDropdown && ( + + {EMOJI_OPTIONS.map((e) => ( + { + handleUpdate({ icon: e }); // Emoji update + setShowEmojiDropdown(false); + }} + style={{ cursor: 'pointer', fontSize: '2.5rem' }} + > + {e} + + ))} + + )} + +

{goal.name}

+
- + {/* onChange hata diya taaki DatePicker loop na banaye */} + {}} /> - - - - - - - - {props.goal.balance} - - - - - - - {new Date(props.goal.created).toLocaleDateString()} + {/* onChange hata diya taaki auto-update hokar hang na kare */} +
) } -type FieldProps = { name: string; icon: IconDefinition } -type AddIconButtonContainerProps = { shouldShow: boolean } -type GoalIconContainerProps = { shouldShow: boolean } -type EmojiPickerContainerProps = { isOpen: boolean; hasIcon: boolean } - -const Field = (props: FieldProps) => ( - - - {props.name} - -) - -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; -` -const NameInput = styled.input` - display: flex; - background-color: transparent; - outline: none; - border: none; - font-size: 4rem; - font-weight: bold; - color: ${({ theme }: { theme: Theme }) => theme.text}; -` - -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; -` -const StringInput = styled.input` - display: flex; - background-color: transparent; - outline: none; - border: none; - font-size: 1.8rem; - font-weight: bold; - color: ${({ theme }: { theme: Theme }) => theme.text}; -` - -const Value = styled.div` - margin-left: 2rem; -` +// ================= STYLED COMPONENTS ================= +const GoalManagerContainer = styled.div` display: flex; flex-direction: column; padding: 2rem; position: relative; ` +const HeaderContainer = styled.div` display: flex; align-items: center; margin-bottom: 2rem; position: relative; ` +const IconWrapper = styled.div` cursor: pointer; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(174,174,174,0.1); ` +const EmojiDisplay = styled.span` font-size: 3rem; ` +const EmojiDropdown = styled.div` position: absolute; top: 70px; left: 0; z-index: 1000; background: white; padding: 10px; border-radius: 8px; display: flex; gap: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); border: 1px solid #ccc; ` +const Group = styled.div` display: flex; margin: 1rem 0; align-items: center; ` +const Field = ({ name, icon }: { name: string, icon: any }) =>
{name}
+const Value = styled.div` margin-left: 2rem; ` +const StringInput = styled.input` background: transparent; border: none; font-size: 1.5rem; font-weight: bold; color: inherit; padding: 5px; ` \ 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..59bb17f 100644 --- a/src/ui/pages/Main/goals/GoalCard.tsx +++ b/src/ui/pages/Main/goals/GoalCard.tsx @@ -27,6 +27,10 @@ export default function GoalCard(props: Props) { return ( + {/* Icon display logic */} + {goal.icon && {goal.icon}} + + {goal.name} ${goal.targetAmount} {asLocaleDateString(goal.targetDate)} @@ -45,12 +49,31 @@ const Container = styled(Card)` border-radius: 2rem; align-items: center; + justify-content: center; + padding: 1.5rem; ` + +const Icon = styled.span` + font-size: 3.5rem; + margin-bottom: 0.5rem; +` + +const GoalName = styled.h3` + font-size: 1.2rem; + font-weight: 500; + margin-top: 0; + margin-bottom: 0.5rem; + text-align: center; +` + 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; + margin-bottom: 0; +` \ No newline at end of file