import { useState, useEffect, ChangeEvent } from "react";
import Select, { SingleValue } from "react-select";
import axios from "axios";

interface BusinessCategory {
  value: string;
  label: string;
}

interface SignupData {
  email: string;
  fullName: string;
  password: string;
  category: string;
  businessName: string;
  phone?: string;
  website?: string;
  logo?: File | null;
}

const API_BASE_URL = "http://localhost:8080/api";

const SignupWizard = (): JSX.Element => {
  const [step, setStep] = useState<number>(1);
  const [userId, setUserId] = useState<number | null>(null);

  const [categories, setCategories] = useState<BusinessCategory[]>([]);
  const [loadingCategories, setLoadingCategories] = useState<boolean>(false);
  const [categoriesError, setCategoriesError] = useState<string | null>(null);

  const [signupData, setSignupData] = useState<SignupData>({
    email: "",
    fullName: "",
    password: "",
    category: "",
    businessName: "",
    phone: "",
    website: "",
    logo: null,
  });

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    setLoadingCategories(true);
    axios
      .get(`${API_BASE_URL}/business-categories`)
      .then((res) => {
        const options: BusinessCategory[] = res.data.map(
          (cat: { slug: string; name: string }) => ({
            value: cat.slug,
            label: cat.name,
          })
        );
        setCategories(options);
      })
      .catch(() => setCategoriesError("Failed to load business categories"))
      .finally(() => setLoadingCategories(false));
  }, []);

  function updateField<K extends keyof SignupData>(field: K, value: SignupData[K]) {
    setSignupData((prev) => ({ ...prev, [field]: value }));
  }

  function nextStep() {
    setStep((s) => Math.min(s + 1, 5));
  }
  function prevStep() {
    setStep((s) => Math.max(s - 1, 1));
  }

  // Step 1: Submit email
  async function submitEmail() {
    setError(null);
    setLoading(true);
    try {
      const res = await axios.post(`${API_BASE_URL}/signup/init`, {
        email: signupData.email,
      });
      if (res.data.success && res.data.userId) {
        setUserId(res.data.userId);
        nextStep();
      } else {
        setError(res.data.message || "Failed to submit email");
      }
    } catch (err: any) {
      setError(
        err.response?.data?.message || err.message || "Unknown error occurred"
      );
    }
    setLoading(false);
  }

  // Step 2: Submit credentials
  async function submitCredentials() {
    if (!userId) {
      setError("User ID missing");
      return;
    }
    setError(null);
    setLoading(true);
    try {
      const res = await axios.post(
        `${API_BASE_URL}/user/update-credentials?id=${userId}`,
        {
          fullName: signupData.fullName,
          password: signupData.password,
        }
      );
      if (res.data.success) nextStep();
      else setError(res.data.message || "Failed to submit credentials");
    } catch (err: any) {
      setError(
        err.response?.data?.message || err.message || "Unknown error occurred"
      );
    }
    setLoading(false);
  }

  // Step 3: Submit business info
  async function submitBusinessInfo() {
    if (!userId) {
      setError("User ID missing");
      return;
    }
    setError(null);
    setLoading(true);
    try {
      const res = await axios.post(
        `${API_BASE_URL}/user/business-info?id=${userId}`,
        {
          businessName: signupData.businessName,
          category: signupData.category,
          phone: signupData.phone,
          website: signupData.website,
        }
      );
      if (res.data.success) nextStep();
      else setError(res.data.message || "Failed to submit business info");
    } catch (err: any) {
      setError(
        err.response?.data?.message || err.message || "Unknown error occurred"
      );
    }
    setLoading(false);
  }

  // Step 4: Upload logo
  async function uploadLogo() {
    if (!userId) {
      setError("User ID missing");
      return;
    }
    if (!signupData.logo) {
      nextStep();
      return;
    }
    setError(null);
    setLoading(true);

    try {
      const formData = new FormData();
      formData.append("logo", signupData.logo);

      const res = await axios.post(
        `${API_BASE_URL}/user/upload-logo?id=${userId}`,
        formData,
        {
          headers: { "Content-Type": "multipart/form-data" },
        }
      );
      if (res.data.success) nextStep();
      else setError(res.data.message || "Failed to upload logo");
    } catch (err: any) {
      setError(
        err.response?.data?.message || err.message || "Unknown error occurred"
      );
    }
    setLoading(false);
  }

  // Final submit handler
  function finalSubmit() {
    alert("Signup complete! Thank you!");
    // Optionally reset or redirect here
  }

  function handleInputChange(
    e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>
  ) {
    const { name, value, files } = e.target as HTMLInputElement;
    if (name === "logo") {
      updateField("logo", files ? files[0] : null);
    } else {
      updateField(name as keyof SignupData, value as any);
    }
  }

  function handleCategoryChange(
    selected: SingleValue<BusinessCategory> | null
  ): void {
    updateField("category", selected ? selected.value : "");
  }

  return (
    <div className="container mt-4" style={{ maxWidth: 480 }}>
      <h2 className="mb-4">Sign Up - Step {step} of 5</h2>

      {error && <div className="alert alert-danger">{error}</div>}

      {step === 1 && (
        <>
          <div className="mb-3">
            <label htmlFor="email" className="form-label">
              Email address
            </label>
            <input
              type="email"
              id="email"
              name="email"
              className="form-control"
              value={signupData.email}
              onChange={handleInputChange}
              disabled={loading}
              required
            />
          </div>
          <button
            className="btn btn-primary"
            onClick={submitEmail}
            disabled={loading || !signupData.email.includes("@")}
          >
            {loading ? "Submitting..." : "Next"}
          </button>
        </>
      )}

      {step === 2 && (
        <>
          <div className="mb-3">
            <label htmlFor="fullName" className="form-label">
              Full Name
            </label>
            <input
              type="text"
              id="fullName"
              name="fullName"
              className="form-control"
              value={signupData.fullName}
              onChange={handleInputChange}
              disabled={loading}
              required
            />
          </div>

          <div className="mb-3">
            <label htmlFor="password" className="form-label">
              Password (min 6 characters)
            </label>
            <input
              type="password"
              id="password"
              name="password"
              className="form-control"
              value={signupData.password}
              onChange={handleInputChange}
              disabled={loading}
              required
              minLength={6}
            />
          </div>

          <div>
            <button
              className="btn btn-secondary me-2"
              onClick={prevStep}
              disabled={loading}
            >
              Back
            </button>
            <button
              className="btn btn-primary"
              onClick={submitCredentials}
              disabled={
                loading ||
                !signupData.fullName.trim() ||
                signupData.password.length < 6
              }
            >
              {loading ? "Submitting..." : "Next"}
            </button>
          </div>
        </>
      )}

      {step === 3 && (
        <>
          <div className="mb-3">
            <label htmlFor="businessName" className="form-label">
              Business Name
            </label>
            <input
              type="text"
              id="businessName"
              name="businessName"
              className="form-control"
              value={signupData.businessName}
              onChange={handleInputChange}
              disabled={loading}
              required
            />
          </div>

          <div className="mb-3">
            <label htmlFor="category" className="form-label">
              Business Category
            </label>
            {loadingCategories ? (
              <p>Loading categories...</p>
            ) : categoriesError ? (
              <div className="text-danger">{categoriesError}</div>
            ) : (
              <Select
                inputId="category"
                options={categories}
                value={categories.find(
                  (c) => c.value === signupData.category
                ) || null}
                onChange={handleCategoryChange}
                isClearable
                isSearchable
                placeholder="Select your business category"
                isDisabled={loading}
                classNamePrefix="react-select"
              />
            )}
          </div>

          <div className="border p-3 mb-3">
            <strong>Preview:</strong>
            <div>{signupData.businessName || "[Business Name]"}</div>
            <small>
              {categories.find((c) => c.value === signupData.category)?.label ||
                "[Category]"}
            </small>
          </div>

          <div>
            <button
              className="btn btn-secondary me-2"
              onClick={prevStep}
              disabled={loading}
            >
              Back
            </button>
            <button
              className="btn btn-primary"
              onClick={submitBusinessInfo}
              disabled={
                loading ||
                !signupData.businessName.trim() ||
                !signupData.category.trim()
              }
            >
              {loading ? "Submitting..." : "Next"}
            </button>
          </div>
        </>
      )}

      {step === 4 && (
        <>
          <div className="mb-3">
            <label htmlFor="phone" className="form-label">
              Phone (optional)
            </label>
            <input
              type="tel"
              id="phone"
              name="phone"
              className="form-control"
              value={signupData.phone}
              onChange={handleInputChange}
              disabled={loading}
              placeholder="+1 555 1234"
            />
          </div>

          <div className="mb-3">
            <label htmlFor="website" className="form-label">
              Website (optional)
            </label>
            <input
              type="url"
              id="website"
              name="website"
              className="form-control"
              value={signupData.website}
              onChange={handleInputChange}
              disabled={loading}
              placeholder="https://yourbusiness.com"
            />
          </div>

          <div className="mb-3">
            <label htmlFor="logo" className="form-label">
              Upload Logo (optional)
            </label>
            <input
              id="logo"
              name="logo"
              type="file"
              accept="image/*"
              className="form-control"
              onChange={handleInputChange}
              disabled={loading}
            />
          </div>

          <div>
            <button
              className="btn btn-secondary me-2"
              onClick={prevStep}
              disabled={loading}
            >
              Back
            </button>
            <button
              className="btn btn-primary me-2"
              onClick={uploadLogo}
              disabled={loading}
            >
              {loading ? "Uploading..." : "Next"}
            </button>
            <button
              className="btn btn-outline-secondary"
              onClick={() => setStep(5)}
              disabled={loading}
            >
              Skip
            </button>
          </div>
        </>
      )}

      {step === 5 && (
        <>
          <h3>Review & Confirm</h3>
          <ul className="list-group mb-3">
            <li className="list-group-item">
              <strong>Email:</strong> {signupData.email}
            </li>
            <li className="list-group-item">
              <strong>Full Name:</strong> {signupData.fullName}
            </li>
            <li className="list-group-item">
              <strong>Business Name:</strong> {signupData.businessName}
            </li>
            <li className="list-group-item">
              <strong>Business Category:</strong>{" "}
              {categories.find((c) => c.value === signupData.category)?.label ||
                "N/A"}
            </li>
            <li className="list-group-item">
              <strong>Phone:</strong> {signupData.phone || "N/A"}
            </li>
            <li className="list-group-item">
              <strong>Website:</strong> {signupData.website || "N/A"}
            </li>
            <li className="list-group-item">
              <strong>Logo:</strong>{" "}
              {signupData.logo ? signupData.logo.name : "No logo uploaded"}
            </li>
          </ul>
          <button
            className="btn btn-secondary me-2"
            onClick={prevStep}
            disabled={loading}
          >
            Back
          </button>
          <button
            className="btn btn-success"
            onClick={finalSubmit}
            disabled={loading}
          >
            Confirm & Submit
          </button>
        </>
      )}
    </div>
  );
};

export default SignupWizard;

I want to break this code up like:

// src/services/signupService.ts
import apiClient from './apiClient';
import { ENDPOINTS } from '../constants/api';

export interface SignupData
{
	email: string;
	fullname: string;
	password: string;
	category: string;
	businessName: string;
	discription?: string;
	phone?: string;
	website?: string;
	logo?: File | null;
}

export interface CsrfData
{
	csrfParam: string;
	csrfToken: string;
}

export async function signup(data: SignupData, csrf: CsrfData)
{
	const postData =
	{
		...data,
		[csrf.csrfParam]: csrf.csrfToken,
	};
	
	const response = await apiClient.post(ENDPOINTS.account.signup, postData,
	{
		headers:
		{
			'X-CSRF-Token': csrf.csrfToken,
		},
	});
	
	return response.data;
}


the apiClient is this:

import axios from 'axios';
import { API_BASE_URL } from '../constants/api';
import { emitEvent } from '../utils/eventBus';
import logger from '../utils/logger';

const apiClient = axios.create({
	baseURL: API_BASE_URL,
	withCredentials: true,// send cookies for CORS requests automatically
	headers:
	{
		'Content-Type': 'application/json',
	},
	
	// timeout, etc.
});

/**
* Add request interceptor
*
* Axios interceptors allow you to intercept and modify requests or responses globally before 
* they are handled by your individual API calls.
* - Request interceptors run before a request is sent, letting you add headers (e.g., auth tokens), 
*   modify request config, or log requests.
* - Response interceptors run after receiving a response, enabling centralized error handling, token 
*   refresh logic, or response transformation.
* 
* Using interceptors helps keep your API client DRY, consistent, and easier to maintain.
*/
apiClient.interceptors.request.use((config) =>
{
	// e.g. attach Authorization token here if using JWT
	const token = localStorage.getItem('token');
	
	if (token)
	{
		config.headers['Authorization'] = `Bearer ${token}`;
	}
	
	return config;
});

/**
* Add response interceptor to handle errors globally, refresh tokens, etc.
*/
apiClient.interceptors.response.use(
	(response) => response,// Pass through successful responses unchanged
	async (error) =>
	{
		const status = error.response?.status;
		switch (status)
		{
			case 400:
				// Bad Request — maybe validation failed
				logger.warn('400 Bad Request:', error);
				emitEvent('alert', { type: 'warning', key: 'alert.badRequest' });
				break;
				
			case 401:
				// Unauthorized — handle auth refresh or redirect login
				logger.error('Error 401 Unauthorized:', error);
				emitEvent('alert', { type: 'danger', key: 'alert.unauthorized' });
				
				// TODO: Add logic to refresh auth token here and retry the original request
				// Or redirect the user to the login page if refresh is unavailable or fails
				
				/* Example pseudo-code:
				const refreshed = await tryRefreshToken();
				if (refreshed)
				{
					// Update the failed request with new token and retry
					error.config.headers['Authorization'] = `Bearer ${newToken}`;
					return apiClient.request(error.config);
				}
				else
				{
					redirectToLogin();
				}//*/
				break;
			
			case 403:
				// Forbidden — user lacks permissions
				logger.warn('403 Forbidden:', error);
				emitEvent('alert', { type: 'danger', key: 'alert.forbidden' });
				// Maybe redirect or show permission error dialog
				break;
				
			case 404:
				// Not Found — resource missing
				logger.info('404 Not Found:', error);
				emitEvent('alert', { type: 'info', key: 'alert.notFound' });
				break;
				
			case 500:
				// Server error — inform user, retry options, etc
				logger.error('500 Server Error:', error);
				emitEvent('alert', { type: 'danger', key: 'alert.serverError' });
				break;
				
			default:
				// Other errors: network issues, etc.
				logger.error('Unhandled error:', error);
				emitEvent('alert', { type: 'danger', key: 'alert.unknownError' });
		}
	
		// handle 401, token refresh, global error logging here
		return Promise.reject(error);
	}
);

export default apiClient;


and the csrf-token:

// src/services/csrfService.ts
import apiClient from './apiClient';
import { ENDPOINTS } from '../constants/api';

export type CsrfData =
{
	csrfParam: string;
	csrfToken: string;
};

export async function fetchCsrfToken(): Promise<CsrfData>
{
	const response = await apiClient.get(ENDPOINTS.security.csrfToken);
	const { csrfParam, csrfToken } = response.data;
	
	if (!csrfParam || !csrfToken)
	{
		throw new Error('Failed to fetch CSRF token');
	}
	
	return { csrfParam, csrfToken };
}


also the api.ts

// app/constants/api.ts

// ENV uses "http://url:port/"
export const API_BASE_URL = process.env.REACT_APP_API_URL;// || 'https://your.api.url';

/**
* How to use API:
*
* 	import { ENDPOINTS } from '../../constants/api';
*	const response = await axios.post(ENDPOINTS.account.signup, data);
* 
* For a contact detail fetch:
*
*	const contactId = 42;
*	const response = await axios.get(ENDPOINTS.contact.view(contactId));
*
* For uploading an image to a layout:
* 
*	const layoutId = 123;
*	const formData = new FormData();
*	// append files to formData...
*	
*	await axios.post(ENDPOINTS.layouts.uploadImages(layoutId), formData, {
*		headers: {
*			'Content-Type': 'multipart/form-data',
*		},
*	});
*/
export const ENDPOINTS =
{
	security: {
		csrfToken: `security/csrf-token`,// GET
	},
	account: {
		login: `account/login`,// POST
		signup: `account/signup`,// POST
		settings: `account/settings`,// GET
		updateSettings: `account/settings/update`,// POST
		changePassword: `account/settings/change-password`,// POST
		deleteAccount: `account/settings/delete-account`,// POST
	},
	contact: {
		// RESTful endpoints for contacts, replace :id dynamically
		list: `contacts`,// GET
		create: `contacts/create`,// POST (if used)
		view: (id: number | string) => `contacts/${id}`,// GET
		update: (id: number | string) => `contacts/update/${id}`,// PUT/POST
		delete: (id: number | string) => `contacts/delete/${id}`,// DELETE or POST as per backend
	},
	error: {
		list: `error`,// GET
		create: `error/create`,// POST
		view: (id: number | string) => `error/${id}`,// GET
		update: (id: number | string) => `error/update/${id}`,// PUT/POST
	},
	layout: {
		list: `layouts`,// GET
		view: (id: number | string) => `layouts/${id}`,// GET
		create: `layouts/create`,// POST
		update: (id: number | string) => `layouts/update/${id}`,// PUT/POST
		delete: (id: number | string) => `layouts/delete/${id}`,// DELETE or POST
		uploadImages: (id: number | string) => `layouts/${id}/images`,// POST
		removeImages: (id: number | string) => `layouts/${id}/images`,// DELETE
	},
	
	// add other endpoints here
};


which ill need to add extra api calls for the signup wizard