Skip to content
Merged
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
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<title>Open Parametric</title>
<title>RiskMesh</title>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Open Parametric</title>
<title>RiskMesh</title>
<script>
// SPA fallback for GitHub Pages
// Encodes the path into a query parameter and redirects to index.html
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/hooks/useMyPolicies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface MyPolicySummary {
statusLabel: string;
roles: MyPolicyRole[];
track: 'A' | 'B';
coverageEndTs?: number;
/** Track B only fields */
flightNo?: string;
route?: string;
Expand All @@ -33,6 +34,7 @@ interface BackendMasterAgreementFull {
reinsurer: string;
reinsurer_confirmed: boolean;
reinsurer_effective_bps: number;
coverage_end_ts: number;
participants: Array<{
insurer: string;
share_bps: number;
Expand Down Expand Up @@ -108,6 +110,7 @@ export function useMyPolicies() {
statusLabel: mp.status_label,
roles,
track: 'A',
coverageEndTs: mp.coverage_end_ts,
});
}
}
Expand Down
72 changes: 25 additions & 47 deletions frontend/src/pages/PortalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { useWallet } from '@solana/wallet-adapter-react';
import { useTranslation } from 'react-i18next';
import { PageShell } from '@/components/layout/PageShell';
import { PortalHeader } from '@/components/layout/PortalHeader';
import { Tag, Mono } from '@/components/common';

import { useParticipantRole } from '@/hooks/useParticipantRole';
import { useMyPolicies, type MyPolicySummary } from '@/hooks/useMyPolicies';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { MasterAgreementStatus, POLICY_STATE_LABELS, PolicyState } from '@/lib/idl/open_parametric';
import { MasterAgreementStatus, POLICY_STATE_LABELS } from '@/lib/idl/open_parametric';
import { LeaderPortal } from './portal/LeaderPortal';
import { ParticipantPortal } from './portal/ParticipantPortal';
import { ReinPortal } from './portal/ReinPortal';
Expand Down Expand Up @@ -71,13 +71,6 @@ const PolicyCard = styled.div`
}
`;

const ROLE_COLORS: Record<string, string> = {
leader: '#9945FF',
partA: '#22C55E',
partB: '#F59E0B',
rein: '#38BDF8',
};

const STATUS_LABELS: Record<number, string> = {
[MasterAgreementStatus.Draft]: 'Draft',
[MasterAgreementStatus.PendingConfirm]: 'Pending',
Expand All @@ -86,55 +79,40 @@ const STATUS_LABELS: Record<number, string> = {
[MasterAgreementStatus.Cancelled]: 'Cancelled',
};

const STATUS_COLORS: Record<number, string> = {
[MasterAgreementStatus.Draft]: '#94A3B8',
[MasterAgreementStatus.PendingConfirm]: '#F59E0B',
[MasterAgreementStatus.Active]: '#22C55E',
[MasterAgreementStatus.Closed]: '#64748B',
[MasterAgreementStatus.Cancelled]: '#EF4444',
};

const TRACK_B_STATUS_COLORS: Record<number, string> = {
[PolicyState.Draft]: '#94A3B8',
[PolicyState.Open]: '#38BDF8',
[PolicyState.Funded]: '#F59E0B',
[PolicyState.Active]: '#22C55E',
[PolicyState.Claimable]: '#EF4444',
[PolicyState.Approved]: '#9945FF',
[PolicyState.Settled]: '#64748B',
[PolicyState.Expired]: '#475569',
const PORTAL_ROLE_LABEL: Record<string, string> = {
leader: '리더사',
partA: '참여사',
partB: '참여사',
rein: '재보험사',
};

function formatDate(ts: number): string {
return new Date(ts * 1000).toLocaleDateString('ko-KR', {
year: '2-digit', month: '2-digit', day: '2-digit',
});
}

function PolicyListItem({ policy, onClick }: { policy: MyPolicySummary; onClick: () => void }) {
const { t } = useTranslation();
const isTrackB = policy.track === 'B';
const statusColor = isTrackB
? (TRACK_B_STATUS_COLORS[policy.status] || '#94A3B8')
: (STATUS_COLORS[policy.status] || '#94A3B8');
const statusLabel = isTrackB
? (POLICY_STATE_LABELS[policy.status] || 'Unknown')
: (STATUS_LABELS[policy.status] || 'Unknown');
const primaryRole = policy.roles[0];
const roleLabel = primaryRole ? (PORTAL_ROLE_LABEL[primaryRole.role] || primaryRole.role) : '';
const dateStr = policy.coverageEndTs ? formatDate(policy.coverageEndTs) : '';
const label = [
`${policy.pda.slice(0, 8)}...`,
statusLabel,
roleLabel,
dateStr,
].filter(Boolean).join(' · ');

return (
<PolicyCard onClick={onClick}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
{policy.roles.map(r => (
<Tag key={r.role} variant="subtle" style={{ color: ROLE_COLORS[r.role] || '#94A3B8', fontSize: 9, minWidth: 48, textAlign: 'center' }}>
{t(`portal.role.${r.role}`, r.role)}
</Tag>
))}
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<span style={{ fontFamily: 'DM Mono, monospace', fontSize: 11, fontWeight: 600 }}>
{isTrackB ? `Policy #${policy.masterId}` : `Master #${policy.masterId}`}
</span>
<Mono style={{ fontSize: 9, color: 'var(--sub)' }}>
{isTrackB && policy.flightNo
? `${policy.flightNo} · ${policy.route}`
: `${policy.pda.slice(0, 12)}...${policy.pda.slice(-8)}`}
</Mono>
</div>
</div>
<Tag variant="subtle" style={{ color: statusColor, fontSize: 8 }}>{statusLabel}</Tag>
<span style={{ fontFamily: 'DM Mono, monospace', fontSize: 11, fontWeight: 600 }}>
{label}
</span>
</PolicyCard>
);
}
Expand Down
Loading