From 2e85e962abb945e8f9f98f9a8ea62aff47a88ca3 Mon Sep 17 00:00:00 2001 From: Naman Sharma Date: Thu, 23 Oct 2025 14:21:10 +0000 Subject: [PATCH 1/2] added admin company addition form --- frontend/src/app/router.tsx | 2 + .../company/components/AdminCompanyForm.tsx | 28 +++++ frontend/src/features/company/mutations.ts | 16 ++- .../company/pages/AdminCompanyAddPage.tsx | 102 ++++++++++++++++++ frontend/src/features/company/schema.ts | 14 ++- 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 frontend/src/features/company/components/AdminCompanyForm.tsx create mode 100644 frontend/src/features/company/pages/AdminCompanyAddPage.tsx 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..c374b9c --- /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..69f35e0 --- /dev/null +++ b/frontend/src/features/company/pages/AdminCompanyAddPage.tsx @@ -0,0 +1,102 @@ +// src/features/company/pages/AdminCompanyAddPage.tsx +import { useState } from 'react' +import { + Box, Button, Stack, Typography, Paper, Divider +} 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 ? ( + Loading options… + ) : ( + + )} + + + + + + + + + {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..3d69514 100644 --- a/frontend/src/features/company/schema.ts +++ b/frontend/src/features/company/schema.ts @@ -36,4 +36,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(2, 'Required'), + website: z.url('Invalid URL').optional().or(z.literal('')), + domain: z.string().min(1, 'Required'), + state: z.string().min(1, 'Required'), + city: z.string().min(2, 'Required'), + address: z.string().min(5, 'Required'), +}) + +export type AdminCompanyForm = z.infer \ No newline at end of file From 3fe52067a4a6e348ce0b675335f72e46c67af83d Mon Sep 17 00:00:00 2001 From: Naman Sharma Date: Wed, 29 Oct 2025 15:54:54 +0000 Subject: [PATCH 2/2] addressed and implemented the suggested changes in the PR --- .../company/components/AdminCompanyForm.tsx | 2 +- .../company/pages/AdminCompanyAddPage.tsx | 22 +++++++++--- frontend/src/features/company/schema.ts | 36 +++++++++++-------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/frontend/src/features/company/components/AdminCompanyForm.tsx b/frontend/src/features/company/components/AdminCompanyForm.tsx index c374b9c..fcdc561 100644 --- a/frontend/src/features/company/components/AdminCompanyForm.tsx +++ b/frontend/src/features/company/components/AdminCompanyForm.tsx @@ -7,7 +7,7 @@ export default function AdminCompanyForm({ }: { domains: string[]; states: { code: string; name: string }[] }) { return ( - + Add New Company - + {loadingMeta ? ( - Loading options… + + + + + + + + + + + + + ) : ( diff --git a/frontend/src/features/company/schema.ts b/frontend/src/features/company/schema.ts index 3d69514..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 @@ -40,12 +46,12 @@ export type RegistrationPayload = z.infer // Admin Company Addition (simplified, just company details) export const adminCompanySchema = z.object({ - name: z.string().min(2, 'Required'), - website: z.url('Invalid URL').optional().or(z.literal('')), - domain: z.string().min(1, 'Required'), - state: z.string().min(1, 'Required'), - city: z.string().min(2, 'Required'), - address: z.string().min(5, 'Required'), + 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