The wild oasis [Admin]

By Amadou Seck
ReactTypescriptTailwind

Tuesday, February 11, 2025

Admin Page Architecture Overview

State and Data Management

The project uses @tanstack/react-query and @tanstack/react-table for efficient state and asynchronous data management. This approach standardizes data fetching, caching, and updating, preventing UI component clutter. For example, API calls related to cabins are encapsulated in a service function using Supabase as the backend:

export async function createCabin(cabin: CabinForm) {
  if (!cabin.image) throw new Error('Image is required');

  const imgPath = await handleImageUpload(cabin.image);
  const fullImgPath = `${cabinBucketBaseUrl}/${imgPath.path}`;
  const { error, data } = await supabase
    .from('cabins')
    .insert({ ...cabin, image: fullImgPath })
    .select();

  if (error) throw new Error('Error creating cabin');
  return data;
}

This layered design means that UI components can focus on rendering, while data management and fetching logic are abstracted away into other files.

Error Handling

Error handling is implemented at both the API and UI levels. API errors during cabin creation are captured with descriptive messages. @react-error-boundary within the component hierarchy catches exceptions from API calls or component lifecycles, ensuring user-friendly error displays.

Form validation is also enforced using zod, ensuring user input concforms to the defined schema. For example:

export const updatePasswordSchema = z
  .object({
    currentPassword: loginPasswordSchema,
    newPassword: createPasswordSchema,
    confirmPassword: z.string(),
  })
  .refine((data) => data.newPassword === data.confirmPassword, {
    message: 'Passwords must match',
  });

This approach helps in catching errors early by strictly validating the payloads.

Authentication

Authentication is handled via dedicated service functions that interact with Supabase. The apiAuth.ts file contains functions for signing up users and updating their information. The authentication flow follows a separation of concerns principle, abstracting API communication from UI components, which then focus exclusively on data display and error messaging.

Here’s an example of the sign-up process:

/** Signs up a user with email and password */
export const signUpWithEmail = async ({
  fullName,
  email,
  password,
}: {
  fullName: string;
  email: string;
  password: string;
}) => {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: { data: { fullName, avatar: '' } },
  });
  if (error) throw new Error(error.message);
  return data;
};

And updating the current user's profile or password is also handled neatly:

/** Updates the user's name and profile image if the file exists */
export const updateCurrentUser = async ({
  password,
  fullName,
  avatar,
}: {
  password?: string;
  fullName?: string;
  avatar?: File | null;
}) => {
  const updateData = password ? { password } : { data: { fullName, avatar } };
  if (!avatar) delete updateData.data?.avatar;
  //? update password or name. Both can't be updated at the same time
  const { data, error: updateError } = await supabase.auth.update(updateData);
  if (updateError) throw new Error(updateError.message);
  return data;
};

Conclusion

In summary, the project demonstrates a robust architecture built with React and Vite, sophisticated state and data management through @tanstack/react-query, and a comprehensive error handling system covering both API and client-side validation. This results in a modular and maintainable codebase with clearly defined responsibilities.

© 2025 Amadou Seck. Published on aseck.io