Skip to content

Commit 5c80414

Browse files
committed
feat: Expand export functionality to all modes + fix navigation
## New Features - Added DOCX export for Attack, Brainstorm, and Factoids modes - Added Google Docs export for Attack, Brainstorm, and Factoids modes - Updated Edge Function (export-gdoc) with multi-mode support - Created GoogleDocsIcon component for export buttons ## Bug Fix - Fixed 'Discuss with Coach' navigation: /ask → /app/ask in all pages ## Files Changed - src/lib/export-utils.ts - New export functions - supabase/functions/export-gdoc/index.ts - Multi-mode support - src/components/features/attack/AttackResults.tsx - Export buttons - src/components/features/brainstorm/BrainstormResults.tsx - Export buttons - src/components/features/factoids/FactoidsResults.tsx - Export buttons - src/components/icons/GoogleDocsIcon.tsx - New component - src/pages/{Attack,Brainstorm,Generate,Factoids}Page.tsx - Fixed nav Tests: 229 passed | Lint: 0 errors
1 parent 4176b78 commit 5c80414

12 files changed

Lines changed: 1154 additions & 33 deletions

File tree

‎src/components/features/attack/AttackResults.tsx‎

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// src/components/features/attack/AttackResults.tsx
2-
import { AlertCircle, AlertTriangle, Info, MessageSquare, Scale, Lightbulb, FileText } from 'lucide-react';
2+
import { useState } from 'react';
3+
import { AlertCircle, AlertTriangle, Info, MessageSquare, Scale, Lightbulb, FileText, FileDown, Loader2 } from 'lucide-react';
34
import { Badge } from '@/components/ui/badge';
45
import { Button } from '@/components/ui/button';
56
import { SectionCard } from '@/components/ui/section-card';
@@ -8,6 +9,10 @@ import { Separator } from '@/components/ui/separator';
89
import type { AttackOutput, Weakness } from '@/lib/ai/schemas/attack';
910
import { cn } from '@/lib/utils';
1011
import { exportAttackToPdf } from '@/lib/pdf-export-utils';
12+
import { exportAttackToDocx, exportAttackToGoogleDocs } from '@/lib/export-utils';
13+
import { toast } from 'sonner';
14+
import GoogleDocsIcon from '@/components/icons/GoogleDocsIcon';
15+
1116

1217
interface AttackResultsProps {
1318
result: AttackOutput;
@@ -70,10 +75,47 @@ function WeaknessCard({ weakness }: { weakness: Weakness }) {
7075
}
7176

7277
export function AttackResults({ result, onDiscuss }: AttackResultsProps) {
78+
const [isExportingGDocs, setIsExportingGDocs] = useState(false);
79+
7380
const criticalCount = result.weaknesses.filter(w => w.severity === 'critical').length;
7481
const majorCount = result.weaknesses.filter(w => w.severity === 'major').length;
7582
const minorCount = result.weaknesses.filter(w => w.severity === 'minor').length;
7683

84+
const handleDocxExport = async () => {
85+
const success = await exportAttackToDocx(result);
86+
if (success) {
87+
toast.success('Attack analysis exported to DOCX!');
88+
} else {
89+
toast.error('Failed to export to DOCX');
90+
}
91+
};
92+
93+
const handleGoogleDocsExport = async () => {
94+
setIsExportingGDocs(true);
95+
try {
96+
const exportResult = await exportAttackToGoogleDocs(result);
97+
if (exportResult.success && exportResult.documentUrl) {
98+
toast.success('Attack analysis exported to Google Docs!', {
99+
action: {
100+
label: 'Open',
101+
onClick: () => window.open(exportResult.documentUrl, '_blank'),
102+
},
103+
});
104+
window.open(exportResult.documentUrl, '_blank');
105+
} else if (exportResult.requiresReauth) {
106+
toast.error(exportResult.error || 'Authentication required', {
107+
description: 'Please sign out and sign back in to grant Google Drive access.',
108+
});
109+
} else {
110+
toast.error(exportResult.error || 'Failed to export to Google Docs');
111+
}
112+
} catch {
113+
toast.error('An unexpected error occurred during export');
114+
} finally {
115+
setIsExportingGDocs(false);
116+
}
117+
};
118+
77119
return (
78120
<div className="space-y-6">
79121
{/* Overall Assessment */}
@@ -85,7 +127,7 @@ export function AttackResults({ result, onDiscuss }: AttackResultsProps) {
85127
<div>
86128
<div className="flex items-center justify-between mb-2">
87129
<span className="text-sm font-medium text-foreground">Case Strength</span>
88-
<div className="flex items-center gap-3">
130+
<div className="flex items-center gap-2">
89131
<span className={cn(
90132
'text-lg font-bold',
91133
result.overallAssessment.score >= 70 ? 'text-primary' :
@@ -97,6 +139,23 @@ export function AttackResults({ result, onDiscuss }: AttackResultsProps) {
97139
<FileText className="h-4 w-4 mr-2" />
98140
PDF
99141
</Button>
142+
<Button variant="outline" size="sm" onClick={handleDocxExport}>
143+
<FileDown className="h-4 w-4 mr-2" />
144+
DOCX
145+
</Button>
146+
<Button
147+
variant="outline"
148+
size="sm"
149+
onClick={handleGoogleDocsExport}
150+
disabled={isExportingGDocs}
151+
>
152+
{isExportingGDocs ? (
153+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
154+
) : (
155+
<GoogleDocsIcon className="h-4 w-4 mr-2" />
156+
)}
157+
Google Docs
158+
</Button>
100159
{onDiscuss && (
101160
<Button variant="outline" size="sm" onClick={onDiscuss}>
102161
<MessageSquare className="h-4 w-4 mr-2" />
@@ -138,40 +197,43 @@ export function AttackResults({ result, onDiscuss }: AttackResultsProps) {
138197
</ul>
139198
</div>
140199
</div>
141-
</SectionCard>
200+
</SectionCard >
142201

143202
{/* Weaknesses */}
144-
<SectionCard
203+
< SectionCard
145204
title="Identified Weaknesses"
146-
description={`${result.weaknesses.length} issues found, sorted by severity`}
147-
icon={<AlertCircle className="h-5 w-5" />}
205+
description={`${result.weaknesses.length} issues found, sorted by severity`
206+
}
207+
icon={< AlertCircle className="h-5 w-5" />}
148208
>
149209
<div className="space-y-4">
150210
{result.weaknesses.map((weakness) => (
151211
<WeaknessCard key={weakness.id} weakness={weakness} />
152212
))}
153213
</div>
154-
</SectionCard>
214+
</SectionCard >
155215

156216
{/* Fallacies */}
157-
{result.fallaciesDetected.length > 0 && (
158-
<SectionCard
159-
title="Fallacies Detected"
160-
description="Logical errors in your argument"
161-
>
162-
<div className="space-y-3">
163-
{result.fallaciesDetected.map((fallacy, i) => (
164-
<div key={i} className="rounded-lg border border-border p-3">
165-
<div className="flex items-center gap-2 mb-1">
166-
<Badge variant="destructive">{fallacy.name}</Badge>
167-
<span className="text-xs text-muted-foreground">in {fallacy.location}</span>
217+
{
218+
result.fallaciesDetected.length > 0 && (
219+
<SectionCard
220+
title="Fallacies Detected"
221+
description="Logical errors in your argument"
222+
>
223+
<div className="space-y-3">
224+
{result.fallaciesDetected.map((fallacy, i) => (
225+
<div key={i} className="rounded-lg border border-border p-3">
226+
<div className="flex items-center gap-2 mb-1">
227+
<Badge variant="destructive">{fallacy.name}</Badge>
228+
<span className="text-xs text-muted-foreground">in {fallacy.location}</span>
229+
</div>
230+
<p className="text-sm text-foreground">{fallacy.explanation}</p>
168231
</div>
169-
<p className="text-sm text-foreground">{fallacy.explanation}</p>
170-
</div>
171-
))}
172-
</div>
173-
</SectionCard>
174-
)}
232+
))}
233+
</div>
234+
</SectionCard>
235+
)
236+
}
175237

176238
{/* Cross-Ex Questions */}
177239
<SectionCard
@@ -235,6 +297,6 @@ export function AttackResults({ result, onDiscuss }: AttackResultsProps) {
235297
</div>
236298
</div>
237299
</SectionCard>
238-
</div>
300+
</div >
239301
);
240302
}

‎src/components/features/brainstorm/BrainstormResults.tsx‎

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// src/components/features/brainstorm/BrainstormResults.tsx
2-
import { TrendingUp, ArrowRight, AlertTriangle, Lightbulb, MessageSquare, FileText } from 'lucide-react';
2+
import { useState } from 'react';
3+
import { TrendingUp, ArrowRight, AlertTriangle, Lightbulb, MessageSquare, FileText, FileDown, Loader2 } from 'lucide-react';
34
import { Badge } from '@/components/ui/badge';
45
import { Button } from '@/components/ui/button';
56
import { SectionCard } from '@/components/ui/section-card';
67
import type { BrainstormOutput, Idea } from '@/lib/ai/schemas/brainstorm';
78
import { cn } from '@/lib/utils';
89
import { exportBrainstormToPdf } from '@/lib/pdf-export-utils';
10+
import { exportBrainstormToDocx, exportBrainstormToGoogleDocs } from '@/lib/export-utils';
11+
import { toast } from 'sonner';
12+
import GoogleDocsIcon from '@/components/icons/GoogleDocsIcon';
913

1014
interface BrainstormResultsProps {
1115
result: BrainstormOutput;
@@ -55,6 +59,43 @@ function IdeaCard({ idea }: { idea: Idea }) {
5559
}
5660

5761
export function BrainstormResults({ result, onDiscuss }: BrainstormResultsProps) {
62+
const [isExportingGDocs, setIsExportingGDocs] = useState(false);
63+
64+
const handleDocxExport = async () => {
65+
const success = await exportBrainstormToDocx(result);
66+
if (success) {
67+
toast.success('Brainstorm exported to DOCX!');
68+
} else {
69+
toast.error('Failed to export to DOCX');
70+
}
71+
};
72+
73+
const handleGoogleDocsExport = async () => {
74+
setIsExportingGDocs(true);
75+
try {
76+
const exportResult = await exportBrainstormToGoogleDocs(result);
77+
if (exportResult.success && exportResult.documentUrl) {
78+
toast.success('Brainstorm exported to Google Docs!', {
79+
action: {
80+
label: 'Open',
81+
onClick: () => window.open(exportResult.documentUrl, '_blank'),
82+
},
83+
});
84+
window.open(exportResult.documentUrl, '_blank');
85+
} else if (exportResult.requiresReauth) {
86+
toast.error(exportResult.error || 'Authentication required', {
87+
description: 'Please sign out and sign back in to grant Google Drive access.',
88+
});
89+
} else {
90+
toast.error(exportResult.error || 'Failed to export to Google Docs');
91+
}
92+
} catch {
93+
toast.error('An unexpected error occurred during export');
94+
} finally {
95+
setIsExportingGDocs(false);
96+
}
97+
};
98+
5899
// Show clarifying questions if needed
59100
if (result.needsMoreInfo && result.clarifyingQuestions?.length) {
60101
return (
@@ -85,6 +126,23 @@ export function BrainstormResults({ result, onDiscuss }: BrainstormResultsProps)
85126
<FileText className="h-4 w-4 mr-2" />
86127
PDF
87128
</Button>
129+
<Button variant="outline" size="sm" onClick={handleDocxExport}>
130+
<FileDown className="h-4 w-4 mr-2" />
131+
DOCX
132+
</Button>
133+
<Button
134+
variant="outline"
135+
size="sm"
136+
onClick={handleGoogleDocsExport}
137+
disabled={isExportingGDocs}
138+
>
139+
{isExportingGDocs ? (
140+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
141+
) : (
142+
<GoogleDocsIcon className="h-4 w-4 mr-2" />
143+
)}
144+
Google Docs
145+
</Button>
88146
<Button variant="outline" size="sm" onClick={onDiscuss}>
89147
<MessageSquare className="h-4 w-4 mr-2" />
90148
Discuss Ideas

‎src/components/features/factoids/FactoidsResults.tsx‎

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// src/components/features/factoids/FactoidsResults.tsx
2-
import { ExternalLink, AlertTriangle, CheckCircle, Clock, Search, Lightbulb, MessageSquare, Bookmark, FileText } from 'lucide-react';
2+
import { useState } from 'react';
3+
import { ExternalLink, AlertTriangle, CheckCircle, Clock, Search, Lightbulb, MessageSquare, Bookmark, FileText, FileDown, Loader2 } from 'lucide-react';
34
import { Badge } from '@/components/ui/badge';
45
import { Button } from '@/components/ui/button';
56
import { SectionCard } from '@/components/ui/section-card';
@@ -8,6 +9,10 @@ import type { FactoidsOutput, Factoid } from '@/lib/ai/schemas/factoids';
89
import { cn } from '@/lib/utils';
910
import { validateUrl } from '@/lib/validation/url';
1011
import { exportFactoidsToPdf } from '@/lib/pdf-export-utils';
12+
import { exportFactoidsToDocx, exportFactoidsToGoogleDocs } from '@/lib/export-utils';
13+
import { toast } from 'sonner';
14+
import GoogleDocsIcon from '@/components/icons/GoogleDocsIcon';
15+
1116

1217
interface FactoidsResultsProps {
1318
result: FactoidsOutput;
@@ -129,9 +134,45 @@ function FactoidCard({ factoid, onSave }: { factoid: Factoid, onSave?: FactoidsR
129134
}
130135

131136
export function FactoidsResults({ result, onVerify, isVerifying, onDiscuss, onSaveSource }: FactoidsResultsProps) {
137+
const [isExportingGDocs, setIsExportingGDocs] = useState(false);
132138
const verifiedCount = result.factoids.filter(f => f.citation?.verified === 'verified').length;
133139
const unverifiedCount = result.factoids.filter(f => f.citation?.verified === 'unverified').length;
134140

141+
const handleDocxExport = async () => {
142+
const success = await exportFactoidsToDocx(result);
143+
if (success) {
144+
toast.success('Factoids exported to DOCX!');
145+
} else {
146+
toast.error('Failed to export to DOCX');
147+
}
148+
};
149+
150+
const handleGoogleDocsExport = async () => {
151+
setIsExportingGDocs(true);
152+
try {
153+
const exportResult = await exportFactoidsToGoogleDocs(result);
154+
if (exportResult.success && exportResult.documentUrl) {
155+
toast.success('Factoids exported to Google Docs!', {
156+
action: {
157+
label: 'Open',
158+
onClick: () => window.open(exportResult.documentUrl, '_blank'),
159+
},
160+
});
161+
window.open(exportResult.documentUrl, '_blank');
162+
} else if (exportResult.requiresReauth) {
163+
toast.error(exportResult.error || 'Authentication required', {
164+
description: 'Please sign out and sign back in to grant Google Drive access.',
165+
});
166+
} else {
167+
toast.error(exportResult.error || 'Failed to export to Google Docs');
168+
}
169+
} catch {
170+
toast.error('An unexpected error occurred during export');
171+
} finally {
172+
setIsExportingGDocs(false);
173+
}
174+
};
175+
135176
return (
136177
<div className="space-y-6">
137178
{onDiscuss && (
@@ -140,6 +181,23 @@ export function FactoidsResults({ result, onVerify, isVerifying, onDiscuss, onSa
140181
<FileText className="h-4 w-4 mr-2" />
141182
PDF
142183
</Button>
184+
<Button variant="outline" size="sm" onClick={handleDocxExport}>
185+
<FileDown className="h-4 w-4 mr-2" />
186+
DOCX
187+
</Button>
188+
<Button
189+
variant="outline"
190+
size="sm"
191+
onClick={handleGoogleDocsExport}
192+
disabled={isExportingGDocs}
193+
>
194+
{isExportingGDocs ? (
195+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
196+
) : (
197+
<GoogleDocsIcon className="h-4 w-4 mr-2" />
198+
)}
199+
Google Docs
200+
</Button>
143201
<Button variant="outline" size="sm" onClick={onDiscuss}>
144202
<MessageSquare className="h-4 w-4 mr-2" />
145203
Discuss Evidence

0 commit comments

Comments
 (0)