From 9ad762b106f50615c3c32a227bbc16fd670e5ce6 Mon Sep 17 00:00:00 2001 From: Herman Liang Date: Sat, 19 Apr 2025 21:10:44 -0300 Subject: [PATCH 1/3] chore: update CI --- .github/workflows/CI.yml | 45 +--------------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ec3572e..403f659 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -45,7 +45,7 @@ jobs: cache: npm - run: npm ci - - name: Build Next.js for E2E tests + - name: Build Next.js for tests run: npm run build env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -68,48 +68,5 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Install Playwright (used for Storybook and E2E tests) - run: npx playwright install --with-deps - - name: Run storybook tests run: npm run test-storybook:ci - - - name: Run E2E tests - run: npx percy exec -- npm run test:e2e - env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} - CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} - - - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results - path: test-results/ - retention-days: 7 - - synchronize-with-crowdin: - name: GitHub PR synchronize with Crowdin - runs-on: ubuntu-latest - - needs: [build, test] - if: github.event_name == 'pull_request' - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} # Crowdin Actions needs to push commits to the PR branch, checkout HEAD commit instead of merge commit - fetch-depth: 0 - - - name: crowdin action - uses: crowdin/github-action@v2 - with: - upload_sources: true - upload_translations: true - download_translations: true - create_pull_request: false - localization_branch_name: ${{ github.head_ref || github.ref_name }} # explanation here: https://stackoverflow.com/a/71158878 - commit_message: 'chore: new Crowdin translations by GitHub Action' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} From 5b681be36106b36e185b246d0599f34fb5b1f677 Mon Sep 17 00:00:00 2001 From: Herman Liang Date: Sat, 19 Apr 2025 21:37:17 -0300 Subject: [PATCH 2/3] fix: wrong name when same amount --- src/app/[locale]/page.tsx | 161 +------------------------------------- 1 file changed, 2 insertions(+), 159 deletions(-) diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index 2e0d61f..fe7b23b 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,162 +1,5 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import { - Bar, - BarChart, - CartesianGrid, - Cell, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from 'recharts'; - -type Member = { - id: number; - first_name: string; - last_name: string | null; - balance: Array<{ - currency_code: string; - amount: string; - }>; -}; - -type Group = { - id: number; - name: string; - members: Member[]; -}; +import VisualSplitwise from '@/components/VisualSplitwise'; export default function Home() { - const [groups, setGroups] = useState([]); - const [selectedGroup, setSelectedGroup] = useState(null); - - const processChartData = (group: Group) => { - return group.members.map((member) => { - const balance = member.balance.find(b => b.currency_code === 'CAD'); - return { - name: member.first_name, - amount: balance ? Number.parseFloat(balance.amount) : 0, - }; - }); - }; - - const chartData = useMemo(() => { - if (!selectedGroup) { - return []; - } - return processChartData(selectedGroup); - }, [selectedGroup]); - - useEffect(() => { - const fetchData = async () => { - try { - const response = await fetch('/api/splitwise'); - const data = await response.json(); - setGroups(data.groups); - const firstGroup = data.groups.find((group: Group) => group.id !== 0); - if (firstGroup) { - setSelectedGroup(firstGroup); - } - } catch (error) { - console.error('Error fetching data:', error); - } - }; - - fetchData(); - }, []); - - const CustomTooltip = ({ active, payload, label }: any) => { - if (active && payload && payload.length) { - return ( -
-

{label}

-

- {payload[0].value > 0 ? 'To Receive' : 'To Pay'} - : $ - {Math.abs(payload[0].value).toFixed(2)} -

-
- ); - } - return null; - }; - - return ( -
- -

Visual Splitwise

- -
- -
- - {selectedGroup && ( -
-
- {chartData.length > 0 - ? ( - - - - - - } /> - { - const data = chartData.find(d => d.amount === value); - return data ? `${data.name} ($${Math.abs(value).toFixed(2)})` : ''; - }, - }} - > - {chartData.map((entry, index) => ( - = 0 ? '#4CAF50' : '#FF5252'} /> - ))} - - - - ) - : ( -
No data available
- )} -
-
- )} -
- ); + return ; } From d135bf30bb531adec9f42df8f51ff944daa805ac Mon Sep 17 00:00:00 2001 From: Herman Liang Date: Sat, 19 Apr 2025 22:01:52 -0300 Subject: [PATCH 3/3] feat: update to use daisy UI and refactor to make components in an individual page --- package-lock.json | 10 ++ package.json | 1 + src/app/[locale]/page.tsx | 63 +++++++++++- src/components/VisualSplitwise.tsx | 150 +++++++++++++++++++++++++++++ src/types/daisyui.d.ts | 1 + tailwind.config.ts | 3 +- 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 src/components/VisualSplitwise.tsx create mode 100644 src/types/daisyui.d.ts diff --git a/package-lock.json b/package-lock.json index 6d27cb5..569f470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@spotlightjs/spotlight": "^2.12.0", "@t3-oss/env-nextjs": "^0.12.0", "chart.js": "^4.4.9", + "daisyui": "^5.0.27", "drizzle-orm": "^0.41.0", "next": "^15.3.0", "next-intl": "^3.26.5", @@ -17147,6 +17148,15 @@ "node": ">=12" } }, + "node_modules/daisyui": { + "version": "5.0.27", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.27.tgz", + "integrity": "sha512-XrpqgfpGaZJvTPg9pS9Rq6xbYpmMnR0a7AKqyVPZceJzjAs5HH3rfkRkiuGin0+KC2Adnu+WLHU7UDxAtCMyAw==", + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", diff --git a/package.json b/package.json index d8247bc..f921d4d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@spotlightjs/spotlight": "^2.12.0", "@t3-oss/env-nextjs": "^0.12.0", "chart.js": "^4.4.9", + "daisyui": "^5.0.27", "drizzle-orm": "^0.41.0", "next": "^15.3.0", "next-intl": "^3.26.5", diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index fe7b23b..602d077 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,5 +1,66 @@ +'use client'; + import VisualSplitwise from '@/components/VisualSplitwise'; +import { useEffect, useState } from 'react'; + +type Member = { + id: number; + first_name: string; + last_name: string | null; + balance: Array<{ + currency_code: string; + amount: string; + }>; +}; + +type Group = { + id: number; + name: string; + members: Member[]; +}; export default function Home() { - return ; + const [groups, setGroups] = useState([]); + const [selectedGroup, setSelectedGroup] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch('/api/splitwise'); + const data = await response.json(); + setGroups(data.groups); + const firstGroup = data.groups.find((group: Group) => group.id !== 0); + if (firstGroup) { + setSelectedGroup(firstGroup); + } + } catch (error) { + console.error('Error fetching data:', error); + setError('Failed to fetch data'); + } finally { + setIsLoading(false); + } + }; + + fetchData(); + }, []); + + if (isLoading) { + return
Loading...
; + } + + if (error) { + return
{error}
; + } + + return ( +
+ setSelectedGroup(group)} + /> +
+ ); } diff --git a/src/components/VisualSplitwise.tsx b/src/components/VisualSplitwise.tsx new file mode 100644 index 0000000..0874ac8 --- /dev/null +++ b/src/components/VisualSplitwise.tsx @@ -0,0 +1,150 @@ +'use client'; + +import { useMemo } from 'react'; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; + +type Member = { + id: number; + first_name: string; + last_name: string | null; + balance: Array<{ + currency_code: string; + amount: string; + }>; +}; + +type Group = { + id: number; + name: string; + members: Member[]; +}; + +type VisualSplitwiseProps = { + groups: Group[]; + selectedGroup: Group | null; + onGroupChange: (group: Group) => void; +}; + +export default function VisualSplitwise({ groups, selectedGroup, onGroupChange }: VisualSplitwiseProps) { + const processChartData = (group: Group) => { + return group.members.map((member) => { + const balance = member.balance.find(b => b.currency_code === 'CAD'); + return { + id: member.id, + name: member.first_name, + amount: balance ? Number.parseFloat(balance.amount) : 0, + }; + }); + }; + + const chartData = useMemo(() => { + if (!selectedGroup) { + return []; + } + return processChartData(selectedGroup); + }, [selectedGroup]); + + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+

+ {payload[0].value > 0 ? 'To Receive' : 'To Pay'} + : $ + {Math.abs(payload[0].value).toFixed(2)} +

+
+ ); + } + return null; + }; + + return ( +
+

Visual Splitwise

+ +
+ +
+ + {selectedGroup && ( +
+
+ {chartData.length > 0 + ? ( + + + + + + } /> + + {chartData.map((entry: any) => ( + = 0 ? '#4CAF50' : '#FF5252'} /> + ))} + + + + ) + : ( +
No data available
+ )} +
+
+ )} +
+ ); +} diff --git a/src/types/daisyui.d.ts b/src/types/daisyui.d.ts new file mode 100644 index 0000000..39b5c70 --- /dev/null +++ b/src/types/daisyui.d.ts @@ -0,0 +1 @@ +declare module 'daisyui'; diff --git a/tailwind.config.ts b/tailwind.config.ts index 83ee47b..d7a08a5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,5 @@ import type { Config } from 'tailwindcss'; +import daisyui from 'daisyui'; const config: Config = { content: [ @@ -19,7 +20,7 @@ const config: Config = { }, }, }, - plugins: [], + plugins: [daisyui], }; export default config;