FreelancePro
- NextJS
- TypeScript
- React
- JavaScript
- PostgreSQL
- Oauth
FreelancePro is an application that gives freelancers the ability to manage their invoices and get a bird's eye view of their outstanding invoices and their business.
Category: Financial Services
Project link: https://www.freelancepro.app
Project Purpose and Goal
As a freelancer, I often found that existing tools were either overloaded with unnecessary features or too basic to meet the unique demands of freelancing. With this in mind, I designed and developed FreelancePro—a streamlined platform focused on helping freelancers efficiently manage clients, track outstanding invoices, and get a clear view of their business at a glance.
In its initial release, FreelancePro offers core features designed for simplicity and effectiveness, with a roadmap to expand into a comprehensive toolkit, including integrated invoicing and built-in client messaging.
My goal is to continuously improve FreelancePro to empower freelancers with a dedicated, versatile service, ultimately enabling smoother client interactions and financial management tailored to their specific needs.
Problems and Thought Process
In developing FreelancePro, I embraced a mobile-first approach, recognizing that most users access web applications via mobile devices rather than desktops. My goal was to create a seamless, vertically scrollable interface, allowing users to view their data intuitively on mobile screens without the need to switch to landscape mode. Achieving a clean and functional mobile dashboard required multiple iterations, but the final result is a streamlined user experience that meets the needs of freelancers on the go.
To support scalability and easy maintenance, I designed a database architecture centered on efficient PostgreSQL queries, ensuring each user sees only the data relevant to them. Protecting user data was another core priority. By leveraging secure server actions, FreelancePro displays client-specific information while preventing exposure of unrelated client data. Additionally, I implemented optimized server queries to allow authenticated users to quickly search invoices and client records, boosting efficiency and reliability.
Although the application's code is not publicly available, here is an example of a Server Action utilizing widely adopted packages such as Zod for robust data validation and DOMPurify to sanitize user input before database storage. Additionally, I implemented logic to prevent users from updating a customer's email to one that is already assigned to another customer.
export async function editCustomer(
id: string,
prevState: CreateCustomerState,
formData: FormData
){
const schema = z.object({
name: z.string({
required_error: "Name is required",
invalid_type_error: "Name must be a string",
}).trim().min(1, {message: "Name is required"}),
email: z.string({
required_error: "Email is required",
invalid_type_error: "Email must be a string",
}).email({ message: "Invalid email address"}),
website: z.string(),
notes: z.string().max(5000,
{message: "Notes must be under 5000 characters"}),
status: z.string({
required_error: "Status is required",
invalid_type_error: "Status is required",
}).trim().min(1, {message: "Status is Required"}),
})
const validatedFields = schema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
website: formData.get('website'),
notes: formData.get('notes'),
status: formData.get('status')
})
if(!validatedFields.success){
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to add Customer.',
};
}
const data = validatedFields.data;
const cleanName = DOMPurify.sanitize(data.name);
const cleanEmail = DOMPurify.sanitize(data.email);
const cleanWebsite = DOMPurify.sanitize(data.website);
const cleanNotes = DOMPurify.sanitize(data.notes);
const cleanStatus = DOMPurify.sanitize(data.status);
try {
let user: any = await getUser();
await sql`
UPDATE customers
SET
name = ${cleanName},
email = ${cleanEmail},
website = ${cleanWebsite},
notes = ${cleanNotes},
status = ${cleanStatus}
WHERE id = ${id}
`;
revalidatePath('/')
return { message: `Customer has been updated!` };
} catch(e: any){
console.log(e)
if(
e.routine ==='_bt_check_unique' &&
e.constraint_name === 'customers_email_key'
){
return {
message: `This email address (${data.email})
is already used by another customer. An email
address can be associated with one customer at a time.`}
}
return { message: "Database Error: Failed to update customer"}
}
};
FreelancePro ultimately provides freelancers with a simple yet powerful tool tailored to their workflow. From its mobile-optimized dashboard to its secure and efficient backend, FreelancePro meets the challenges I set out to address, empowering freelancers to manage clients, track invoices, and get an insightful overview of their business with ease.
Web Stack and Explanation
For FreelancePro, I chose Next.js and deployed with Vercel to leverage their robust performance, server-side rendering capabilities, and seamless deployment. This setup has been highly effective, as I’ve had positive experiences with Next.js and Vercel on previous projects.
For data storage, I implemented PostgreSQL, designing custom schemas rather than relying on ORMs. By using foreign keys, I maintained structured and organized data relationships, while cascading deletions simplified data management and cleanup.
This structure supports FreelancePro’s flexibility, as users can easily edit invoices and client information on demand. Error handling was a priority, so I implemented clear and user-friendly error messages, ensuring that any character limits or data type requirements were communicated in an actionable way.
I also wanted to implement a comprehensive confirmation step system so users would need to confirm the deletion of invoices and customers.
Given the high interactivity of FreelancePro, I took extra steps to ensure data integrity and security. Using Zod for data validation allowed for precise control over data formats, and I implemented DOMPurify to sanitize all user input, protecting the database from potentially harmful entries. To optimize performance, I incorporated debouncing to limit redundant server queries during invoice and client searches, significantly improving responsiveness and reducing server load.
This web stack allows FreelancePro to be highly interactive, responsive, and secure, all while maintaining the flexibility freelancers need to manage their client information and invoices smoothly.
Lesons Learned & Future Work
FreelancePro has been a passion project I envisioned for some time, and I waited to gain more coding experience before bringing it to life. Developing this app presented unique challenges, particularly in minimizing server calls while maintaining strict user authorization. Overcoming these hurdles not only deepened my understanding of efficient data management but also strengthened the app’s security.
Moving forward, I aim to expand FreelancePro by introducing advanced features such as invoicing and in-app client messaging. By initially launching a free tier, I can gather user feedback to guide the development of these paid features and ensure they meet freelancers’ needs. Completing this project has been both intensive and rewarding, and I look forward to enhancing it with real-world solutions that simplify freelance work.
Let's connect and see what we can accomplish together! hi@johan.me