diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx
index 33f4c5b..9553d6c 100644
--- a/frontend/src/app/router.tsx
+++ b/frontend/src/app/router.tsx
@@ -28,6 +28,7 @@ import AdminCompanies from '@features/admin/AdminCompanies';
import AdminJobs from '@features/admin/AdminJobs';
import AdminStudents from '@features/admin/AdminStudents';
import AdminEmails from '@features/admin/AdminEmails';
+import AdminCompanyAddPage from '@features/company/pages/AdminCompanyAddPage';
export const router = createBrowserRouter([
@@ -72,6 +73,7 @@ export const router = createBrowserRouter([
children: [
{ index: true, element: },
{ path: "companies", element: },
+ { path: "companies/add", element: },
{ path: "jobs", element: },
{ path: "students", element: },
{ path: "emails", element: },
diff --git a/frontend/src/features/company/components/AdminCompanyForm.tsx b/frontend/src/features/company/components/AdminCompanyForm.tsx
new file mode 100644
index 0000000..fcdc561
--- /dev/null
+++ b/frontend/src/features/company/components/AdminCompanyForm.tsx
@@ -0,0 +1,28 @@
+import RHFTextField from '@shared/RHF/RHFTextField'
+import RHFAutocomplete from '@shared/RHF/RHFAutocomplete';
+import { Stack } from '@mui/material'
+
+export default function AdminCompanyForm({
+ domains, states
+}: { domains: string[]; states: { code: string; name: string }[] }) {
+ return (
+
+
+
+
+ ({ label: s.name, value: s.name }))}
+ fullWidth
+ />
+
+
+
+ )
+}
diff --git a/frontend/src/features/company/mutations.ts b/frontend/src/features/company/mutations.ts
index d988f74..7b6c24b 100644
--- a/frontend/src/features/company/mutations.ts
+++ b/frontend/src/features/company/mutations.ts
@@ -1,7 +1,7 @@
// React Query hooks
import { useMutation } from "@tanstack/react-query";
import { api } from "@app/axios";
-import type { RegistrationPayload } from "./schema";
+import type { RegistrationPayload, AdminCompanyForm } from "./schema";
// --- Registration mutation (company + employer + optional job) ---
export function useRegisterCompany() {
@@ -20,4 +20,18 @@ export function useRegisterCompany() {
}
}
})
+}
+
+// --- Admin: Add Company mutation ---
+export function useAddCompany() {
+ return useMutation({
+ mutationFn: async (payload: AdminCompanyForm) => {
+ const url = `/admin/companies/`;
+ const res = await api.post(url, payload);
+ return res.data as {
+ company_id: number;
+ name: string;
+ }
+ }
+ })
}
\ No newline at end of file
diff --git a/frontend/src/features/company/pages/AdminCompanyAddPage.tsx b/frontend/src/features/company/pages/AdminCompanyAddPage.tsx
new file mode 100644
index 0000000..55d38c2
--- /dev/null
+++ b/frontend/src/features/company/pages/AdminCompanyAddPage.tsx
@@ -0,0 +1,114 @@
+// src/features/company/pages/AdminCompanyAddPage.tsx
+import { useState } from 'react'
+import {
+ Box, Button, Stack, Typography, Paper, Divider, Skeleton
+} from '@mui/material'
+import { FormProvider, useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { useNavigate } from 'react-router-dom'
+
+import AdminCompanyForm from '../components/AdminCompanyForm'
+import { adminCompanySchema, type AdminCompanyForm as AdminCompanyFormType } from '../schema'
+import { useMetaOptions } from '../queries'
+import { useAddCompany } from '../mutations'
+
+export default function AdminCompanyAddPage() {
+ const navigate = useNavigate()
+ const { data: options, isLoading: loadingMeta } = useMetaOptions()
+ const addCompany = useAddCompany()
+
+ const methods = useForm({
+ resolver: zodResolver(adminCompanySchema),
+ defaultValues: {
+ name: '',
+ website: '',
+ domain: '',
+ state: '',
+ city: '',
+ address: ''
+ },
+ mode: 'onTouched'
+ })
+
+ const [submitted, setSubmitted] = useState(null)
+
+ const onSubmit = async (data: AdminCompanyFormType) => {
+ try {
+ const result = await addCompany.mutateAsync(data)
+ setSubmitted({ success: true, companyId: result.company_id })
+ } catch (e) {
+ setSubmitted({ success: false })
+ }
+ }
+
+ // Success screen
+ if (submitted?.success) {
+ return (
+
+
+ ✅ Company added successfully!
+
+
+ Company ID: {submitted.companyId}
+
+
+
+
+
+
+
+ )
+ }
+
+ return (
+
+ Add New Company
+
+
+
+ {loadingMeta ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ {addCompany.isError && (
+ Something went wrong. Please try again.
+ )}
+
+ )
+}
diff --git a/frontend/src/features/company/schema.ts b/frontend/src/features/company/schema.ts
index d61f623..e1383f1 100644
--- a/frontend/src/features/company/schema.ts
+++ b/frontend/src/features/company/schema.ts
@@ -2,29 +2,35 @@
import { domain } from 'node_modules/zod/v4/core/regexes.cjs';
import { z } from 'zod';
+// Helper function to convert empty strings to undefined
+const emptyToUndefined = (val: string) => (val === '' ? undefined : val);
+
// Company Details
export const companySchema = z.object({
- name: z.string().min(2, 'Required'),
- website: z.url('Invalid URL').optional().or(z.literal('')),
- domain: z.string().min(1, 'Required'), // e.g., "Software", "Edu"
+ name: z.string().min(2, 'Company name must be at least 2 characters'),
+ website: z.preprocess(
+ emptyToUndefined,
+ z.string().url('Please enter a valid URL').optional()
+ ),
+ domain: z.string().min(1, 'Please select a domain'), // e.g., "Software", "Edu"
})
export type CompanyForm = z.infer
// Employer Details
export const employerSchema = z.object({
- first_name: z.string().min(2, 'Required'),
- last_name: z.string().min(2, 'Required'),
- email: z.email('Invalid Email'),
- phone: z.string().min(8, 'Invalid Phone')
+ first_name: z.string().min(2, 'First name must be at least 2 characters'),
+ last_name: z.string().min(2, 'Last name must be at least 2 characters'),
+ email: z.string().email('Please enter a valid email address'),
+ phone: z.string().min(8, 'Phone number must be at least 8 digits')
})
export type EmployerForm = z.infer
// Job Details
export const jobSchema = z.object({
- title: z.string().min(2, 'Required'),
- description: z.string().min(10, 'Required'),
+ title: z.string().min(2, 'Job title must be at least 2 characters'),
+ description: z.string().min(10, 'Job description must be at least 10 characters'),
})
export type JobForm = z.infer
@@ -36,4 +42,16 @@ export const registrationSchema = z.object({
job: jobSchema.optional() // present if not skipped
})
-export type RegistrationPayload = z.infer
\ No newline at end of file
+export type RegistrationPayload = z.infer
+
+// Admin Company Addition (simplified, just company details)
+export const adminCompanySchema = z.object({
+ name: z.string().min(5, 'Must be at least 5 characters'),
+ website: z.string().url('Please enter a valid URL'),
+ domain: z.string().min(1, 'Please select a domain'),
+ state: z.string().min(1, 'Please select a state'),
+ city: z.string().min(2, 'City name must be at least 2 characters'),
+ address: z.string().min(5, 'Address must be at least 5 characters'),
+})
+
+export type AdminCompanyForm = z.infer
\ No newline at end of file