From f0b998ce8c83760b3911f1dbcc531032ace99635 Mon Sep 17 00:00:00 2001 From: Dvir Daniel <183808379+dvir-daniel@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:43:14 +0200 Subject: [PATCH] myaccount update --- .../myaccount-web-app/src/app/(me)/page.tsx | 216 ++++++++---------- .../(me)/profile/profile/profile-section.tsx | 8 +- .../myaccount-web-app/src/app/auth/page.tsx | 59 ++++- apps/myaccount/myaccount-web-app/src/auth.ts | 13 +- .../src/service/api.service.ts | 7 +- .../src/service/user.service.ts | 20 +- 6 files changed, 161 insertions(+), 162 deletions(-) diff --git a/apps/myaccount/myaccount-web-app/src/app/(me)/page.tsx b/apps/myaccount/myaccount-web-app/src/app/(me)/page.tsx index d8b4e6ed..e547eeb8 100755 --- a/apps/myaccount/myaccount-web-app/src/app/(me)/page.tsx +++ b/apps/myaccount/myaccount-web-app/src/app/(me)/page.tsx @@ -1,156 +1,122 @@ -import { auth, protectAuth } from "@/auth"; -import BreadCrumb from "@/components/breadcrumb"; -import { ConnectionClient } from "@/components/tables/connections-tables/client"; -import apiService from '@/service/api.service'; -import { CalendarDateRangePicker } from "@/components/date-range-picker"; -import { Button } from "@/components/ui/button"; +import { protectAuth } from "@/auth"; import { Card, CardContent, - CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { ActivityHistoryClient } from "@/components/tables/activity-tables/client"; -import UserService from 'service/user.service'; import ActivityTableSection from "./activity-history-section"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import UserService from "@/service/user.service"; -const breadcrumbItems = [{ title: "Home", link: "/" }]; -async function getStatistics() { - const session = await auth(); - const userService = UserService() - if (!session) { - return - } +async function fetchStatistics(token: string) { + const userService = UserService(); try { - const res = userService.getUserStatistics(session) - return res; - } catch (e) { - console.log(e); + return await userService.getUserStatistics(token); + } catch (error) { + console.error("Error fetching statistics:", error); return { activeAppsConnections: 0, activeSubscriptions: { total_count: 0 }, - expenses: 0 + expenses: 0, }; } } export default async function Page() { - const statistics = await getStatistics(); - const session = await protectAuth() - const user = session?.user; - if (!user) return + const userService = UserService(); + const session = await protectAuth(); - return ( - -
- -
-
- - - {(user.displayName || user.email)?.[0]?.toLocaleUpperCase()} - -
-

Welcome, {user?.firstName} Inc

-

Manage your info, privacy, and security to make Eartho work better for you.

-
+ if (!session?.accessToken) return null; -
- - - - Active Apps - - - - - - - - -
{statistics?.activeAppsConnections || 0}
-

- 0% from last month -

-
-
- - - - Active Subscriptions - - - - - - - - -
{statistics?.activeSubscriptions?.total_count || 0}
-

- 0% from last month -

-
-
- - - Expenses - - - - - -
${statistics?.expenses || 0}
-

- 0% from last month -

-
-
+ const [statistics, user] = await Promise.all([ + fetchStatistics(session.accessToken), + userService.getUserProfile(session.accessToken), + ]); -
+ if (!user) return null; -
- -
+ return ( + +
+ + +
); } + +function UserWelcomeSection({ user }: { user: any }) { + return ( +
+ +

Welcome {user?.firstName ?? "To Eartho"}

+

+ Manage your info, privacy, and security to make Eartho work better for you. +

+
+ ); +} + +function AvatarContainer({ user }: { user: any }) { + return ( +
+ + + + {(user.displayName || user.email)?.[0]?.toUpperCase()} + + +
+ ); +} + +function StatisticsSection({ statistics }: { statistics: any }) { + return ( +
+ + + +
+ ); +} + +function StatisticCard({ title, value }: { title: string; value: string | number }) { + return ( + + + {title} + + + +
{value}
+

0% from last month

+
+
+ ); +} + +function IconPlaceholder() { + return ( + + + + + + ); +} diff --git a/apps/myaccount/myaccount-web-app/src/app/(me)/profile/profile/profile-section.tsx b/apps/myaccount/myaccount-web-app/src/app/(me)/profile/profile/profile-section.tsx index c0d96c27..b61526f9 100755 --- a/apps/myaccount/myaccount-web-app/src/app/(me)/profile/profile/profile-section.tsx +++ b/apps/myaccount/myaccount-web-app/src/app/(me)/profile/profile/profile-section.tsx @@ -6,16 +6,19 @@ import { ProfileForm, ProfileFormValues } from "./profile-form"; import UserService from '@/service/user.service'; import { toast } from '@/components/ui/use-toast'; import ProfileFormSkeleton from './profile-loader'; +import { useSession } from 'next-auth/react'; const ProfileSection = () => { const userSerivce = UserService(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); + const { data: session, update } = useSession(); + useEffect(() => { const fetchProfileData = async () => { setLoading(true) - const data = await userSerivce.getUserProfile(); + const data = await userSerivce.getUserProfile(session?.accessToken!); setProfile(data); setLoading(false) }; @@ -25,8 +28,9 @@ const ProfileSection = () => { const handleUpdateProfile = async (updatedData: ProfileFormValues) => { try { - const updatedProfile = await userSerivce.updateUserProfile(updatedData); + const updatedProfile = await userSerivce.updateUserProfile(session?.accessToken!, updatedData); setProfile(updatedProfile); + update() toast({ title: "Your profile has been updated successfully", description:"It may take a few minutes for the changes to appear in other services", diff --git a/apps/myaccount/myaccount-web-app/src/app/auth/page.tsx b/apps/myaccount/myaccount-web-app/src/app/auth/page.tsx index 9a9d4c59..eb89bb4c 100755 --- a/apps/myaccount/myaccount-web-app/src/app/auth/page.tsx +++ b/apps/myaccount/myaccount-web-app/src/app/auth/page.tsx @@ -1,22 +1,31 @@ "use client" import Link from "next/link"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Button, buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { Loader2 } from "lucide-react"; import { toast } from "@/components/ui/use-toast"; -import { signIn } from "next-auth/react"; +import { signIn, useSession } from "next-auth/react"; export default function Page() { - const [loading, setLoading] = useState(false); const router = useRouter(); + const searchParams = useSearchParams(); + + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const { data: session, status } = useSession(); - const handleLogin = async () => { + const handleLogin = (e: any) => { + e.preventDefault(); setLoading(true); + try { - signIn("eartho", { redirect: true, redirectTo: "/" }); + const currentUrl = window.location.href; + sessionStorage.setItem("authReferrer", currentUrl); + + signIn("eartho", { redirect: true, redirectTo: '/' }, { access_id: "" }); } catch (error) { console.error("Login failed:", error); toast({ @@ -24,8 +33,31 @@ export default function Page() { title: "Uh oh! Something went wrong.", description: "Please contact support.", }); - } finally { - setLoading(false); + } + }; + + useEffect(() => { + if (window && window.sessionStorage) { + const errorStorageParam = sessionStorage.getItem("authError"); + if (errorStorageParam && !searchParams?.get("error")) { + sessionStorage.removeItem("authError"); + setErrorMessage(errorStorageParam); + } + } + }, [searchParams]); + + const getErrorMessage = (error: string) => { + switch (error) { + case "CredentialsSignin": + return "Invalid credentials, please try again."; + case "OAuthAccountNotLinked": + return "Account already exists with another provider. Use the same account to link."; + case "EmailSignin": + return "Issue sending the email."; + case "Configuration": + return "This provider is restricted in your country."; + default: + return "Please contact support."; } }; @@ -48,9 +80,8 @@ export default function Page() {

- {/* “We replaced 37,000 lines of code with ~50 lines of @Eartho integration and can’t imagine working without it.” */} + {/* Optional quote or info */}

- {/*
Ilya K
*/}
@@ -58,7 +89,7 @@ export default function Page() {

- Welcome to Eartho Memebers + Welcome to Eartho

Click on the button below to connect and start @@ -67,7 +98,6 @@ export default function Page() { + {errorMessage && ( +

+ {getErrorMessage(errorMessage)} +
+ )}

By clicking continue, you agree to our{' '} { return { uid: profile.id, + image: profile.photoURL, + name: profile.displayName, ...profile } } @@ -104,11 +103,11 @@ const authOptions: NextAuthConfig = { const { handlers, signIn, signOut, auth } = NextAuth(authOptions); -async function protectAuth() { +async function protectAuth(): Promise { const session = await auth(); if (!session) { redirect(loginPage); - return null; + throw Error(); } return session; } diff --git a/apps/myaccount/myaccount-web-app/src/service/api.service.ts b/apps/myaccount/myaccount-web-app/src/service/api.service.ts index ac17c2ea..cc828e7d 100755 --- a/apps/myaccount/myaccount-web-app/src/service/api.service.ts +++ b/apps/myaccount/myaccount-web-app/src/service/api.service.ts @@ -3,7 +3,7 @@ import config from "@/constants/config"; interface RequestOptions { method?: string; - cache?: string; + cache?: RequestCache; headers?: Record; body?: any; accessToken?: string; @@ -22,7 +22,6 @@ class ApiService { const url = this.createUrl(endpoint, params); const defaultHeaders = { - 'auth-provider': 'one.eartho', 'Authorization': options.accessToken ? `Bearer ${options.accessToken}` : '', 'Content-Type': 'application/json', }; @@ -36,6 +35,7 @@ class ApiService { body: options.body || undefined, method: options.method, headers: headers, + cache: options.cache || undefined, }; try { @@ -72,8 +72,7 @@ class ApiService { { method: "POST", body: formData, - headers:{ - 'auth-provider': 'one.eartho', + headers: { 'Authorization': options.accessToken ? `Bearer ${options.accessToken}` : '', } } diff --git a/apps/myaccount/myaccount-web-app/src/service/user.service.ts b/apps/myaccount/myaccount-web-app/src/service/user.service.ts index 1306e78f..fbb8113f 100755 --- a/apps/myaccount/myaccount-web-app/src/service/user.service.ts +++ b/apps/myaccount/myaccount-web-app/src/service/user.service.ts @@ -1,33 +1,29 @@ -import { getSession } from 'next-auth/react'; import apiService from './api.service'; -import { Session } from 'next-auth'; export default function UserService() { - async function getUserProfile() { - const session = await getSession(); + async function getUserProfile(accessToken: string) { const data = await apiService.get('/me/profile', { - accessToken: session?.accessToken, - }); + accessToken, + cache: 'no-store' + }, {}); return data; } - async function updateUserProfile(payload: any) { - const session = await getSession(); + async function updateUserProfile(accessToken: string, payload: any) { const data = await apiService.put( '/me/profile', payload, { - accessToken: session?.accessToken, + accessToken, } ); return data; } - async function getUserStatistics(session: Session | null) { - const s = session //|| await getSession(); + async function getUserStatistics(accessToken: string) { const data = await apiService.get('/me/statistics', { - accessToken: session?.accessToken, + accessToken, }); return data; }