The platform includes a public marketplace where professionals (architects, designers, consultants, etc.) can publish profiles, list services, display skills, and be discovered by others.
| Route | Component | Access |
|---|---|---|
/discover |
DiscoverPage |
All authenticated users |
/u/:userId |
PublicProfilePage |
Public (unauthenticated too) |
Profiles are opt-in public. The user_profiles.is_public boolean controls visibility:
is_public = false (default) — profile is private, not listed in /discover, /u/:id returns "not found"is_public = true — profile appears in directory and public profile page is accessibleUsers toggle this in their profile settings (ProfileTab).
Stored in user_profiles table:
| Field | Type | Description |
|---|---|---|
full_name |
varchar | Display name |
company |
text | Company or studio name |
bio |
text | Short personal/professional description |
avatar_url |
text | Profile photo URL |
location |
text | City, country, or region |
website_url |
text | External website link |
professional_type |
enum | Role classification (see below) |
is_public |
boolean | Controls public visibility |
services |
text[] | Legacy simple service name list |
services_detail |
jsonb | Rich service objects (preferred, see below) |
skill_tags |
text[] | Free-form skill/expertise tags |
preferred_factories |
jsonb | Array of { name, country? } factory references |
featured_moodboard_id |
uuid | FK to moodboards — pinned to profile top |
profile_views |
integer | Incremented on each public profile view via increment_profile_views() RPC |
designer | interior_designer | architect | manufacturer |
brand | supplier | sourcing_agent | consultant | other
Services are stored in two columns for backwards compatibility:
services (text[]) — legacy, name-only listservices_detail (jsonb) — rich objects, preferred when availableinterface ServiceItem {
id: string;
name: string;
description?: string;
price?: string;
previous_work?: { title: string; url?: string }[];
}
On the public profile, if services_detail is populated it takes precedence over services. Each service card has:
HireMeModalVisitors can contact a professional directly from their public profile.
HireMeModal opens with optional service pre-selectedprofile_contact_requests and emits a hire_me_received flow eventprofile_contact_requests| Column | Type | Notes |
|---|---|---|
id |
uuid | PK |
to_user_id |
uuid | FK to user_profiles.user_id |
from_name |
text | Requester's name |
from_email |
text | Requester's email |
message |
text | Message body |
services_requested |
text[] | Selected service names (nullable) |
created_at |
timestamptz | Auto |
Emits hire_me_received via flowEventService — can be used to trigger automated flows (e.g., email notification to the professional).
/discover)Lists all public profiles ordered by profile_views DESC, limit 60.
user_follows table, FollowButton componentSELECT user_id, full_name, company, bio, avatar_url, location,
website_url, services, skill_tags, profile_views, professional_type
FROM user_profiles
WHERE is_public = true
ORDER BY profile_views DESC
LIMIT 60
Follower counts are fetched in a second query and merged client-side.
/u/:userId)Full profile view for a single user.
FollowButton component, stored in user_followsMoodboardComments component inline on each boardincrement_profile_views(p_user_id) RPC (fire-and-forget)| Event | When |
|---|---|
hire_me_received |
Hire Me form submitted |
profile_followed |
A user follows a public profile |
profile_published |
User makes their profile public |
preferred_factory_added |
User adds a preferred factory |
These can be used in the Flow Builder to trigger automated actions (emails, notifications, etc.).
| Component | Path |
|---|---|
| Profile editing | src/components/core/Profile/ProfileTab.tsx |
| Public profile page | src/pages/PublicProfilePage.tsx |
| Discover directory | src/pages/DiscoverPage.tsx |
| Hire Me modal | src/components/core/Profile/HireMeModal.tsx |
| Follow button | src/components/features/social/FollowButton.tsx |
| Moodboard comments | src/components/features/social/MoodboardComments.tsx |
| Reviews display | src/components/features/profile/ReviewsSection.tsx |
| Review modal | src/components/features/profile/ReviewModal.tsx |
| Booking widget (public) | src/components/features/profile/BookingWidget.tsx |
| Booking modal | src/components/features/profile/BookingModal.tsx |
| Appointments dashboard | src/pages/AppointmentsPage.tsx |
profile_reviews — one review per reviewer per professional. Columns: to_user_id, from_user_id, overall_rating (1–5), dimension_ratings JSONB (communication, expertise, timeliness, value), comment, service_name, reply (professional's reply), is_verified.review_summaries — cached AI-generated summary per professional (summary_text, last_computed_at).Displays: aggregate score card, AI summary, per-dimension stars, individual reviews, reply thread. Authenticated users (not the owner) see "Write a Review" button.
appointment_availability — per-user per-day availability. Columns: day_of_week (0–6), slots (text[]), timezone, is_active.appointments — bookings. Columns: professional_user_id, client_*, appointment_date, appointment_time, status (pending/confirmed/cancelled/completed), notes, inbox_conversation_id.| Route | Component | Access |
|---|---|---|
/appointments |
AppointmentsPage |
Authenticated (own appointments only) |
| Event | When |
|---|---|
appointment_booked |
Client submits booking |
appointment_confirmed |
Professional confirms |
appointment_cancelled |
Either party cancels |
appointment_moved_to_inbox |
Professional clicks "Email Client" |
"Email Client" in AppointmentDetailDrawer creates a new agent_chat_conversations row (agent_id='kai') pre-seeded with appointment context, saves the conversation_id back to appointments.inbox_conversation_id, then navigates to /agent-hub?conversation=<id>.