Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions app/apply/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import { ApplyWizard } from '@iblai/iblai-js/web-containers';

import { ApplyNavbar } from '@/components/apply-navbar';
import { useCurrentTenant } from '@/hooks/use-user';

export default function ApplyPage() {
const { currentTenant } = useCurrentTenant();
const orgName =
currentTenant?.platform_name ||
currentTenant?.name ||
'American Faith Academy';

return (
<div className="flex min-h-dvh flex-col bg-white">
<ApplyNavbar />
<main className="flex-1">
{/* The navbar provides the top chrome, so the wizard hides its own. */}
<ApplyWizard brandName={orgName} showTopBar={false} />
</main>
</div>
);
}
50 changes: 50 additions & 0 deletions components/__tests__/apply-navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';

let mockTenant: { key?: string; name?: string; platform_name?: string } | null = {
key: 'acme',
name: 'Acme',
platform_name: 'Acme Academy',
};

vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
}));

vi.mock('@/hooks/use-user', () => ({
useCurrentTenant: () => ({ currentTenant: mockTenant }),
useIsAdmin: () => false,
useUsername: () => 'tester',
}));

// Heavy SDK/nav children are exercised elsewhere; stub them here.
vi.mock('@iblai/iblai-js/web-containers', () => ({
NotificationDropdown: () => <div data-testid="notifications" />,
}));

vi.mock(
'@/app/platform/[tenantKey]/[mentorId]/_components/nav-bar/user-profile',
() => ({ UserProfile: () => <div data-testid="user-profile" /> }),
);

import { ApplyNavbar } from '../apply-navbar';

beforeEach(() => {
mockTenant = { key: 'acme', name: 'Acme', platform_name: 'Acme Academy' };
});
afterEach(cleanup);

describe('ApplyNavbar', () => {
it('shows the tenant name and the translated "Application" label', () => {
render(<ApplyNavbar />);
expect(screen.getByText('Acme Academy')).toBeInTheDocument();
expect(screen.getByText('Application')).toBeInTheDocument();
});

it('falls back to the default brand name when no tenant is present', () => {
mockTenant = null;
render(<ApplyNavbar />);
expect(screen.getByText('American Faith Academy')).toBeInTheDocument();
});
});
66 changes: 66 additions & 0 deletions components/apply-navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client';

import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useCallback } from 'react';

import { NotificationDropdown } from '@iblai/iblai-js/web-containers';
import { UserProfile } from '@/app/platform/[tenantKey]/[mentorId]/_components/nav-bar/user-profile';
import { useCurrentTenant, useIsAdmin, useUsername } from '@/hooks/use-user';

/**
* Top navigation bar for the standalone /apply flow. Shows the tenant name
* (text, NOT the tenant logo) on the left, and the notification bell + profile
* dropdown from the ibl.ai SDK on the right. Tenant/user context comes from
* local storage, so it renders on a top-level route with no tenant/mentor
* URL segments.
*/
export function ApplyNavbar() {
const t = useTranslations('componentsApplyNavbar');
const router = useRouter();
const username = useUsername();
const isAdmin = useIsAdmin();
const { currentTenant } = useCurrentTenant();
const tenantKey = currentTenant?.key ?? '';
const orgName =
currentTenant?.platform_name ||
currentTenant?.name ||
'American Faith Academy';

const handleViewNotifications = useCallback(
(notificationId?: string) => {
if (!tenantKey) return;
router.push(
`/platform/${tenantKey}/notifications/${notificationId ?? ''}`,
);
},
[router, tenantKey],
);

return (
<header className="h-16 flex-shrink-0 border-b border-[var(--border-color,#d1d5db)] bg-[var(--navbar-bg,#fff)] md:h-20">
<div className="flex h-full items-center justify-between px-4 sm:px-6 lg:px-8">
{/* Left: tenant name + context (no logo) */}
<div className="flex min-w-0 items-baseline gap-2">
<span className="truncate text-base font-semibold text-gray-900 md:text-lg">
{orgName}
</span>
<span className="shrink-0 text-sm text-gray-500">{t('application')}</span>
</div>

{/* Right: notification bell + profile dropdown */}
<div className="flex items-center space-x-4">
{username && (
<NotificationDropdown
org={tenantKey}
userId={username}
isAdmin={isAdmin}
onViewNotifications={handleViewNotifications}
/>
)}
<UserProfile />
</div>
</div>
</header>
);
}
3 changes: 3 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@
"monthlyRevenue": "Monthly Revenue",
"revenueLabel": "Revenue"
},
"componentsApplyNavbar": {
"application": "Application"
},
"componentsChartWithTooltip": {
"monthlyPerformance": "Monthly Performance",
"valueLabel": "Value"
Expand Down
3 changes: 3 additions & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@
"monthlyRevenue": "Ingresos mensuales",
"revenueLabel": "Ingresos"
},
"componentsApplyNavbar": {
"application": "Solicitud"
},
"componentsChartWithTooltip": {
"monthlyPerformance": "Rendimiento mensual",
"valueLabel": "Valor"
Expand Down
3 changes: 3 additions & 0 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@
"monthlyRevenue": "Revenus mensuels",
"revenueLabel": "Revenus"
},
"componentsApplyNavbar": {
"application": "Candidature"
},
"componentsChartWithTooltip": {
"monthlyPerformance": "Performance mensuelle",
"valueLabel": "Valeur"
Expand Down
3 changes: 3 additions & 0 deletions messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@
"monthlyRevenue": "月度收入",
"revenueLabel": "收入"
},
"componentsApplyNavbar": {
"application": "申请"
},
"componentsChartWithTooltip": {
"monthlyPerformance": "月度绩效",
"valueLabel": "数值"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@iblai/agent-ai": "2.5.2",
"@iblai/iblai-api": "4.166.0-ai",
"@iblai/iblai-js": "1.25.1",
"@iblai/iblai-js": "1.25.2",
"@iblai/iblai-web-mentor": "1.3.4",
"@livekit/components-react": "2.8.1",
"@livekit/components-styles": "1.1.4",
Expand Down
34 changes: 17 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ overrides:
fast-uri: '>=3.1.2'
ip-address: '>=10.1.1'
brace-expansion: '>=5.0.6'
'minimatch@^9>brace-expansion': '2.0.3'
# Keep minimatch@9 (glob@10 in the vitest coverage tooling) on the CommonJS 2.x
# line — the 5.x major above is incompatible — but at the patched release.
# 2.0.3 was vulnerable to GHSA-3jxr-9vmj-r5cp (ReDoS); >=2.1.2 fixes it.
'minimatch@^9>brace-expansion': '>=2.1.2 <3'
postcss: '>=8.5.10'
ws: '>=8.20.1'
qs: '>=6.15.2'
Expand Down
5 changes: 4 additions & 1 deletion providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,12 @@ export default function Providers({ children }: { children: React.ReactNode }) {
// check. Match the index path only — the project chat route
// (/platform/<tenant>/projects/<projectId>/<mentorId>) still needs the mentor check.
const isProjectsIndexPage = /\/projects\/?$/.test(pathname);
// The top-level /apply route renders the application wizard and has no mentor in
// its URL, so MentorProvider would otherwise redirect it to a default mentor.
const isApplyPage = /^\/apply(\/|$)/.test(pathname);
// Pages that own their mentor context and must not be redirected away when the
// URL has no mentorId segment.
const skipMentorCheck = isWorkflowPage || isProjectsIndexPage;
const skipMentorCheck = isWorkflowPage || isProjectsIndexPage || isApplyPage;

// Use the same offline check (already computed above)
const isTauriOffline = isTauriOfflineEarly;
Expand Down
Loading