Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CristianParadaLopez/cv-builder/llms.txt

Use this file to discover all available pages before exploring further.

Skillara AI uses Firebase Authentication for user identity. Sign-in is optional — you can generate and download CVs without an account. An account is required only to save CVs to your personal dashboard, where they persist across sessions and devices.

Authentication provider

Skillara AI supports two sign-in methods through Firebase Auth:

Google OAuth

One-click sign-in via signInWithPopup using GoogleAuthProvider. This is the recommended and most prominent sign-in path in the UI.

Email & Password

Standard signInWithEmailAndPassword and createUserWithEmailAndPassword flows are also available on the /login page for users who prefer not to use Google.
Both methods are initialised from the same Firebase project config and share the same auth instance exported from firebase/config.ts:
import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);

export const auth = getAuth(app);
export const db = getFirestore(app);
export const googleProvider = new GoogleAuthProvider();

How it works

AuthProvider wraps the entire application and listens to Firebase’s onAuthStateChanged observer. When the observer fires, it updates the user state and sets loading to false. While the initial auth check is in progress, AuthProvider suppresses rendering of its children to prevent a flash of unauthenticated UI.
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsub = onAuthStateChanged(auth, (u) => {
      setUser(u);
      setLoading(false);
    });
    return unsub;
  }, []);

  const logout = () => signOut(auth);

  return (
    <AuthContext.Provider value={{ user, loading, logout }}>
      {!loading && children}
    </AuthContext.Provider>
  );
}
The useAuth() hook exposes three values to any component in the tree:
ValueTypeDescription
userUser | nullThe Firebase User object when signed in, or null when signed out
loadingbooleantrue while the initial onAuthStateChanged check is in progress
logout() => Promise<void>Calls signOut(auth) and resolves when the sign-out is complete
import { useAuth } from "../context/AuthContext";

function MyComponent() {
  const { user, loading, logout } = useAuth();

  if (loading) return <Spinner />;
  if (!user) return <p>Please sign in.</p>;

  return <p>Welcome, {user.displayName}</p>;
}

ProtectedRoute

The /dashboard route is wrapped in a ProtectedRoute component. If user is null, the component immediately redirects the visitor to /login using React Router’s <Navigate> with replace, preventing the protected URL from appearing in browser history. Because AuthProvider already suppresses its children until the initial auth check completes, ProtectedRoute only ever evaluates once the Firebase identity is known.
import { Navigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user } = useAuth();
  return user ? <>{children}</> : <Navigate to="/login" replace />;
}
1

User visits /dashboard

ProtectedRoute reads user from AuthContext.
2

Not signed in

The component renders <Navigate to="/login" replace />, sending the user to the login page without adding /dashboard to browser history.
3

Signs in successfully

Firebase calls onAuthStateChanged, user is set, and the dashboard renders.

Firestore data structure

Once signed in, user.uid is used as the top-level segment of the Firestore path for all CV documents belonging to that user:
users/{userId}/cvs/{cvId}
Each document in the cvs sub-collection conforms to the SavedCV interface:
export interface SavedCV {
  id: string;           // Firestore document ID
  title: string;        // Derived from formData.name
  html: string;         // Full generated HTML of the CV
  formData: any;        // Raw form values submitted by the user
  createdAt: any;       // Firestore serverTimestamp()
}

Setting up Firebase for self-hosting

To run Skillara AI with your own Firebase project, create a .env file in the frontend/ directory and populate all six required variables:
VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id
All six variables are required. If any value is missing, initializeApp will throw at runtime and the entire frontend will fail to load.
You also need to enable Google and Email/Password as sign-in providers in the Firebase console under Authentication → Sign-in method, and add your domain to the list of Authorized domains. For a complete walkthrough of all environment variables across both the frontend and backend, see Environment Variables.
The backend also defines a Prisma User model with a googleId String? @unique field, designed to mirror Firebase identities on the server side. However, the current UI relies exclusively on Firebase client-side authentication — the Prisma model is not yet wired into the auth flow and exists for future server-side use.

Build docs developers (and LLMs) love