diff --git a/GoalControllerTests.cs b/GoalControllerTests.cs new file mode 100644 index 0000000..b14e8a0 --- /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++; + } + } +} diff --git a/put_request.txt b/put_request.txt new file mode 100644 index 0000000..910cce2 --- /dev/null +++ b/put_request.txt @@ -0,0 +1,12 @@ +import axios from 'axios' +import { Goal } from './types' +import { API_ROOT } from './lib' // Assuming API_ROOT is defined in lib.ts + +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 + } +} 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/features/goalmanager/GoalManager.tsx b/src/ui/features/goalmanager/GoalManager.tsx index 0779dda..df95ba8 100644 --- a/src/ui/features/goalmanager/GoalManager.tsx +++ b/src/ui/features/goalmanager/GoalManager.tsx @@ -1,8 +1,10 @@ import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons' -import { faDollarSign, IconDefinition } from '@fortawesome/free-solid-svg-icons' +import { faDollarSign, faPlus, 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 { Picker } from 'emoji-mart' +import 'emoji-mart/css/emoji-mart.css' import React, { useEffect, useState } from 'react' import styled from 'styled-components' import { updateGoal as updateGoalApi } from '../../../api/lib' @@ -21,16 +23,20 @@ export function GoalManager(props: Props) { const [name, setName] = useState(null) const [targetDate, setTargetDate] = useState(null) const [targetAmount, setTargetAmount] = useState(null) + const [icon, setIcon] = useState(null) + const [showPicker, setShowPicker] = useState(false) useEffect(() => { setName(props.goal.name) setTargetDate(props.goal.targetDate) setTargetAmount(props.goal.targetAmount) + setIcon(props.goal.icon ?? null) }, [ props.goal.id, props.goal.name, props.goal.targetDate, props.goal.targetAmount, + props.goal.icon, ]) useEffect(() => { @@ -43,6 +49,7 @@ export function GoalManager(props: Props) { const updatedGoal: Goal = { ...props.goal, name: nextName, + icon: icon ?? props.goal.icon, } dispatch(updateGoalRedux(updatedGoal)) updateGoalApi(props.goal.id, updatedGoal) @@ -56,6 +63,7 @@ export function GoalManager(props: Props) { name: name ?? props.goal.name, targetDate: targetDate ?? props.goal.targetDate, targetAmount: nextTargetAmount, + icon: icon ?? props.goal.icon, } dispatch(updateGoalRedux(updatedGoal)) updateGoalApi(props.goal.id, updatedGoal) @@ -69,14 +77,43 @@ export function GoalManager(props: Props) { name: name ?? props.goal.name, targetDate: date ?? props.goal.targetDate, targetAmount: targetAmount ?? props.goal.targetAmount, + icon: icon ?? props.goal.icon, } dispatch(updateGoalRedux(updatedGoal)) updateGoalApi(props.goal.id, updatedGoal) } } + const pickEmojiOnClick = () => (emoji: any, event?: any) => { + setIcon(emoji.native) + setShowPicker(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) + } + return ( + {icon ? ( + setShowPicker(!showPicker)}> + {icon} + + ) : ( + setShowPicker(!showPicker)}> + + + )} + + + + + @@ -139,6 +176,32 @@ const Group = styled.div` margin-top: 1.25rem; margin-bottom: 1.25rem; ` + +const AddIconButtonContainer = styled.div` + display: ${(props) => (props.shouldShow ? 'flex' : 'none')}; + width: 4rem; + height: 4rem; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.1); + align-items: center; + justify-content: center; + cursor: pointer; + margin-bottom: 1rem; +` + +const GoalIconContainer = styled.div` + display: ${(props) => (props.shouldShow ? 'flex' : 'none')}; + font-size: 4rem; + cursor: pointer; + margin-bottom: 1rem; +` + +const EmojiPickerContainer = styled.div` + display: ${(props) => (props.isOpen ? 'block' : 'none')}; + position: absolute; + top: ${(props) => (props.hasIcon ? '5rem' : '5rem')}; + z-index: 100; +` const NameInput = styled.input` display: flex; background-color: transparent; diff --git a/src/ui/pages/Main/goals/GoalCard.tsx b/src/ui/pages/Main/goals/GoalCard.tsx index e8f6d0a..fb2908e 100644 --- a/src/ui/pages/Main/goals/GoalCard.tsx +++ b/src/ui/pages/Main/goals/GoalCard.tsx @@ -27,6 +27,7 @@ export default function GoalCard(props: Props) { return ( + {goal.icon && {goal.icon}} ${goal.targetAmount} {asLocaleDateString(goal.targetDate)} @@ -54,3 +55,8 @@ const TargetDate = styled.h4` color: rgba(174, 174, 174, 1); font-size: 1rem; ` + +const Icon = styled.span` + font-size: 3rem; + margin-bottom: 0.5rem; +`