Overview
The Person Management feature allows you to create, update, and manage contacts in your address book. These contacts can be selected as recipients when scheduling messages.
Key Features
Contact Directory Centralized contact management system
Phone Validation Automatic validation of phone numbers
CRUD Operations Create, read, update, and delete contacts
Message Integration Contacts are linked to scheduled messages
Person Data Structure
Each person record contains:
interface People {
id : string ;
name : string ;
phone : string ;
}
UI Components
The People component displays all contacts in a scrollable list:
export default async function People () {
const data = await fetch ( ` ${ process . env . BACKEND_URL } /people/all` , {
headers: {
Authorization: `Basic ${ Buffer . from (
` ${ process . env . USERNAME } : ${ process . env . PASSWORD } `
). toString ( "base64" ) } ` ,
},
});
const people : People [] = await data . json ();
return (
< div className = "cursor-pointer" >
< h2 className = "text-xl font-bold mb-2" > People : </ h2 >
< ScrollArea className = "max-h-[calc(100vh-180px)]" >
{ people . map (( person : People ) => (
< Person key = { person . id } person = { person } />
)) }
</ ScrollArea >
< Dialog >
< DialogTrigger asChild >
< Button className = "w-full" >
< UserPlus />
< span > Add Person </ span >
</ Button >
</ DialogTrigger >
< DialogContent >
< DialogTitle > Add Person </ DialogTitle >
< PersonCreate />
</ DialogContent >
</ Dialog >
</ div >
);
}
Open the Add Person Dialog
Click the “Add Person” button to open the creation form: < Button className = "w-full" >
< UserPlus />
< span > Add Person </ span >
</ Button >
Fill in Contact Information
The form requires two fields:
Name : The contact’s full name
Phone : Phone number in Bangladesh format
< form action = { createAction } className = "space-y-3" >
< div className = "space-y-2" >
< Label htmlFor = "name" className = "font-bold" > Name </ Label >
< Input name = "name" />
</ div >
< div className = "space-y-2" >
< Label htmlFor = "phone" className = "font-bold" > Phone </ Label >
< Input name = "phone" />
</ div >
< Button type = "submit" disabled = { createIsPending } >
Add
</ Button >
</ form >
Submit and Validate
The form data is validated on the server before being saved to the database.
Phone Number Validation
The application enforces strict phone number validation using Zod schema:
const personSchema = z . object ({
name: z . string (). min ( 1 , "Name cannot be empty" ),
phone: z
. string ()
. regex ( / ^ 8801 \d {9} $ / , "Invalid phone number format. Must be 8801XXXXXXXXX" ),
});
Phone numbers must be in the format: 8801XXXXXXXXX
Must start with 8801
Must be exactly 13 digits long
Only numeric characters allowed
Example: 8801712345678
Valid Phone Number Examples
8801712345678 ✓
8801812345678 ✓
8801912345678 ✓
Invalid Phone Number Examples
01712345678 ✗ (Missing country code)
+8801712345678 ✗ (Contains + symbol)
88017123456 ✗ (Too short)
88017123456789 ✗ (Too long)
Each contact is displayed as a card in the scrollable list:
function Person ({ person } : { person : People }) {
return (
< Dialog >
< DialogTrigger asChild >
< Card className = "border-1 border-dashed drop-shadow-xl hover:bg-ring-offset mb-2" >
< CardHeader className = "font-bold" > { person . name } </ CardHeader >
< CardContent className = "space-y-2" >
< CardDescription > Phone: { person . phone } </ CardDescription >
</ CardContent >
</ Card >
</ DialogTrigger >
< DialogContent >
< DialogTitle > Update Person </ DialogTitle >
< PersonUpdate person = { person } />
</ DialogContent >
</ Dialog >
);
}
Click on any contact card to open the update dialog.
The update form is pre-filled with existing data:
export default function PersonUpdate ({ person } : { person : People }) {
const [ updateState , updateAction , updateIsPending ] = useActionState (
updatePerson ,
{}
);
return (
< form action = { updateAction } className = "space-y-3" >
< Input type = "text" name = "id" hidden defaultValue = { person . id } />
< div className = "space-y-2" >
< Label htmlFor = "name" > Name </ Label >
< Input name = "name" defaultValue = { person . name } />
</ div >
< div className = "space-y-2" >
< Label htmlFor = "phone" > Phone </ Label >
< Input name = "phone" defaultValue = { person . phone } />
</ div >
< Button type = "submit" disabled = { updateIsPending } >
Update
</ Button >
</ form >
);
}
The delete button is available in the update dialog:
< form action = { deleteAction } className = "space-y-3" >
< Input type = "text" name = "id" hidden defaultValue = { person . id } />
< Button type = "submit" variant = "outline" disabled = { deleteIsPending } >
Delete
</ Button >
</ form >
Deleting a contact may affect existing scheduled messages that reference this contact.
Server Actions
All person management operations use Next.js Server Actions:
Create Person Action
Update Person Action
Delete Person Action
"use server" ;
import { revalidatePath } from "next/cache" ;
import { z } from "zod" ;
interface PersonPrevState {
success ?: boolean ;
error ?: string ;
}
const personSchema = z . object ({
name: z . string (). min ( 1 , "Name cannot be empty" ),
phone: z
. string ()
. regex ( / ^ 8801 \\ d {9} $ / , "Invalid phone number format. Must be 8801XXXXXXXXX" ),
});
export default async function createPerson (
prevState : PersonPrevState ,
formData : FormData
) : Promise < PersonPrevState > {
try {
const parsedData = personSchema . safeParse ({
name: formData . get ( "name" ),
phone: formData . get ( "phone" ),
});
if ( ! parsedData . success ) {
return {
error: parsedData . error . errors . map (( err ) => err . message ). join ( ", " ),
};
}
const response = await fetch (
` ${ process . env . BACKEND_URL } /people/create-one` ,
{
method: "POST" ,
headers: {
Authorization: `Basic ${ Buffer . from (
` ${ process . env . USERNAME } : ${ process . env . PASSWORD } `
). toString ( "base64" ) } ` ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ( parsedData . data ),
}
);
if ( ! response . ok ) {
return {
error: `Backend Error: ${ response . status } ${ response . statusText } ` ,
};
}
revalidatePath ( "/" );
return { success: true };
} catch ( error ) {
return {
error: error instanceof Error ? error . message : "Something went wrong" ,
};
}
}
Toast Notifications
The application provides feedback for all operations:
useEffect (() => {
if ( createState . success ) {
toast . success ( "Person created successfully" , {
richColors: true ,
});
}
if ( createState . error ) {
toast . error ( "Person creation failed" , {
description: createState . error ,
duration: 3500 ,
richColors: true ,
});
}
}, [ createState ]);
useEffect (() => {
if ( updateState . success ) {
toast . success ( "Person updated successfully" , { richColors: true });
}
if ( deleteState . success ) {
toast . success ( "Person deleted successfully" , { richColors: true });
}
}, [ updateState , deleteState ]);
Backend API Endpoints
Endpoint Method Purpose /people/allGET Fetch all contacts /people/create-onePOST Create a new contact /people/update-one/:idPOST Update an existing contact /people/delete-one/:idPOST Delete a contact
All API requests require Basic Authentication using environment variables USERNAME and PASSWORD.
Integration with Messages
When creating or updating a message, contacts from the People list are available in the recipient dropdown:
< Select name = "sendToPhone" >
< SelectTrigger >
< SelectValue placeholder = "Send To" />
</ SelectTrigger >
< SelectContent >
{ people . map (( person ) => (
< SelectItem key = { person . id } value = { person . phone } >
{ person . name } ( { person . phone } )
</ SelectItem >
)) }
</ SelectContent >
</ Select >