From a20c939c91d98bd3eaa97955e92c4314531ca2ff Mon Sep 17 00:00:00 2001 From: i701 Date: Fri, 11 Jul 2025 14:41:06 +0500 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20implement=20user=20profile=20page?= =?UTF-8?q?=20and=20integrate=20profile=20fetching=20logic=20in=20AccountP?= =?UTF-8?q?opover=20and=20ApplicationLayout=20components=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- actions/user-actions.ts | 102 +++++++++---------------- app/(dashboard)/profile/page.tsx | 95 +++++++++++++++++++++++ components/auth/account-popver.tsx | 35 +++++---- components/auth/application-layout.tsx | 2 +- 4 files changed, 152 insertions(+), 82 deletions(-) create mode 100644 app/(dashboard)/profile/page.tsx diff --git a/actions/user-actions.ts b/actions/user-actions.ts index 40514c3..a986604 100644 --- a/actions/user-actions.ts +++ b/actions/user-actions.ts @@ -1,8 +1,9 @@ "use server"; -import { VerifyUserDetails } from "@/lib/person"; -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; -import { CreateClient } from "./ninja/client"; + +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/auth"; +import type { User, UserProfile } from "@/lib/types/user"; +import { handleApiResponse } from "@/utils/tryCatch"; export async function VerifyUser(userId: string) { // const user = await prisma.user.findUnique({ @@ -52,72 +53,37 @@ export async function VerifyUser(userId: string) { // revalidatePath("/users"); } -export async function Rejectuser({ - userId, - reason, -}: { userId: string; reason: string }) { - // const user = await prisma.user.findUnique({ - // where: { - // id: userId, - // }, - // }); - // if (!user) { - // throw new Error("User not found"); - // } - - // await SendUserRejectionDetailSMS({ - // details: reason, - // phoneNumber: user.phoneNumber, - // }); - // await prisma.user.delete({ - // where: { - // id: userId, - // }, - // }); - revalidatePath("/users"); - redirect("/users"); -} - -export const SendUserRejectionDetailSMS = async ({ - details, - phoneNumber, -}: { - details: string; - phoneNumber: string; -}) => { - try { - const respose = await fetch(`${process.env.SMS_API_BASE_URL}/api/sms`, { - method: "POST", +export async function getProfile() { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/auth/profile/`, + { + method: "GET", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.SMS_API_KEY}`, + Authorization: `Token ${session?.apiToken}`, }, - body: JSON.stringify({ - check_delivery: false, - number: phoneNumber, - message: details, - }), - }); - const data = await respose.json(); - console.log(data); - return data; - } catch (error) { - console.error(error); - } -}; + }, + ); -export async function AddDevice({ - name, - mac_address, - user_id, -}: { name: string; mac_address: string; user_id: string }) { - // const newDevice = await prisma.device.create({ - // data: { - // name: name, - // mac: mac_address, - // userId: user_id, - // }, - // }); - revalidatePath("/devices"); - // return newDevice; + return handleApiResponse(response, "getProfile"); } + + + +export async function getProfileById(userId: string) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/auth/users/${userId}/`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + + return handleApiResponse(response, "getProfilebyId"); +} + diff --git a/app/(dashboard)/profile/page.tsx b/app/(dashboard)/profile/page.tsx new file mode 100644 index 0000000..0b28631 --- /dev/null +++ b/app/(dashboard)/profile/page.tsx @@ -0,0 +1,95 @@ +import { redirect } from "next/navigation"; +import { getServerSession } from "next-auth"; +import { getProfileById } from "@/actions/user-actions"; +import { authOptions } from "@/app/auth"; +import ClientErrorMessage from "@/components/client-error-message"; +import { Badge } from "@/components/ui/badge"; +import { FloatingLabelInput } from "@/components/ui/floating-label"; +import { tryCatch } from "@/utils/tryCatch"; + +export default async function Profile() { + const session = await getServerSession(authOptions); + if (!session?.user) return redirect("/auth/signin?callbackUrl=/profile"); + const [error, profile] = await tryCatch(getProfileById(session?.user.id)); + if (error) { + if (error.message === "Invalid token.") redirect("/auth/signin"); + return ; + } + return ( +
+
+

Profile

+
+ Profile Status + {verifiedStatus(profile?.verified ?? false)} +
+
+
+
+ + + + + + + +
+
+ {/* + + */} +
+ ); +} + +function verifiedStatus(status: boolean) { + switch (status) { + case true: + return Verified; + case false: + return Not Verified; + default: + return Unknown; + } +} diff --git a/components/auth/account-popver.tsx b/components/auth/account-popver.tsx index 20aba64..aa447c5 100644 --- a/components/auth/account-popver.tsx +++ b/components/auth/account-popver.tsx @@ -1,13 +1,14 @@ "use client"; +import { Loader2, User as UserIcon } from "lucide-react"; +import Link from "next/link"; +import { signOut, useSession } from "next-auth/react"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { Loader2, User as UserIcon } from "lucide-react"; -import { signOut, useSession } from "next-auth/react"; -import { useState } from "react"; export function AccountPopover() { const session = useSession(); @@ -36,16 +37,24 @@ export function AccountPopover() {

{session.data?.user?.mobile}

- +
+ + + + +
+ diff --git a/components/auth/application-layout.tsx b/components/auth/application-layout.tsx index 08a9bf4..aff92a4 100644 --- a/components/auth/application-layout.tsx +++ b/components/auth/application-layout.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation"; import { getServerSession } from "next-auth"; import { NuqsAdapter } from 'nuqs/adapters/next/app' -import { getProfile } from "@/actions/payment"; +import { getProfile } from "@/actions/user-actions"; import { authOptions } from "@/app/auth"; import { DeviceCartDrawer } from "@/components/device-cart"; import { ModeToggle } from "@/components/theme-toggle"; From 78673e050c3b839074719f1c64cfdd8ea1965c2a Mon Sep 17 00:00:00 2001 From: i701 Date: Fri, 11 Jul 2025 14:41:30 +0500 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20remove=20unused=20user=20verifi?= =?UTF-8?q?cation=20function=20and=20clean=20up=20user=20type=20definition?= =?UTF-8?q?s=20=F0=9F=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- actions/payment.ts | 14 -------------- lib/person.ts | 25 ------------------------- lib/types/user.ts | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/actions/payment.ts b/actions/payment.ts index eb3950d..9eb01ef 100644 --- a/actions/payment.ts +++ b/actions/payment.ts @@ -363,18 +363,4 @@ export async function verifyTopupPayment( } } -export async function getProfile() { - const session = await getServerSession(authOptions); - const response = await fetch( - `${process.env.SARLINK_API_BASE_URL}/api/auth/profile/`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Token ${session?.apiToken}`, - }, - }, - ); - return handleApiResponse(response, "getProfile"); -} diff --git a/lib/person.ts b/lib/person.ts index c62f462..72b5114 100644 --- a/lib/person.ts +++ b/lib/person.ts @@ -1,6 +1,5 @@ "use server"; import type { TNationalPerson } from "@/lib/types"; -import type { User } from "./types/user"; export async function getNationalPerson({ idCard, @@ -17,28 +16,4 @@ export async function getNationalPerson({ return nationalData; } -export async function VerifyUserDetails({ user }: { user: User }) { - console.log(user); - // const phoneNumber = String(user.phoneNumber).slice(4); - // console.log({ phoneNumber }); - // const nationalData = await getNationalPerson({ idCard: user.id_card ?? "" }); - // const dob = new Date(nationalData.dob); - // const age = new Date().getFullYear() - dob.getFullYear(); - // console.log("ID card", user.id_card === nationalData.nic); - // console.log("name", user.name === nationalData.name_en); - // console.log("house", user.address === nationalData.house_name_en); - // console.log("phone", phoneNumber === nationalData.primary_contact); - // console.log("db phone", phoneNumber); - // console.log("national phone", nationalData.primary_contact); - - // if ( - // user.id_card === nationalData.nic && - // user.name === nationalData.name_en && - // user.address === nationalData.house_name_en && - // age >= 18 - // ) { - // return true; - // } - return false; -} diff --git a/lib/types/user.ts b/lib/types/user.ts index fee5f7d..07106d0 100644 --- a/lib/types/user.ts +++ b/lib/types/user.ts @@ -1,4 +1,5 @@ import type { ISODateString } from "next-auth"; +import type { Atoll, Island } from "../backend-types"; export interface Permission { id: number; @@ -30,6 +31,22 @@ export interface User { last_login: string; } +export interface UserProfile { + id: number; + email: string; + first_name: string; + last_name: string; + atoll: Atoll; + island: Island; + dob: string; + verified: boolean; + username: string; + mobile: string; + address: string; + acc_no: string; + id_card: string; +} + export interface Session { user?: { token?: string; From 9c1d1c5d2bbb547285b6e440a4138b544d54d5ad Mon Sep 17 00:00:00 2001 From: i701 Date: Fri, 11 Jul 2025 14:41:47 +0500 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20add=20floating=20label=20input=20co?= =?UTF-8?q?mponent=20for=20enhanced=20form=20usability=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/floating-label.tsx | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 components/ui/floating-label.tsx diff --git a/components/ui/floating-label.tsx b/components/ui/floating-label.tsx new file mode 100644 index 0000000..9d453cb --- /dev/null +++ b/components/ui/floating-label.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { cn } from '@/lib/utils'; + +export interface InputProps extends React.InputHTMLAttributes { } + +const FloatingInput = React.forwardRef( + ({ className, ...props }, ref) => { + return ; + }, +); +FloatingInput.displayName = 'FloatingInput'; + +const FloatingLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( +