Skip to main content

Company Profile

This guide covers viewing and updating your company profile information, including contact details, branding, and industry-specific settings.


Overview

What is the Company Profile?

The company profile contains essential information about your organisation:

  1. Basic Information - Company name, industry, contact details
  2. Address - Business address with UK postal code validation
  3. Branding - Company logo and visual identity
  4. Industry Template - Determines terminology and default settings
  5. Contact Information - Phone, email, and website

Access Levels

RoleViewEditRequest Changes
System AdminN/A
Company Manager
StaffLimited
note

Only System Administrators can directly modify company profile settings. Company Managers can request changes which notifies all System Admins for review.


Requesting Changes

For Company Managers

If you need to update company profile information, you can submit a change request:

  1. Navigate to DashboardCompany SettingsDetails tab
  2. Click the "Request Changes" button
  3. Enter a detailed description of the changes needed
  4. Submit the request

Example Requests

Address change:

"Our business has moved. Please update the address to:
123 New Street, London, W1A 1AA"

Contact update:

"Please update the main phone number to: 020 1234 5678
The old number has been disconnected."

Name change:

"Our practice has been renamed. Please change from
'Smith Medical Centre' to 'Smith & Partners Medical Practice'"

Viewing Company Profile

  1. Navigate to SettingsCompany Profile from the sidebar
  2. The company profile page displays current settings
  3. View all information including industry template and statistics

Company Profile API

Endpoint: GET /api/companies/[companyId]

// Source: src/app/api/companies/[companyId]/route.ts:22-80
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ companyId: string }> }
) {
const supabase = await createClient()
const resolvedParams = await params

// Fetch company with industry template
const { data: company, error } = await supabase
.from('companies')
.select(`
*,
industry_template:industry_templates (
id,
name,
description,
staff_terminology,
room_terminology,
shift_terminology
)
`)
.eq('id', resolvedParams.companyId)
.single()

// Get staff and manager counts
const { count: staffCount } = await supabase
.from('staff')
.select('*', { count: 'exact', head: true })
.eq('company_id', resolvedParams.companyId)
.eq('is_active', true)

const { count: managerCount } = await supabase
.from('staff')
.select('*', { count: 'exact', head: true })
.eq('company_id', resolvedParams.companyId)
.eq('is_company_manager', true)
.eq('is_active', true)

// Fetch opening hours
const { data: openingHours } = await supabase
.from('company_opening_hours')
.select('*')
.eq('company_id', resolvedParams.companyId)
.order('day_of_week')

return ApiResponses.success({
...companyAdapter.toComponent(company),
staffCount: staffCount || 0,
managerCount: managerCount || 0,
openingHours: openingHours || []
})
}

Response Data

// Company profile response structure
{
id: "company-uuid",
name: "Sunrise Medical Centre",
industry_template_id: "gp-template-uuid",
industry_template: {
id: "gp-template-uuid",
name: "GP Practice",
staff_terminology: "Staff",
room_terminology: "Consultation Room",
shift_terminology: "Shift"
},
address_line_1: "123 High Street",
address_line_2: "Suite 100",
city: "London",
postcode: "SW1A 1AA",
country: "United Kingdom",
phone: "+44 20 7123 4567",
email: "info@sunrisemedical.co.uk",
website: "https://sunrisemedical.co.uk",
logo_url: "https://storage.supabase.co/...",
is_active: true,
staffCount: 25,
managerCount: 3,
openingHours: [...]
}

Company Profile Fields

Basic Information

FieldDescriptionValidation
NameCompany/organisation name2-100 characters
Industry TemplateIndustry type (GP, Dental, etc.)Valid template ID
StatusActive/InactiveBoolean

Contact Details

FieldDescriptionValidation
PhonePrimary contact numberUK phone format
EmailPrimary email addressValid email format
WebsiteCompany website URLValid URL (optional)

Address Fields

FieldDescriptionValidation
Address Line 1Street addressRequired, max 200 chars
Address Line 2Additional address infoOptional, max 200 chars
CityCity/TownRequired, max 100 chars
PostcodeUK postal codeValid UK format
CountryCountry nameDefault: United Kingdom

Updating Company Profile

Update API

Endpoint: PUT /api/companies/[companyId]

Permission Required

Only System Administrators can update company profiles.

// Source: src/app/api/companies/[companyId]/route.ts:128-164
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ companyId: string }> }
) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()

// System Admin only
if (getAuthRole(user) !== 'SYSTEM_ADMIN') {
return ApiResponses.forbidden('Forbidden - System Admin access required')
}

const body = await request.json()
const validationResult = parseWithErrors(updateCompanySchema, body)

if (!validationResult.success) {
return ApiResponses.validationError(validationResult.errors)
}

const result = await companyDB.updateCompany(
resolvedParams.companyId,
validationResult.data
)

return ApiResponses.success(companyAdapter.toComponent(result))
}

Update Request Schema

// Source: src/lib/validation/company.schemas.ts:52-85
export const updateCompanySchema = z.object({
name: z.string().min(2).max(100).optional(),
industry_template_id: z.string().uuid().optional(),
address_line_1: z.string().max(200).optional(),
address_line_2: z.string().max(200).nullable().optional(),
city: z.string().max(100).optional(),
postcode: z.string()
.regex(/^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i, 'Invalid UK postal code')
.optional(),
country: z.string().max(100).optional(),
phone: z.string()
.regex(/^(\+44|0)\s?\d{2,4}\s?\d{3,4}\s?\d{3,4}$/, 'Invalid UK phone number')
.optional(),
email: z.string().email().optional(),
website: z.string().url().nullable().optional(),
is_active: z.boolean().optional()
})

Example Update Request

// Update company profile
const response = await fetch(`/api/companies/${companyId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: "Sunrise Medical Centre",
address_line_1: "456 New Street",
city: "Manchester",
postcode: "M1 1AA",
phone: "+44 161 123 4567",
email: "contact@sunrisemedical.co.uk"
})
})

const data = await response.json()

Validation Rules

UK Postal Code Validation

// Source: src/lib/validation/company.schemas.ts:12-15
const ukPostcodeRegex = /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i

// Valid examples:
// SW1A 1AA, M1 1AA, B1 1AA, EC1A 1BB
// sw1a1aa (case insensitive, spaces optional)

UK Phone Number Validation

// Source: src/lib/validation/company.schemas.ts:17-20
const ukPhoneRegex = /^(\+44|0)\s?\d{2,4}\s?\d{3,4}\s?\d{3,4}$/

// Valid examples:
// +44 20 7123 4567
// 020 7123 4567
// +44 161 123 4567
// 0161 123 4567

Email Validation

CheckRequirement
FormatValid email format (user@domain.tld)
LengthMaximum 254 characters
CaseCase-insensitive storage

Logo Management

Logo Upload

Company logos are stored in Supabase Storage:

// Logo upload process
const uploadLogo = async (file: File, companyId: string) => {
const supabase = createClient()

// Generate unique filename
const filename = `${companyId}/logo-${Date.now()}.${file.type.split('/')[1]}`

// Upload to company-logos bucket
const { data, error } = await supabase.storage
.from('company-logos')
.upload(filename, file, {
cacheControl: '3600',
upsert: true
})

if (error) throw error

// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('company-logos')
.getPublicUrl(filename)

// Update company with logo URL
await supabase
.from('companies')
.update({ logo_url: publicUrl })
.eq('id', companyId)

return publicUrl
}

Logo Requirements

RequirementValue
FormatPNG, JPG, SVG
Max Size2MB
Recommended200x200px minimum
Aspect RatioSquare preferred

Logo Display

// Logo component usage
<img
src={company.logo_url || '/default-logo.png'}
alt={`${company.name} logo`}
className="w-16 h-16 rounded-lg object-cover"
/>

Industry Templates

Available Templates

TemplateIDTerminology
GP Practicegp-templateStaff, Consultation Room, Shift
Dental Practicedental-templateTeam Member, Treatment Room, Appointment
Restaurantrestaurant-templateCrew, Section, Shift
Officeoffice-templateEmployee, Meeting Room, Schedule

Template Selection

Industry template affects:

  1. Terminology - Room names, staff titles, shift labels
  2. Default Roles - Pre-configured staff roles
  3. Room Types - Default room/facility types
  4. Shift Types - Standard shift patterns
// Industry template structure
interface IndustryTemplate {
id: string
name: string
description: string
staff_terminology: string // e.g., "Staff", "Team Member"
room_terminology: string // e.g., "Room", "Station"
shift_terminology: string // e.g., "Shift", "Schedule"
default_roles: StaffRole[]
default_room_types: RoomType[]
}

Company Statistics

Dashboard Statistics

The company profile includes real-time statistics:

StatisticDescription
Staff CountTotal active staff members
Manager CountStaff with Company Manager role
Room CountTotal configured rooms
Shift CountShifts in current period

Statistics Query

// Statistics fetched with company profile
const stats = {
staffCount: await countActiveStaff(companyId),
managerCount: await countManagers(companyId),
roomCount: await countRooms(companyId),
activeShifts: await countActiveShifts(companyId)
}

Deactivating a Company

Soft Delete

Companies are soft-deleted (deactivated) rather than permanently removed:

// Source: src/app/api/companies/[companyId]/route.ts:166-193
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ companyId: string }> }
) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()

// System Admin only
if (getAuthRole(user) !== 'SYSTEM_ADMIN') {
return ApiResponses.forbidden('Forbidden - System Admin access required')
}

// Soft delete - set is_active to false
const { error } = await supabase
.from('companies')
.update({ is_active: false })
.eq('id', resolvedParams.companyId)

return ApiResponses.success({ message: 'Company deactivated successfully' })
}

Deactivation Effects

When a company is deactivated:

  1. Login Blocked - Staff cannot log in
  2. Data Preserved - All data remains in database
  3. Reports Available - Historical reports still accessible
  4. Reactivation Possible - Can be reactivated by System Admin

Best Practices

For Managing Company Profile

  1. Keep Information Current - Update contact details promptly
  2. Use Professional Branding - Upload high-quality logo
  3. Verify Address - Ensure postal code is valid
  4. Complete All Fields - Fill in optional fields where applicable

For System Administrators

  1. Regular Audits - Review company profiles periodically
  2. Template Selection - Choose appropriate industry template
  3. Contact Verification - Verify phone and email work
  4. Backup Awareness - Understand soft delete preserves data

Troubleshooting

Common Issues

IssueCauseSolution
Cannot edit profileInsufficient permissionsContact System Admin
Invalid postcode errorWrong formatUse valid UK format (e.g., SW1A 1AA)
Logo upload failsFile too largeReduce image size below 2MB
Phone validation failsWrong formatUse UK format (+44 or 0 prefix)

Error Messages

ErrorMeaningAction
"Forbidden - System Admin access required"Not authorisedOnly System Admin can update
"Invalid UK postal code"Format errorCheck postal code format
"Company not found"Invalid IDVerify company ID
"Validation failed"Schema errorCheck all field requirements


Source Files:

  • src/app/api/companies/[companyId]/route.ts - Company CRUD API
  • src/lib/validation/company.schemas.ts - Validation schemas
  • src/lib/adapters/company.adapter.ts - Data transformation
  • src/lib/db/company.db.ts - Database operations