Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
db655fe
refactor(media-tab): extract WorkspaceDialog reusable component
derianrddev Dec 11, 2025
bd12611
feat(media-tab): add BrandKitEntry component for brand creation and i…
derianrddev Dec 11, 2025
3414201
feat(media-tab): implement BrandKitWizard with steps for brand creation
derianrddev Dec 11, 2025
c618117
feat(media-tab): add BrandKitDialog and integrate with MediaWizards a…
derianrddev Dec 11, 2025
f5a6091
feat(media-tab): implement brand kit wizard with vibe selection step
derianrddev Dec 11, 2025
4208e89
feat(media-tab): implement fonts step with vibe-based recommendations
derianrddev Dec 16, 2025
6dcbf74
refactor(media-tab): improve fonts step with sticky selection bar and…
derianrddev Dec 16, 2025
f64b397
feat(media-tab): implement colors step with palette selection and liv…
derianrddev Dec 17, 2025
c3f7a41
feat(media-tab): enhance ColorsStep layout with improved scrolling an…
derianrddev Dec 18, 2025
5c360e1
feat(media-tab): refactor ColorsStep for theme-based color adjustment…
derianrddev Dec 22, 2025
0d53a7c
feat(media-tab): add checkpoint step to brand kit wizard
derianrddev Dec 22, 2025
5466885
fix(media-tab): adapt checkpoint step for single source and validate …
derianrddev Dec 23, 2025
0c43c43
feat(media-tab): add persistence with IDB sync and checkpoint handlers
derianrddev Dec 26, 2025
3e34d4d
fix(media-tab): restore wizard step when changing organizations
derianrddev Dec 29, 2025
0e113d2
feat(hasura): add brand_kit jsonb column to organization
derianrddev Dec 29, 2025
6037eb6
fix(media-tab): improve validation and error handling for brand kit flow
derianrddev Dec 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ insert_permissions:
permission:
check: {}
columns:
- brand_kit
- name
- user_id
comment: ""
Expand All @@ -36,23 +37,26 @@ insert_permissions:
user_id:
_eq: X-Hasura-User-Id
columns:
- brand_kit
- name
- user_id
comment: ""
select_permissions:
- role: moderator
permission:
columns:
- organization_id
- brand_kit
- name
- organization_id
- user_id
filter: {}
comment: ""
- role: user
permission:
columns:
- organization_id
- brand_kit
- name
- organization_id
- user_id
filter:
user_id:
Expand All @@ -62,13 +66,15 @@ update_permissions:
- role: moderator
permission:
columns:
- brand_kit
- name
filter: {}
check: null
comment: ""
- role: user
permission:
columns:
- brand_kit
- name
filter:
user_id:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table "public"."organization"
drop column "brand_kit";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table "public"."organization" add column "brand_kit" jsonb
not null default '{}'::jsonb;
2 changes: 2 additions & 0 deletions apps/pro-web/app/actions/thread.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ export async function getUserOrganizations(
organizationId: true,
name: true,
userId: true,
brandKit: true,
organization_chatbots: {
chatbotId: true,
isActive: true,
Expand All @@ -575,6 +576,7 @@ export async function getUserOrganizations(
(org): OrganizationData => ({
id: org.organizationId?.toString() || '',
name: org.name || '',
brandKit: org.brandKit || null,
chatbots: org.organization_chatbots?.map((oc) => ({
chatbotId: oc.chatbotId || 0,
isActive: oc.isActive || false,
Expand Down
26 changes: 26 additions & 0 deletions apps/pro-web/app/api/organizations/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { auth } from '@/auth'
import { logErrorToSentry } from '@/lib/sentry'
import type { BrandKitData } from '@/types/media.types'
import type { OrganizationChatbot } from '@/types/thread.types'
import { getHasuraClient } from 'mb-lib'
import type { NextRequest } from 'next/server'
Expand All @@ -8,6 +9,7 @@ import { NextResponse } from 'next/server'
interface UpdateOrganizationBody {
name?: string
chatbots?: OrganizationChatbot[]
brandKit?: BrandKitData
}

/**
Expand Down Expand Up @@ -65,6 +67,14 @@ export async function PATCH(
)
}

// Validate brandKit if provided
if (body.brandKit !== undefined && typeof body.brandKit !== 'object') {
return NextResponse.json(
{ error: 'Brand Kit must be an object' },
{ status: 422 },
)
}

// Initialize Hasura client
const client = getHasuraClient()

Expand Down Expand Up @@ -163,6 +173,20 @@ export async function PATCH(
}
}

// Update brandKit if provided
if (body.brandKit !== undefined) {
await client.mutation({
updateOrganizationByPk: {
__args: {
pkColumns: { organizationId: id },
_set: { brandKit: body.brandKit },
},
organizationId: true,
brandKit: true,
},
})
}

// Fetch and return the updated organization
const { organizationByPk } = await client.query({
organizationByPk: {
Expand All @@ -172,6 +196,7 @@ export async function PATCH(
organizationId: true,
name: true,
userId: true,
brandKit: true,
organization_chatbots: {
chatbotId: true,
isActive: true,
Expand All @@ -188,6 +213,7 @@ export async function PATCH(
id: organizationByPk?.organizationId,
name: organizationByPk?.name,
userId: organizationByPk?.userId,
brandKit: organizationByPk?.brandKit,
chatbots: organizationByPk?.organization_chatbots?.map((oc) => ({
chatbotId: oc.chatbotId,
isActive: oc.isActive,
Expand Down
2 changes: 2 additions & 0 deletions apps/pro-web/app/api/organizations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export async function GET() {
organizationId: true,
name: true,
userId: true,
brandKit: true,
organization_chatbots: {
chatbotId: true,
isActive: true,
Expand All @@ -59,6 +60,7 @@ export async function GET() {
id: org.organizationId,
name: org.name,
userId: org.userId,
brandKit: org.brandKit,
chatbots: org.organization_chatbots?.map((oc) => ({
chatbotId: oc.chatbotId,
isActive: oc.isActive,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client'

import { WorkspaceDialog } from '@/components/ui/workspace-dialog'
import { useWorkspace } from '@/lib/hooks/use-workspace'
import { useMemo, useState } from 'react'
import { BrandKitEntry } from './brand-kit-entry'
import { BrandKitWizard } from './wizard/brand-kit-wizard'

interface BrandKitDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}

type BrandKitView = 'entry' | 'wizard' | 'summary'

export function BrandKitDialog({ open, onOpenChange }: BrandKitDialogProps) {
const [currentView, setCurrentView] = useState<BrandKitView>('entry')
const { activeOrganization } = useWorkspace()

const subtitle = useMemo(() => {
if (currentView === 'wizard') {
return activeOrganization
? `${activeOrganization}' Logo & Style`
: 'Logo & Style'
}
return 'Brand Kit'
}, [currentView, activeOrganization])

const handleNewBrand = () => {
setCurrentView('wizard')
}

const handleImportBrand = () => {
// TODO: Open brand import flow
// This could trigger a file picker or navigate to import wizard
console.log('Importing brand...')
}

const handleWizardFinish = () => {
// TODO: Navigate to summary view
console.log('Wizard finished, navigating to summary...')
setCurrentView('summary')
}

const handleWizardCancel = () => {
setCurrentView('entry')
}

return (
<WorkspaceDialog
open={open}
onOpenChange={onOpenChange}
title="Media Mode"
subtitle={subtitle}
>
{currentView === 'entry' && (
<BrandKitEntry
onNewBrand={handleNewBrand}
onImportBrand={handleImportBrand}
/>
)}

{currentView === 'wizard' && (
<BrandKitWizard
onFinish={handleWizardFinish}
onCancel={handleWizardCancel}
/>
)}

{/* TODO: Add summary view */}
{/* {currentView === 'summary' && <BrandKitSummary ... />} */}
</WorkspaceDialog>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client'

import { Button } from '@/components/ui/button'
import { ImageDown, ImagePlus } from 'lucide-react'

interface BrandKitEntryProps {
onNewBrand: () => void
onImportBrand: () => void
}

export function BrandKitEntry({
onNewBrand,
onImportBrand,
}: BrandKitEntryProps) {
return (
<div className="flex items-center justify-center min-h-[380px] py-10">
<div className="w-full max-w-xs sm:max-w-sm rounded-3xl border border-border/60 bg-zinc-100/50 dark:bg-zinc-900/50 px-8 py-7 sm:px-10 sm:py-8 shadow-sm">
<p className="text-center text-sm text-black dark:text-white mb-3">
Start from scratch and create
</p>

{/* New Brand Button */}
<Button
onClick={onNewBrand}
className="w-full h-16 sm:h-20 rounded-2xl px-6 text-lg font-normal flex items-center justify-center gap-3"
>
<ImagePlus className="w-5 h-5" />
<span>New Brand</span>
</Button>

{/* Separator with "or" */}
<div className="flex items-center my-6 text-xs text-black dark:text-white">
<div className="flex-1 h-px bg-zinc-200 dark:bg-zinc-700" />
<span className="text-xs sm:text-sm px-3">or</span>
<div className="flex-1 h-px bg-zinc-200 dark:bg-zinc-700" />
</div>

{/* Import Brand Button */}
<Button
onClick={onImportBrand}
className="w-full h-16 sm:h-20 rounded-2xl bg-accent text-accent-foreground hover:bg-accent/90 px-6 text-lg font-normal flex items-center justify-center gap-3"
>
<ImageDown className="w-5 h-5" />
<span>Import Brand</span>
</Button>

<p className="text-center text-xs sm:text-sm text-black dark:text-white mt-3">
from your existing brand assets
</p>
</div>
</div>
)
}
Loading