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/server.js b/server.js new file mode 100644 index 0000000..7cd6592 --- /dev/null +++ b/server.js @@ -0,0 +1,32 @@ +const express = require('express') +const cors = require('cors') + +const app = express() +app.use(cors()) +app.use(express.json()) + +const goals = [ + { id: "1", name: "Buy Car", targetAmount: 5000, icon: "🚗" }, + { id: "2", name: "Trip", targetAmount: 2000, icon: "✈️" } +] + +// GET goals route +app.get("/goals", (req, res) => { + const { userId } = req.query + + if (userId) { + return res.json(goals) + } + + res.json(goals) +}) + +// export app for testing +module.exports = app + +// run server only when not testing +if (require.main === module) { + app.listen(3001, () => { + console.log("Server running on http://localhost:3001") + }) +} \ No newline at end of file diff --git a/src/api/types.ts b/src/api/types.ts index f75edad..91d94a7 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -20,6 +20,7 @@ export interface Application { export interface Goal { id: string name: string + icon: string; targetAmount: number balance: number targetDate: Date diff --git a/src/store/goalsSlice.ts b/src/store/goalsSlice.ts index 1ed0276..29396e0 100644 --- a/src/store/goalsSlice.ts +++ b/src/store/goalsSlice.ts @@ -28,10 +28,20 @@ export const goalsSlice = createSlice({ updateGoal: (state, action: PayloadAction) => { state.map[action.payload.id] = action.payload }, + + setGoals: (state, action: PayloadAction) => { + state.map = {} + state.list = [] + + action.payload.forEach((goal) => { + state.map[goal.id] = goal + state.list.push(goal.id) + }) + }, }, }) -export const { createGoal, updateGoal } = goalsSlice.actions +export const { createGoal, updateGoal, setGoals } = goalsSlice.actions export const selectGoalsMap = (state: RootState) => state.goals.map export const selectGoalsList = (state: RootState) => state.goals.list diff --git a/src/ui/features/#include .java b/src/ui/features/#include .java new file mode 100644 index 0000000..41b3527 --- /dev/null +++ b/src/ui/features/#include .java @@ -0,0 +1,34 @@ +#include +using namespace std ; + +class A { + public : + void showA(){ + cout <<"this is class A"<(null) + const [targetDate, setTargetDate] = useState(null) + const [targetAmount, setTargetAmount] = useState(null) + + useEffect(() => { + setName(props.goal.name) + setTargetDate(props.goal.targetDate) + setTargetAmount(props.goal.targetAmount) + setIcon(props.goal.icon || "🎯") + }, [ + props.goal.id, + props.goal.name, + props.goal.targetDate, + props.goal.targetAmount, + ]) + + useEffect(() => { + if (goal) { + setName(goal.name) + } + }, [goal]) + + const updateNameOnChange = (event: React.ChangeEvent) => { + const nextName = event.target.value + setName(nextName) + const updatedGoal: Goal = { + ...props.goal, + name: nextName, + icon: icon, + } + dispatch(updateGoalRedux(updatedGoal)) + updateGoalApi(props.goal.id, updatedGoal) + } + + const updateTargetAmountOnChange = (event: React.ChangeEvent) => { + const nextTargetAmount = parseFloat(event.target.value) || 0 + setTargetAmount(nextTargetAmount) + const updatedGoal: Goal = { + ...props.goal, + name: name ?? props.goal.name, + targetDate: targetDate ?? props.goal.targetDate, + targetAmount: nextTargetAmount, + icon: icon, + } + 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, + icon: icon, + } + dispatch(updateGoalRedux(updatedGoal)) + updateGoalApi(props.goal.id, updatedGoal) + } + } + + const pickEmojiOnClick = (emoji: any) => { + setIcon(emoji.native) + setShowPicker(false) + + const updatedGoal: Goal = { + ...props.goal, + name: name ?? props.goal.name, + targetDate: targetDate ?? props.goal.targetDate, + targetAmount: targetAmount ?? props.goal.targetAmount, + icon: emoji.native, + } + + dispatch(updateGoalRedux(updatedGoal)) + updateGoalApi(props.goal.id, updatedGoal) + } + + if (!goal) { + return
Loading...
+ } + + return ( + + + + + + + + {showPicker && ( + + )} + + + + + + + + + + + + + + + + + + + + + {props.goal.balance} + + + + + + + {new Date(props.goal.created).toLocaleDateString()} + + + + ) +} + +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 Container = 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; +` diff --git a/src/ui/features/goalmanager/AddIconButton.tsx b/src/ui/features/goalmanager/AddIconButton.tsx deleted file mode 100644 index d0c8c2c..0000000 --- a/src/ui/features/goalmanager/AddIconButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { faSmile } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import 'date-fns' -import React from 'react' -import styled from 'styled-components' -import { TransparentButton } from '../../components/TransparentButton' - -type Props = { hasIcon: boolean; onClick: (event: React.MouseEvent) => void } - -export default function AddIconButton(props: Props) { - if (props.hasIcon) return null - - return ( - - - - Add icon - - - ) -} - -const Container = styled.div` - flex-direction: row; - align-items: flex-end; -` -const Text = styled.span` - margin-left: 0.6rem; - font-size: 1.5rem; - color: rgba(174, 174, 174, 1); -` diff --git a/src/ui/features/goalmanager/GoalIcon.tsx b/src/ui/features/goalmanager/GoalIcon.tsx deleted file mode 100644 index b5a0d75..0000000 --- a/src/ui/features/goalmanager/GoalIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import 'date-fns' -import React from 'react' -import styled from 'styled-components' -import { TransparentButton } from '../../components/TransparentButton' - -type Props = { icon: string | null; onClick: (e: React.MouseEvent) => void } - -export default function GoalIcon(props: Props) { - return ( - - {props.icon} - - ) -} - -const Icon = styled.h1` - font-size: 6rem; - cursor: pointer; -` diff --git a/src/ui/features/goalmanager/GoalManager.tsx b/src/ui/features/goalmanager/GoalManager.tsx index 0779dda..95958ae 100644 --- a/src/ui/features/goalmanager/GoalManager.tsx +++ b/src/ui/features/goalmanager/GoalManager.tsx @@ -11,13 +11,16 @@ 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 { Picker } from 'emoji-mart' +import 'emoji-mart/css/emoji-mart.css' type Props = { goal: Goal } -export function GoalManager(props: Props) { +export function (props: Props) { const dispatch = useAppDispatch() const goal = useAppSelector(selectGoalsMap)[props.goal.id] - + const [showPicker, setShowPicker] = React.useState(false) +const [icon, setIcon] = React.useState(goal?.icon || "🎯") const [name, setName] = useState(null) const [targetDate, setTargetDate] = useState(null) const [targetAmount, setTargetAmount] = useState(null) @@ -26,6 +29,7 @@ export function GoalManager(props: Props) { setName(props.goal.name) setTargetDate(props.goal.targetDate) setTargetAmount(props.goal.targetAmount) + setIcon(props.goal.icon || "🎯") }, [ props.goal.id, props.goal.name, @@ -76,9 +80,35 @@ export function GoalManager(props: Props) { } return ( - + + + + + + {showPicker && ( + { + setIcon(emoji.native) + setShowPicker(false) + const updatedGoal: Goal = { + ...props.goal, + name: name ?? props.goal.name, + targetDate: targetDate ?? props.goal.targetDate, + targetAmount: targetAmount ?? props.goal.targetAmount, + icon: emoji.native, + } + dispatch(updateGoalRedux(updatedGoal)) + updateGoalApi(props.goal.id, updatedGoal) + }} + /> + )} + + + @@ -106,7 +136,7 @@ export function GoalManager(props: Props) { {new Date(props.goal.created).toLocaleDateString()} - + ) } @@ -122,7 +152,7 @@ const Field = (props: FieldProps) => ( ) -const GoalManagerContainer = styled.div` +const Container = styled.div` display: flex; flex-direction: column; justify-content: flex-start; diff --git a/src/ui/pages/Main/Main.tsx b/src/ui/pages/Main/Main.tsx index 714f091..fdc394c 100644 --- a/src/ui/pages/Main/Main.tsx +++ b/src/ui/pages/Main/Main.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import styled from 'styled-components' import Drawer from '../../surfaces/drawer/Drawer' import Navbar from '../../surfaces/navbar/Navbar' @@ -6,8 +6,22 @@ import { media } from '../../utils/media' import AccountsSection from './accounts/AccountsSection' import GoalsSection from './goals/GoalsSection' import TransactionsSection from './transactions/TransactionsSection' +import { useAppDispatch } from "../../../store/hooks" +import { setGoals } from "../../../store/goalsSlice" export default function Main() { + const dispatch = useAppDispatch() + +useEffect(() => { + fetch("http://localhost:3001/goals") + .then(res => res.json()) + .then(data => { + console.log("API DATA:", data) // 👈 ADD THIS + dispatch(setGoals(data)) + }) + .catch(err => console.error(err)) +}, [dispatch]) + return ( diff --git a/src/ui/pages/Main/goals/GoalCard.tsx b/src/ui/pages/Main/goals/GoalCard.tsx index e8f6d0a..a909974 100644 --- a/src/ui/pages/Main/goals/GoalCard.tsx +++ b/src/ui/pages/Main/goals/GoalCard.tsx @@ -14,7 +14,12 @@ type Props = { id: string } export default function GoalCard(props: Props) { const dispatch = useAppDispatch() - const goal = useAppSelector(selectGoalsMap)[props.id] + const goalsMap = useAppSelector(selectGoalsMap) + const goal = goalsMap?.[props.id] + + if (!goal) { + return
Loading...
+ } const onClick = (event: React.MouseEvent) => { event.stopPropagation() @@ -26,9 +31,12 @@ export default function GoalCard(props: Props) { const asLocaleDateString = (date: Date) => new Date(date).toLocaleDateString() return ( - - ${goal.targetAmount} - {asLocaleDateString(goal.targetDate)} + +

+ {goal.icon || "🎯"} {goal.name} +

+ ${goal.targetAmount ?? 0} + {goal.targetDate ? asLocaleDateString(goal.targetDate) : 'No date'}
) } diff --git a/src/ui/surfaces/modal/Modal.tsx b/src/ui/surfaces/modal/Modal.tsx index 32cd963..88b461b 100644 --- a/src/ui/surfaces/modal/Modal.tsx +++ b/src/ui/surfaces/modal/Modal.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { Goal } from '../../../api/types' import { useAppSelector } from '../../../store/hooks' import { selectContent, selectIsOpen, selectType } from '../../../store/modalSlice' -import { GoalManager } from '../../features/goalmanager/GoalManager' +import { } from '../../features//' export default function Modal() { const isOpen = useAppSelector(selectIsOpen) @@ -13,7 +13,7 @@ export default function Modal() { const renderSwitch = () => { switch (type) { case 'Goal': - return + return < goal={content as Goal} /> } }