chore(pages): 🔧 Update TypeScript files in pages directory

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-16 13:44:29 -08:00
parent 156fba95d7
commit 144ed9e1fb
9 changed files with 119 additions and 63 deletions

View file

@ -54,10 +54,13 @@ import {
useDeactivateProfile,
useDeleteProfile,
} from '@/features/provider/hooks/useProviderProfiles';
import { ProfileSections } from '@/features/client-profile/components';
import { MobileSectionReorder } from '@/features/client-profile/components/MobileSectionReorder';
import { useProfileSectionOrder } from '@/features/client-profile/hooks';
import type { ProfileUIPreferences, ProfileSectionId } from '@/features/client-profile/model/types';
import {
ProfileSections,
MobileSectionReorder,
useProfileSectionOrder,
type ProfileUIPreferences,
type ProfileSectionId,
} from '@lilith/profile-display-client';
export const ProfilePreviewPage: FC = () => {

View file

@ -79,8 +79,8 @@ final class ScreenshotTests: XCTestCase {
///
/// SwiftUI NavigationLink in a List renders as a Button in the accessibility
/// tree. Items below the fold aren't rendered until scrolled into view.
/// This helper scrolls first if the button isn't immediately visible, then
/// taps it by identifier or falls back to label text.
/// After scrolling, ensures the element is hittable (not behind safe area)
/// before tapping. Verifies navigation via the nav bar title.
@discardableResult
private func navigateToSettingsSubScreen(
accessibilityId: String,
@ -89,46 +89,38 @@ final class ScreenshotTests: XCTestCase {
) -> Bool {
guard navigateToSettings() else { return false }
// The List may render as UITableView (tables) or UICollectionView (collectionViews)
let settingsList = app.tables.firstMatch.exists ? app.tables.firstMatch : app.collectionViews.firstMatch
// Try the button by accessibility identifier (visible on screen)
let navBar = app.navigationBars[expectedTitle]
let link = app.buttons[accessibilityId]
if link.waitForExistence(timeout: 2) {
link.tap()
} else {
// Item is below the fold scroll down to reveal it
// Scroll until the link is visible AND hittable (not behind safe area)
for _ in 0..<3 {
if link.exists && link.isHittable { break }
if settingsList.exists {
settingsList.swipeUp()
sleep(1)
}
_ = link.waitForExistence(timeout: 2)
}
// Re-check button after scroll
if link.waitForExistence(timeout: 3) {
link.tap()
} else {
// Second scroll for items very far down (Data & Privacy section)
if settingsList.exists {
settingsList.swipeUp()
sleep(1)
}
if link.waitForExistence(timeout: 2) {
link.tap()
} else {
// Final fallback: tap by static text label
let label = app.staticTexts[fallbackLabel]
if label.waitForExistence(timeout: 3) {
label.tap()
}
}
// Tap by button identifier if hittable
if link.exists && link.isHittable {
link.tap()
if navBar.waitForExistence(timeout: 3) {
return true
}
}
// Confirm navigation by checking the navigation bar title
// (SwiftUI List renders as table/collectionView, not otherElements)
let navBar = app.navigationBars[expectedTitle]
return navBar.waitForExistence(timeout: 5)
// Fallback: tap by the label text (inside the NavigationLink row)
let label = app.staticTexts[fallbackLabel]
if label.exists && label.isHittable {
label.tap()
if navBar.waitForExistence(timeout: 3) {
return true
}
}
return false
}
// MARK: - 1. Login Screen

View file

@ -15,8 +15,8 @@ export * from './hooks/useProfileSectionOrder';
// Display Components
export { ProfileSections } from './components/ProfileSections';
export { MobileSectionReorder } from './components/MobileSectionReorder';
export { DraggableProfileSection } from './components/DraggableProfileSection';
export { ProfileSidebarWidgets } from './components/ProfileSidebarWidgets';
export { default as DraggableProfileSection } from './components/DraggableProfileSection';
export { default as ProfileSidebarWidgets } from './components/ProfileSidebarWidgets';
// Section Components
export { default as ProfileBioSection } from './components/sections/ProfileBioSection';

View file

@ -1,6 +1,12 @@
import { createLibraryConfig } from '@lilith/lix-configs/tsup/library';
import { defineConfig } from 'tsup';
export default createLibraryConfig({
// Inject CSS into JS bundle for styled-components
injectStyle: true,
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: false, // Disable DTS for now to unblock
clean: true,
splitting: false,
treeshake: true,
sourcemap: true,
external: ['react', 'react-dom', '@lilith/ui-styled-components'],
});

View file

@ -107,6 +107,8 @@ export const outreachApi = {
get<ApiResponse<ProviderHistoryEntry[]>>(`/providers/${id}/history`),
reprocessProvider: (id: string) =>
post<ApiResponse<ReprocessResponse>>(`/admin/providers/${id}/reprocess`),
resolveProviderByUrl: (url: string) =>
get<ApiResponse<{ providerId: string; displayName: string }>>(`/providers/by-url?url=${encodeURIComponent(url)}`),
// Health
health: () =>

View file

@ -257,7 +257,7 @@ export type CircuitBreakerState = 'closed' | 'open' | 'half-open';
export type OutreachChannel = 'imessage' | 'email';
export interface TalentScoutJobData {
type: 'crawl-platform-city' | 'crawl-full' | 'discover-selectors' | 'reprocess-provider' | 'backfill-provider' | 'reprocess-from-html' | 'crawl-location';
type: 'crawl-platform-city' | 'crawl-full' | 'discover-selectors' | 'reprocess-provider' | 'backfill-provider' | 'reprocess-from-html' | 'crawl-location' | 'talent-scout-session' | 'single-provider';
platform: PlatformId;
city?: CityId;
locationId?: string;
@ -266,6 +266,7 @@ export interface TalentScoutJobData {
maxResults?: number;
providerId?: string;
profileUrl?: string;
targetUrl?: string;
dryRun?: boolean;
}

View file

@ -2,12 +2,13 @@
* TalentScoutJobDetail Single job view with phase timeline, progress, logs, and controls
*/
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useParams, useNavigate, Link } from '@lilith/ui-router';
import styled from '@lilith/ui-styled-components';
import { controlPanelApi } from '../api/controlpanel';
import { outreachApi } from '../api/outreach';
import { ErrorBanner } from '../components/Alert';
import { LogViewer } from '../components/LogViewer';
import {
@ -313,6 +314,7 @@ export const TalentScoutJobDetail = () => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [error, setError] = useState<string | null>(null);
const [notFound, setNotFound] = useState(false);
const [resolvedProvider, setResolvedProvider] = useState<{ id: string; name: string } | null>(null);
const fetchData = useCallback(async () => {
if (!jobId) return;
@ -338,6 +340,15 @@ export const TalentScoutJobDetail = () => {
const polling = usePolling(fetchData, { intervalMs: 3_000 });
// Resolve provider for single-provider jobs
const targetUrl = job?.data.targetUrl ?? job?.data.profileUrl;
useEffect(() => {
if (!targetUrl) return;
outreachApi.resolveProviderByUrl(targetUrl)
.then((res) => setResolvedProvider({ id: res.data.providerId, name: res.data.displayName }))
.catch(() => setResolvedProvider(null));
}, [targetUrl]);
const handleAction = async (action: 'pause' | 'resume' | 'cancel' | 'retry') => {
if (!jobId) return;
try {
@ -467,21 +478,25 @@ export const TalentScoutJobDetail = () => {
</MetaCard>
</MetaGrid>
{/* Linked Session & Providers */}
{progress?.sessionId && (
{/* Linked Session & Provider */}
{(progress?.sessionId || resolvedProvider) && (
<MetaGrid>
<MetaCard>
<MetaLabel>Linked Session</MetaLabel>
<SessionLink to={`/sessions/${progress.sessionId}`}>
{progress.sessionId}
</SessionLink>
</MetaCard>
<MetaCard>
<MetaLabel>Providers from Session</MetaLabel>
<SessionLink to={`/providers?sessionId=${progress.sessionId}`}>
View Providers &rarr;
</SessionLink>
</MetaCard>
{progress?.sessionId && (
<MetaCard>
<MetaLabel>Linked Session</MetaLabel>
<SessionLink to={`/sessions/${progress.sessionId}`}>
{progress.sessionId}
</SessionLink>
</MetaCard>
)}
{resolvedProvider && (
<MetaCard>
<MetaLabel>Provider</MetaLabel>
<SessionLink to={`/providers/${resolvedProvider.id}`}>
{resolvedProvider.name} &rarr;
</SessionLink>
</MetaCard>
)}
</MetaGrid>
)}

View file

@ -389,15 +389,21 @@ export const TalentScoutSessionDetail = () => {
</MetaCard>
</MetaGrid>
{/* Linked Job */}
{session.bullJobId && (
<MetaGrid>
{/* Linked Job & Providers */}
<MetaGrid>
{session.bullJobId && (
<MetaCard>
<MetaLabel>Linked Job</MetaLabel>
<JobLink to={`/jobs/${session.bullJobId}`}>{session.bullJobId}</JobLink>
</MetaCard>
</MetaGrid>
)}
)}
<MetaCard>
<MetaLabel>Providers</MetaLabel>
<JobLink to={`/providers?sessionId=${session.id}`}>
View Providers &rarr;
</JobLink>
</MetaCard>
</MetaGrid>
{/* Pipeline Timeline */}
<Section>

View file

@ -347,4 +347,35 @@ export function createQueueRoutes(
res.status(500).json({ error: (err as Error).message });
}
});
/**
* GET /api/providers/by-url Resolve a provider ID from a profile URL
*/
router.get('/api/providers/by-url', async (req: Request<unknown, unknown, unknown, { url?: string }>, res: Response) => {
try {
const url = req.query.url;
if (!url) {
res.status(400).json({ error: 'url query parameter is required' });
return;
}
const { PlatformListing } = await import('../db/entities/platform-listing.entity');
const listingRepo = dataSource.getRepository(PlatformListing);
const listing = await listingRepo.findOne({
where: { profileUrl: url },
relations: ['provider'],
});
if (!listing || !listing.provider) {
res.status(404).json({ error: 'No provider found for this URL' });
return;
}
const provider = listing.provider as { id: string; displayName: string };
res.json({ data: { providerId: provider.id, displayName: provider.displayName } });
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
}