Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .github/agents/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Auto-generated from all feature plans. Last updated: 2025-12-24

## Active Technologies
- TypeScript 5.7+, Node.js (via monorepo setup) (002-claim-details)
- SQLite (Prisma) - development database reset supported (002-claim-details)

- TypeScript 5.x / Node.js 22+ + React 18, tRPC 11, @tanstack/react-query 5, Vite 6, Prisma (ORM) (001-trpc-react-query)

Expand All @@ -22,6 +24,7 @@ npm test && npm run lint
TypeScript 5.x / Node.js 22+: Follow standard conventions

## Recent Changes
- 002-claim-details: Added TypeScript 5.7+, Node.js (via monorepo setup)

- 001-trpc-react-query: Added TypeScript 5.x / Node.js 22+ + React 18, tRPC 11, @tanstack/react-query 5, Vite 6, Prisma (ORM)

Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import { ClaimDetailsPage } from './pages/ClaimDetailsPage';

function App() {
return (
<div className="min-h-screen bg-gray-50">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/claims/:id" element={<ClaimDetailsPage />} />
</Routes>
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions packages/client/src/components/claim/ClaimDescription.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ClaimDescription } from './ClaimDescription';

const meta: Meta<typeof ClaimDescription> = {
title: 'Claim/ClaimDescription',
component: ClaimDescription,
tags: ['autodocs'],
parameters: {
layout: 'padded',
},
};

export default meta;
type Story = StoryObj<typeof ClaimDescription>;

export const Default: Story = {
args: {
description:
'Sarah Johnson is a single mother of two children seeking housing assistance after losing her apartment due to job loss. She currently has temporary housing but needs permanent housing within 60 days. Her income is below the threshold for the Housing First program.',
},
};

export const Short: Story = {
args: {
description: 'Customer inquiring about coverage limits for recent home damage claim.',
},
};

export const Long: Story = {
args: {
description: `This is a complex insurance claim involving multiple parties and various stages of investigation.

The incident occurred on November 15, 2025, when the policyholder reported water damage to their property. Initial assessment indicated the damage was extensive and required immediate attention.

Several follow-up inspections were conducted, and the claim has been escalated to the senior adjusters team for review. The estimated cost of repairs is currently being evaluated, and we expect to provide a final determination within the next 10 business days.

The policyholder has been cooperative throughout the process and has provided all requested documentation in a timely manner.`,
},
};
12 changes: 12 additions & 0 deletions packages/client/src/components/claim/ClaimDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface ClaimDescriptionProps {
description: string;
}

export function ClaimDescription({ description }: ClaimDescriptionProps) {
return (
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-3">Description</h2>
<p className="text-gray-700 leading-relaxed whitespace-pre-wrap">{description}</p>
</div>
);
}
47 changes: 47 additions & 0 deletions packages/client/src/components/claim/ClaimHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ClaimHeader } from './ClaimHeader';
import { ClaimStatus } from '@carton/shared';

const meta: Meta<typeof ClaimHeader> = {
title: 'Claim/ClaimHeader',
component: ClaimHeader,
tags: ['autodocs'],
parameters: {
layout: 'padded',
},
};

export default meta;
type Story = StoryObj<typeof ClaimHeader>;

export const Default: Story = {
args: {
title: 'Insurance Claim Dispute',
caseNumber: 'CAS-242314-2124',
status: ClaimStatus.TO_DO,
},
};

export const InProgress: Story = {
args: {
title: 'Policy Coverage Inquiry',
caseNumber: 'CAS-242315-2125',
status: ClaimStatus.IN_PROGRESS,
},
};

export const Completed: Story = {
args: {
title: 'Premium Adjustment Request',
caseNumber: 'CAS-242316-2126',
status: ClaimStatus.COMPLETED,
},
};

export const LongTitle: Story = {
args: {
title: 'Very Long Title That Should Wrap Properly When The Screen Width Is Limited',
caseNumber: 'CAS-242317-2127',
status: ClaimStatus.TO_DO,
},
};
24 changes: 24 additions & 0 deletions packages/client/src/components/claim/ClaimHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ClaimStatus } from '@carton/shared';
import { StatusBadge } from './StatusBadge';

interface ClaimHeaderProps {
title: string;
caseNumber: string;
status: ClaimStatus;
}

export function ClaimHeader({ title, caseNumber, status }: ClaimHeaderProps) {
return (
<div className="border-b border-gray-200 pb-4 mb-6">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<h1 className="text-2xl font-bold text-gray-900 mb-2">{title}</h1>
<p className="text-sm text-gray-500">{caseNumber}</p>
</div>
<div className="flex-shrink-0">
<StatusBadge status={status} />
</div>
</div>
</div>
);
}
84 changes: 84 additions & 0 deletions packages/client/src/components/claim/ClaimSidebar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ClaimSidebar } from './ClaimSidebar';
import { ClaimStatus } from '@carton/shared';

const meta: Meta<typeof ClaimSidebar> = {
title: 'Claim/ClaimSidebar',
component: ClaimSidebar,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen',
reactRouter: {
routePath: '/claims/:id',
routeParams: { id: '1' },
},
},
};

export default meta;
type Story = StoryObj<typeof ClaimSidebar>;

const mockClaims = [
{
id: '1',
title: 'Insurance Claim Dispute',
caseNumber: 'CAS-242314-2124',
status: ClaimStatus.TO_DO,
},
{
id: '2',
title: 'Policy Coverage Inquiry',
caseNumber: 'CAS-242315-2125',
status: ClaimStatus.IN_PROGRESS,
},
{
id: '3',
title: 'Premium Adjustment Request',
caseNumber: 'CAS-242316-2126',
status: ClaimStatus.COMPLETED,
},
{
id: '4',
title: 'Claim Status Update',
caseNumber: 'CAS-242317-2127',
status: ClaimStatus.TO_DO,
},
{
id: '5',
title: 'Fraud Investigation',
caseNumber: 'CAS-242318-2128',
status: ClaimStatus.IN_PROGRESS,
},
];

export const Default: Story = {
args: {
claims: mockClaims,
},
};

export const Empty: Story = {
args: {
claims: [],
},
};

export const SingleClaim: Story = {
args: {
claims: [mockClaims[0]],
},
};

export const LongTitle: Story = {
args: {
claims: [
...mockClaims,
{
id: '6',
title: 'This Is A Very Long Claim Title That Should Be Truncated With Ellipsis',
caseNumber: 'CAS-242319-2129',
status: ClaimStatus.CLOSED,
},
],
},
};
76 changes: 76 additions & 0 deletions packages/client/src/components/claim/ClaimSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Link, useParams } from 'react-router-dom';
import { ClaimStatus } from '@carton/shared';

interface ClaimListItem {
id: string;
title: string;
caseNumber: string;
status: ClaimStatus;
}

interface ClaimSidebarProps {
claims: ClaimListItem[];
}

const statusColorMap = {
[ClaimStatus.TO_DO]: 'bg-gray-200',
[ClaimStatus.IN_PROGRESS]: 'bg-blue-100',
[ClaimStatus.COMPLETED]: 'bg-green-100',
[ClaimStatus.CLOSED]: 'bg-gray-300',
};

export function ClaimSidebar({ claims }: ClaimSidebarProps) {
const { id: currentId } = useParams<{ id: string }>();

if (claims.length === 0) {
return (
<div className="w-64 bg-white border-r border-gray-200 p-4">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Claims</h2>
<p className="text-sm text-gray-500">No claims available</p>
</div>
);
}

return (
<div className="w-64 bg-white border-r border-gray-200 overflow-y-auto">
<div className="p-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">Claims</h2>
</div>
<nav className="p-2">
{claims.map((claim) => {
const isActive = claim.id === currentId;
return (
<Link
key={claim.id}
to={`/claims/${claim.id}`}
className={`block p-3 rounded-md mb-1 transition-colors ${
isActive
? 'bg-blue-50 border border-blue-200'
: 'hover:bg-gray-50 border border-transparent'
}`}
>
<div className="flex items-start justify-between gap-2 mb-1">
<h3
className={`text-sm font-medium line-clamp-2 ${
isActive ? 'text-blue-900' : 'text-gray-900'
}`}
>
{claim.title}
</h3>
<div
className={`flex-shrink-0 w-2 h-2 rounded-full mt-1 ${
statusColorMap[claim.status]
}`}
title={claim.status}
/>
</div>
<p className={`text-xs ${isActive ? 'text-blue-600' : 'text-gray-500'}`}>
{claim.caseNumber}
</p>
</Link>
);
})}
</nav>
</div>
);
}
30 changes: 30 additions & 0 deletions packages/client/src/components/claim/CommentInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CommentInput } from './CommentInput';
import { fn } from '@storybook/test';

const meta: Meta<typeof CommentInput> = {
title: 'Claim/CommentInput',
component: CommentInput,
tags: ['autodocs'],
parameters: {
layout: 'padded',
},
args: {
onSubmit: fn(),
},
};

export default meta;
type Story = StoryObj<typeof CommentInput>;

export const Default: Story = {
args: {
isSubmitting: false,
},
};

export const Submitting: Story = {
args: {
isSubmitting: true,
},
};
46 changes: 46 additions & 0 deletions packages/client/src/components/claim/CommentInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from 'react';

interface CommentInputProps {
onSubmit: (content: string) => void;
isSubmitting?: boolean;
}

export function CommentInput({ onSubmit, isSubmitting = false }: CommentInputProps) {
const [content, setContent] = useState('');

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (content.trim()) {
onSubmit(content);
setContent('');
}
};

return (
<form onSubmit={handleSubmit} className="bg-white rounded-lg border border-gray-200 p-4">
<div className="mb-3">
<label htmlFor="comment-input" className="block text-sm font-medium text-gray-700 mb-2">
Add a comment
</label>
<textarea
id="comment-input"
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Add a comment..."
rows={4}
disabled={isSubmitting}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
/>
</div>
<div className="flex justify-end">
<button
type="submit"
disabled={!content.trim() || isSubmitting}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{isSubmitting ? 'Adding...' : 'Add Comment'}
</button>
</div>
</form>
);
}
Loading