chore(pages): 🔧 Update TypeScript files in pages directory
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
156fba95d7
commit
144ed9e1fb
9 changed files with 119 additions and 63 deletions
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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: () =>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 →
|
||||
</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} →
|
||||
</SessionLink>
|
||||
</MetaCard>
|
||||
)}
|
||||
</MetaGrid>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 →
|
||||
</JobLink>
|
||||
</MetaCard>
|
||||
</MetaGrid>
|
||||
|
||||
{/* Pipeline Timeline */}
|
||||
<Section>
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue