From f6461cb62cd8115d58e88d0532d38595dd71c34f Mon Sep 17 00:00:00 2001 From: Naytitorn Chaovirachot Date: Sun, 20 Oct 2024 13:03:41 +0700 Subject: [PATCH 01/59] link graph overview to database: --- src/app/api/dealApi.ts | 55 +++++++++++++++++++-------- src/app/dashboard/hook.ts | 19 +++++++++- src/app/dashboard/page.tsx | 5 ++- src/components/ui/overview.tsx | 68 ++++++---------------------------- 4 files changed, 73 insertions(+), 74 deletions(-) diff --git a/src/app/api/dealApi.ts b/src/app/api/dealApi.ts index 31b2094..5c7c948 100644 --- a/src/app/api/dealApi.ts +++ b/src/app/api/dealApi.ts @@ -25,20 +25,45 @@ export async function getDealList() { .eq('user_id', await getCurrentUserID()) .single(); - if (error || !dealData) { - alert(JSON.stringify(error)); - console.error('Error fetching deal list:', error); - } else { - const dealList = dealData.project[0].investment_deal; + // Handle errors and no data cases + if (error) { + alert(JSON.stringify(error)); + console.error('Error fetching deal list:', error); + return; // Exit on error + } - if (!dealList.length) { - alert("No data available"); - return; // Exit early if there's no data - } + if (!dealData || !dealData.project.length) { + alert("No project available"); + return; // Exit if there's no data + } - // Sort the dealList by created_time in descending order - const byCreatedTimeDesc = (a: Deal, b: Deal) => - new Date(b.created_time).getTime() - new Date(a.created_time).getTime(); - return dealList.sort(byCreatedTimeDesc); - } -}; \ No newline at end of file + const dealList = dealData.project[0].investment_deal; + + // Check for empty dealList + if (!dealList.length) { + alert("No deal list available"); + return; // Exit if there's no data + } + + // Sort the dealList by created_time in descending order + const byCreatedTimeDesc = (a: Deal, b: Deal) => + new Date(b.created_time).getTime() - new Date(a.created_time).getTime(); + return dealList.sort(byCreatedTimeDesc); +}; + +// #TODO move to util +export function convertToGraphData(deals: Deal[]): Record { + let graphData = deals.reduce((acc, deal) => { + const monthYear = new Date(deal.created_time).toISOString().slice(0, 7); // E.g., '2024-10' + acc[monthYear] = (acc[monthYear] || 0) + deal.deal_amount; // Sum the deal_amount + return acc; + }, {} as Record); // Change type to Record + + // Sort keys in ascending order + const sortedKeys = Object.keys(graphData).sort((a, b) => (a > b ? 1 : -1)); + + // Create a sorted graph data object + const sortedGraphData: Record = {}; + sortedKeys.forEach((key) => {sortedGraphData[key] = graphData[key]}); + return sortedGraphData; +} \ No newline at end of file diff --git a/src/app/dashboard/hook.ts b/src/app/dashboard/hook.ts index a220dc0..70166fc 100644 --- a/src/app/dashboard/hook.ts +++ b/src/app/dashboard/hook.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Deal, getDealList } from "../api/dealApi"; +import { Deal, getDealList, convertToGraphData } from "../api/dealApi"; // custom hook for deal list export function useDealList() { @@ -14,4 +14,21 @@ export function useDealList() { }, []); return dealList; +} + +export function useGraphData() { + const [graphData, setGraphData] = useState>({}); + + const fetchGraphData = async () => { + const dealList = await getDealList(); + if (dealList) { + setGraphData(convertToGraphData(dealList)); + } + } + + useEffect(() => { + fetchGraphData(); + }, []); + + return graphData; } \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 3d2840f..5d7cd2e 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -12,10 +12,11 @@ import { Overview } from "@/components/ui/overview"; import { RecentFunds } from "@/components/recent-funds"; import { useState } from "react"; -import { useDealList } from "./hook"; +import { useDealList, useGraphData } from "./hook"; export default function Dashboard() { const [graphType, setGraphType] = useState("line"); + const graphData = useGraphData(); const dealList = useDealList(); const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0; @@ -166,7 +167,7 @@ export default function Dashboard() { Overview - + {/* tab to switch between line and bar graph */} ; // Object with month-year as keys and sum as value } export function Overview(props: OverViewProps) { + // Transform the grouped data into the format for the chart + const chartData = Object.entries(props.graphData).map(([monthYear, totalArray]) => ({ + name: monthYear, + total: totalArray, // Get the total amount for the month + })); + return ( {props.graphType === 'line' ? ( - + ) : ( - + )} - ); + ); } From f65fc2a6964196c0468c8d9126972f2fbf21be43 Mon Sep 17 00:00:00 2001 From: Naytitorn Chaovirachot Date: Mon, 21 Oct 2024 14:52:54 +0700 Subject: [PATCH 02/59] connect recent funded people to database --- src/app/api/dealApi.ts | 51 +++++++++++++++++++++------ src/app/dashboard/hook.ts | 21 +++++++++-- src/app/dashboard/page.tsx | 5 +-- src/components/recent-funds.tsx | 62 +++++++++------------------------ src/hooks/useDealList.ts | 0 5 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 src/hooks/useDealList.ts diff --git a/src/app/api/dealApi.ts b/src/app/api/dealApi.ts index 5c7c948..d4d88af 100644 --- a/src/app/api/dealApi.ts +++ b/src/app/api/dealApi.ts @@ -7,14 +7,17 @@ export type Deal = { investor_id: string; }; +// Sort the dealList by created_time in descending order +export function byCreatedTimeDesc(a: Deal, b: Deal) { + return new Date(b.created_time).getTime() - new Date(a.created_time).getTime(); +} + export async function getDealList() { const supabase = createSupabaseClient(); const { data: dealData, error } = await supabase .from('business') .select(` - id, project ( - id, investment_deal ( deal_amount, created_time, @@ -25,7 +28,6 @@ export async function getDealList() { .eq('user_id', await getCurrentUserID()) .single(); - // Handle errors and no data cases if (error) { alert(JSON.stringify(error)); console.error('Error fetching deal list:', error); @@ -37,22 +39,51 @@ export async function getDealList() { return; // Exit if there's no data } - const dealList = dealData.project[0].investment_deal; + const flattenedDeals = dealData.project.flatMap((proj) => + proj.investment_deal.map((deal) => ({ + deal_amount: deal.deal_amount, + created_time: deal.created_time, + investor_id: deal.investor_id, + })) + ) // Check for empty dealList - if (!dealList.length) { + if (!flattenedDeals.length) { alert("No deal list available"); return; // Exit if there's no data } - - // Sort the dealList by created_time in descending order - const byCreatedTimeDesc = (a: Deal, b: Deal) => - new Date(b.created_time).getTime() - new Date(a.created_time).getTime(); - return dealList.sort(byCreatedTimeDesc); + return flattenedDeals.sort(byCreatedTimeDesc); }; +export async function getRecentDealData() { + const supabase = createSupabaseClient(); + let dealList = await getDealList(); + + if (!dealList) { + // #TODO div no deals available? + return; + } + + dealList = dealList.slice(0, 5) + + const investorIdList: string[] = dealList.map(deal => deal.investor_id); + const { data: userData, error } = await supabase + .from("profiles") + .select("username, avatar_url") // #TODO add email + .in("id", investorIdList); // Filter by investor_id + + if (error) { + // Handle the error and return a meaningful message + console.error("Error fetching usernames and avatars:", error); + } + + alert(JSON.stringify(userData)); + return userData || []; +} + // #TODO move to util export function convertToGraphData(deals: Deal[]): Record { + // group by year & month let graphData = deals.reduce((acc, deal) => { const monthYear = new Date(deal.created_time).toISOString().slice(0, 7); // E.g., '2024-10' acc[monthYear] = (acc[monthYear] || 0) + deal.deal_amount; // Sum the deal_amount diff --git a/src/app/dashboard/hook.ts b/src/app/dashboard/hook.ts index 70166fc..82b3793 100644 --- a/src/app/dashboard/hook.ts +++ b/src/app/dashboard/hook.ts @@ -1,5 +1,8 @@ import { useEffect, useState } from "react"; -import { Deal, getDealList, convertToGraphData } from "../api/dealApi"; +import { Deal, getDealList, convertToGraphData, getRecentDealData } from "../api/dealApi"; + + +type RecentDealData = { username: string; avatar_url: string }[] // custom hook for deal list export function useDealList() { @@ -17,7 +20,7 @@ export function useDealList() { } export function useGraphData() { - const [graphData, setGraphData] = useState>({}); + const [graphData, setGraphData] = useState({}); const fetchGraphData = async () => { const dealList = await getDealList(); @@ -31,4 +34,18 @@ export function useGraphData() { }, []); return graphData; +} + +export function useRecentDealData() { + const [recentDealData, setRecentDealData] = useState(); + + const fetchRecentDealData = async () => { + setRecentDealData(await getRecentDealData()); + } + + useEffect(() => { + fetchRecentDealData(); + }, []); + + return recentDealData; } \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 5d7cd2e..f2db558 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -12,12 +12,13 @@ import { Overview } from "@/components/ui/overview"; import { RecentFunds } from "@/components/recent-funds"; import { useState } from "react"; -import { useDealList, useGraphData } from "./hook"; +import { useDealList, useGraphData, useRecentDealData } from "./hook"; export default function Dashboard() { const [graphType, setGraphType] = useState("line"); const graphData = useGraphData(); const dealList = useDealList(); + const recentDealData = useRecentDealData(); const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0; return ( @@ -198,7 +199,7 @@ export default function Dashboard() { - + diff --git a/src/components/recent-funds.tsx b/src/components/recent-funds.tsx index 493e535..27cd796 100644 --- a/src/components/recent-funds.tsx +++ b/src/components/recent-funds.tsx @@ -1,59 +1,31 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -const data = [ - { - name: "Olivia Martin", - email: "olivia.martin@email.com", - amount: "1900.00", - avatar: "/avatars/01.png", // psuedo avatar image - initials: "OM", - }, - { - name: "Jackson Lee", - email: "jackson.lee@email.com", - amount: "39.00", - avatar: "/avatars/02.png", - initials: "JL", - }, - { - name: "Isabella Nguyen", - email: "isabella.nguyen@email.com", - amount: "299.00", - avatar: "/avatars/03.png", - initials: "IN", - }, - { - name: "William Kim", - email: "will@email.com", - amount: "99.00", - avatar: "/avatars/04.png", - initials: "WK", - }, - { - name: "Sofia Davis", - email: "sofia.davis@email.com", - amount: "39.00", - avatar: "/avatars/05.png", - initials: "SD", - }, -]; +type RecentDealData = { + username: string; + avatar_url: string; + // email: string; +}; -export function RecentFunds() { +interface RecentFundsProps { + recentDealData: RecentDealData[]; +} + +export function RecentFunds({ recentDealData }: RecentFundsProps) { return (
- {data.map((person, index) => ( + {recentDealData?.map((person, index) => (
- - {person.initials} + + {{person.username[0]}}
-

{person.name}

-

{person.email}

+

{person.username}

+ {/*

{person.email}

*/}
-
+${person.amount}
+ {/*
+${person.amount}
*/}
))}
); -} +} \ No newline at end of file diff --git a/src/hooks/useDealList.ts b/src/hooks/useDealList.ts new file mode 100644 index 0000000..e69de29 From 070077f7f3c64d01ac01661ff095393ea3ce457c Mon Sep 17 00:00:00 2001 From: Naytitorn Chaovirachot Date: Mon, 21 Oct 2024 17:36:31 +0700 Subject: [PATCH 03/59] refactor some json query + restructure recent funds json + make amount show in recent funds tab --- src/app/api/dealApi.ts | 89 ++++++++++++++++++++------------- src/app/dashboard/hook.ts | 6 +-- src/app/dashboard/page.tsx | 9 ++-- src/components/recent-funds.tsx | 38 ++++++++------ 4 files changed, 83 insertions(+), 59 deletions(-) diff --git a/src/app/api/dealApi.ts b/src/app/api/dealApi.ts index d4d88af..3c25e4e 100644 --- a/src/app/api/dealApi.ts +++ b/src/app/api/dealApi.ts @@ -7,20 +7,14 @@ export type Deal = { investor_id: string; }; -// Sort the dealList by created_time in descending order -export function byCreatedTimeDesc(a: Deal, b: Deal) { - return new Date(b.created_time).getTime() - new Date(a.created_time).getTime(); -} - export async function getDealList() { const supabase = createSupabaseClient(); - const { data: dealData, error } = await supabase + // get id of investor who invests in the business + const { data: dealData, error: dealError } = await supabase .from('business') .select(` project ( investment_deal ( - deal_amount, - created_time, investor_id ) ) @@ -28,9 +22,9 @@ export async function getDealList() { .eq('user_id', await getCurrentUserID()) .single(); - if (error) { - alert(JSON.stringify(error)); - console.error('Error fetching deal list:', error); + if (dealError) { + alert(JSON.stringify(dealError)); + console.error('Error fetching deal list:', dealError); return; // Exit on error } @@ -39,48 +33,71 @@ export async function getDealList() { return; // Exit if there's no data } - const flattenedDeals = dealData.project.flatMap((proj) => - proj.investment_deal.map((deal) => ({ - deal_amount: deal.deal_amount, - created_time: deal.created_time, - investor_id: deal.investor_id, - })) - ) + const investorIdList = dealData.project[0].investment_deal.map(deal => deal.investor_id); - // Check for empty dealList - if (!flattenedDeals.length) { - alert("No deal list available"); - return; // Exit if there's no data + // get investment_deal data then sort by created_time + const { data: sortedDealData, error: sortedDealDataError } = await supabase + .from("investment_deal") + .select(` + deal_amount, + created_time, + investor_id + `) + .in('investor_id', investorIdList) + .order('created_time', { ascending: false }) + + if (sortedDealDataError) { + alert(JSON.stringify(sortedDealDataError)); + console.error('Error sorting deal list:', sortedDealDataError); + return; // Exit on error } - return flattenedDeals.sort(byCreatedTimeDesc); + + return sortedDealData; }; +// #TODO fix query to be non unique export async function getRecentDealData() { const supabase = createSupabaseClient(); - let dealList = await getDealList(); + const dealList = await getDealList(); if (!dealList) { - // #TODO div no deals available? + // #TODO div error + console.error("No deal available"); return; } - - dealList = dealList.slice(0, 5) - const investorIdList: string[] = dealList.map(deal => deal.investor_id); - const { data: userData, error } = await supabase + // get 5 most recent investor + const recentDealList = dealList.slice(0, 5); + const recentInvestorIdList = recentDealList.map(deal => deal.investor_id); + + const { data: recentUserData, error: recentUserError } = await supabase .from("profiles") - .select("username, avatar_url") // #TODO add email - .in("id", investorIdList); // Filter by investor_id + .select(` + username, + avatar_url + `) + .in('id', recentInvestorIdList); - if (error) { - // Handle the error and return a meaningful message - console.error("Error fetching usernames and avatars:", error); + if (!recentUserData) { + alert("No recent users available"); + return; } - alert(JSON.stringify(userData)); - return userData || []; + if (recentUserError) { + // Handle the error and return a meaningful message + console.error("Error fetching profiles:", recentUserError); + return; + // #TODO div error + } + + // combine two arrays + const recentDealData = recentDealList.map((item, index) => { + return { ...item, ...recentUserData[index] }; + }); + return recentDealData; } +// #TODO refactor using supabase query instead of manual // #TODO move to util export function convertToGraphData(deals: Deal[]): Record { // group by year & month diff --git a/src/app/dashboard/hook.ts b/src/app/dashboard/hook.ts index 82b3793..e2a7ccf 100644 --- a/src/app/dashboard/hook.ts +++ b/src/app/dashboard/hook.ts @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { Deal, getDealList, convertToGraphData, getRecentDealData } from "../api/dealApi"; - - -type RecentDealData = { username: string; avatar_url: string }[] +import { RecentDealData } from "@/components/recent-funds"; // custom hook for deal list export function useDealList() { @@ -37,7 +35,7 @@ export function useGraphData() { } export function useRecentDealData() { - const [recentDealData, setRecentDealData] = useState(); + const [recentDealData, setRecentDealData] = useState(); const fetchRecentDealData = async () => { setRecentDealData(await getRecentDealData()); diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index f2db558..072d95d 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -18,18 +18,19 @@ export default function Dashboard() { const [graphType, setGraphType] = useState("line"); const graphData = useGraphData(); const dealList = useDealList(); - const recentDealData = useRecentDealData(); + // #TODO dependency injection refactor + define default value inside function (and not here) + const recentDealData = useRecentDealData() || []; const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0; return ( <> - {dealList?.map((deal, index) => ( + {/* {dealList?.map((deal, index) => (

Deal Amount: {deal.deal_amount}

Created Time: {new Date(deal.created_time).toUTCString()}

Investor ID: {deal.investor_id}

- ))} + ))} */}
- + diff --git a/src/components/recent-funds.tsx b/src/components/recent-funds.tsx index 27cd796..eec5f6c 100644 --- a/src/components/recent-funds.tsx +++ b/src/components/recent-funds.tsx @@ -1,8 +1,11 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -type RecentDealData = { +export type RecentDealData = { + created_time: Date; + deal_amount: number; + investor_id: string; username: string; - avatar_url: string; + avatar_url?: string; // email: string; }; @@ -13,19 +16,24 @@ interface RecentFundsProps { export function RecentFunds({ recentDealData }: RecentFundsProps) { return (
- {recentDealData?.map((person, index) => ( -
- - - {{person.username[0]}} - -
-

{person.username}

- {/*

{person.email}

*/} + {recentDealData?.length > 0 ? ( + recentDealData.map((data) => ( +
+ + + {/* #TODO make this not quick fix */} + {data.username ? data.username[0]: ""} + +
+

{data.username}

+ {/*

{data.email}

*/} +
+
+${data.deal_amount}
- {/*
+${person.amount}
*/} -
- ))} + )) + ) : ( +

No recent deals available.

+ )}
); -} \ No newline at end of file +} From b65b735bc422df5091c389746056cbfe025f19da Mon Sep 17 00:00:00 2001 From: Nantawat Sukrisunt Date: Mon, 21 Oct 2024 21:01:12 +0700 Subject: [PATCH 04/59] Tests rename and able actions --- .github/workflows/playwright.yml | 54 +++++++++---------- ...ec.ts => test-Investment-process .spec.ts} | 0 ...test-1.spec.ts => test-businesses.spec.ts} | 0 ...c.ts => test-dashboard-visibility.spec.ts} | 0 ....spec.ts => test-filter-with-tags.spec.ts} | 0 5 files changed, 27 insertions(+), 27 deletions(-) rename tests/{test-4.spec.ts => test-Investment-process .spec.ts} (100%) rename tests/{test-1.spec.ts => test-businesses.spec.ts} (100%) rename tests/{test-3.spec.ts => test-dashboard-visibility.spec.ts} (100%) rename tests/{test-2.spec.ts => test-filter-with-tags.spec.ts} (100%) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e70b869..2b1d1ad 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,27 +1,27 @@ -# name: Playwright Tests -# on: -# push: -# branches: [ main, master ] -# pull_request: -# branches: [ main, master ] -# jobs: -# test: -# timeout-minutes: 60 -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# - uses: actions/setup-node@v4 -# with: -# node-version: lts/* -# - name: Install dependencies -# run: npm ci -# - name: Install Playwright Browsers -# run: npx playwright install --with-deps -# - name: Run Playwright tests -# run: npx playwright test -# - uses: actions/upload-artifact@v4 -# if: ${{ !cancelled() }} -# with: -# name: playwright-report -# path: playwright-report/ -# retention-days: 30 + name: Playwright Tests + on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/tests/test-4.spec.ts b/tests/test-Investment-process .spec.ts similarity index 100% rename from tests/test-4.spec.ts rename to tests/test-Investment-process .spec.ts diff --git a/tests/test-1.spec.ts b/tests/test-businesses.spec.ts similarity index 100% rename from tests/test-1.spec.ts rename to tests/test-businesses.spec.ts diff --git a/tests/test-3.spec.ts b/tests/test-dashboard-visibility.spec.ts similarity index 100% rename from tests/test-3.spec.ts rename to tests/test-dashboard-visibility.spec.ts diff --git a/tests/test-2.spec.ts b/tests/test-filter-with-tags.spec.ts similarity index 100% rename from tests/test-2.spec.ts rename to tests/test-filter-with-tags.spec.ts From d5aed41ee081706b1987e8c23afca5a1e0f6dfbf Mon Sep 17 00:00:00 2001 From: Nantawat Sukrisunt Date: Mon, 21 Oct 2024 21:04:27 +0700 Subject: [PATCH 05/59] fix yml --- .github/workflows/playwright.yml | 50 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2b1d1ad..18e1e71 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,27 +1,23 @@ - name: Playwright Tests - on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] - jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 +name: Playwright Tests +on: [ push, pull_request ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 From fe688f2a6a3778e010017b8e568426d17ac7040c Mon Sep 17 00:00:00 2001 From: Nantawat Sukrisunt Date: Mon, 21 Oct 2024 21:21:40 +0700 Subject: [PATCH 06/59] add set environment variables to yml --- .github/workflows/playwright.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 18e1e71..650e594 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -13,6 +13,19 @@ jobs: run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps + - name: Set environment variables + run: | + echo NEXT_PUBLIC_AUTH_GOOGLE_ID=${{ secrets.NEXT_PUBLIC_AUTH_GOOGLE_ID }} >> $GITHUB_ENV + echo NEXT_PUBLIC_AUTH_GOOGLE_SECRET=${{ secrets.NEXT_PUBLIC_AUTH_GOOGLE_SECRET }} >> $GITHUB_ENV + echo NEXT_PUBLIC_DUMMY_EMAIL=${{ secrets.NEXT_PUBLIC_DUMMY_EMAIL }} >> $GITHUB_ENV + echo NEXT_PUBLIC_DUMMY_PASSWORD=${{ secrets.NEXT_PUBLIC_DUMMY_PASSWORD }} >> $GITHUB_ENV + echo NEXT_PUBLIC_STRIPE_PUBLIC_KEY=${{ secrets.NEXT_PUBLIC_STRIPE_PUBLIC_KEY }} >> $GITHUB_ENV + echo NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} >> $GITHUB_ENV + echo NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} >> $GITHUB_ENV + echo NEXT_PUBLIC_SUPABASE_URL_SOURCE=${{ secrets.NEXT_PUBLIC_SUPABASE_URL_SOURCE }} >> $GITHUB_ENV + echo NEXT_PUBLIC_TEST_URL=${{ secrets.NEXT_PUBLIC_TEST_URL }} >> $GITHUB_ENV + echo PROJECT_ID=${{ secrets.PROJECT_ID }} >> $GITHUB_ENV + echo STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }} >> $GITHUB_ENV - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v4 From b0210fb727a04859f547ba9c529e4004ba6bad17 Mon Sep 17 00:00:00 2001 From: Nantawat Sukrisunt Date: Mon, 21 Oct 2024 21:29:19 +0700 Subject: [PATCH 07/59] yml: reduce timeout --- .github/workflows/playwright.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 650e594..c02f547 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,18 +1,24 @@ name: Playwright Tests -on: [ push, pull_request ] + +on: [push, pull_request] + jobs: test: - timeout-minutes: 60 + timeout-minutes: 10 runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: lts/* + - name: Install dependencies run: npm ci + - name: Install Playwright Browsers run: npx playwright install --with-deps + - name: Set environment variables run: | echo NEXT_PUBLIC_AUTH_GOOGLE_ID=${{ secrets.NEXT_PUBLIC_AUTH_GOOGLE_ID }} >> $GITHUB_ENV @@ -26,8 +32,10 @@ jobs: echo NEXT_PUBLIC_TEST_URL=${{ secrets.NEXT_PUBLIC_TEST_URL }} >> $GITHUB_ENV echo PROJECT_ID=${{ secrets.PROJECT_ID }} >> $GITHUB_ENV echo STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }} >> $GITHUB_ENV - - name: Run Playwright tests - run: npx playwright test + + - name: Run Playwright tests with 4 workers + run: npx playwright test --workers=4 + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: From c70e327438fc665647a5a2b373377f3fa11d51be Mon Sep 17 00:00:00 2001 From: sirin Date: Wed, 23 Oct 2024 00:29:20 +0700 Subject: [PATCH 08/59] feat: add edit profile functionality --- src/app/(user)/profile/[uid]/edit/page.tsx | 134 +++++++++++++++++++++ src/app/(user)/profile/[uid]/page.tsx | 5 +- src/lib/data/bucket/uploadAvatar.ts | 31 +++++ src/lib/data/profileMutate.ts | 48 ++++++++ src/types/schemas/profile.schema.ts | 27 +++++ 5 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 src/app/(user)/profile/[uid]/edit/page.tsx create mode 100644 src/lib/data/bucket/uploadAvatar.ts create mode 100644 src/lib/data/profileMutate.ts create mode 100644 src/types/schemas/profile.schema.ts diff --git a/src/app/(user)/profile/[uid]/edit/page.tsx b/src/app/(user)/profile/[uid]/edit/page.tsx new file mode 100644 index 0000000..e9fdf21 --- /dev/null +++ b/src/app/(user)/profile/[uid]/edit/page.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { updateProfile } from "@/lib/data/profileMutate"; +import { useForm } from "react-hook-form"; +import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form"; +import { z } from "zod"; +import { profileSchema } from "@/types/schemas/profile.schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; +import { uploadAvatar } from "@/lib/data/bucket/uploadAvatar"; +import toast from "react-hot-toast"; +import { useRouter } from "next/navigation"; +import { Separator } from "@/components/ui/separator"; + +export default function EditProfilePage({ params }: { params: { uid: string } }) { + const uid = params.uid; + const client = createSupabaseClient(); + const router = useRouter(); + + const profileForm = useForm>({ + resolver: zodResolver(profileSchema), + }); + + const onProfileSubmit = async (updates: z.infer) => { + const { avatars, username, full_name, bio } = updates; + + try { + let avatarUrl = null; + + if (avatars instanceof File) { + const avatarData = await uploadAvatar(client, avatars, uid); + avatarUrl = avatarData?.path + ? `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/avatars/${avatarData.path}` + : null; + } + + const result = await updateProfile(client, uid, { + username, + full_name, + bio, + ...(avatarUrl && { avatar_url: avatarUrl }), + }); + + if (result) { + toast.success("Profile updated successfully!"); + router.push(`/profile/${uid}`); + } else { + toast.error("No fields to update!"); + } + } catch (error) { + toast.error("Error updating profile!"); + console.error("Error updating profile:", error); + } + }; + + return ( +
+
+ Update Profile + +
+
+
+ + ( + + Avatar + + onChange(event.target.files && event.target.files[0])} + /> + + + + + )} + /> + ( + + Username + + + + This is your public display name. + + + )} + /> + ( + + Full Name + + + + This is your public full name. + + + )} + /> + ( + + Bio + +