Skip to content

Commit 3216286

Browse files
refactor(frontend): GenerationStatus enum, delete dead code, dedup validation, hooks barrel
1 parent 50bac33 commit 3216286

16 files changed

Lines changed: 143 additions & 307 deletions

content-gen/src/app/frontend/src/App.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import {
1111
selectUserName,
1212
selectShowChatHistory,
1313
} from './store';
14-
import { useChatOrchestrator } from './hooks/useChatOrchestrator';
15-
import { useContentGeneration } from './hooks/useContentGeneration';
16-
import { useConversationActions } from './hooks/useConversationActions';
14+
import { useChatOrchestrator, useContentGeneration, useConversationActions } from './hooks';
1715

1816

1917
function App() {

content-gen/src/app/frontend/src/components/ChatInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ export const ChatInput = memo(function ChatInput({
3737

3838
// Support both controlled & uncontrolled modes
3939
const inputValue = controlledValue ?? internalValue;
40-
const setInputValue = (v: string) => {
40+
const setInputValue = useCallback((v: string) => {
4141
controlledOnChange?.(v);
4242
if (controlledValue === undefined) setInternalValue(v);
43-
};
43+
}, [controlledOnChange, controlledValue]);
4444

4545
const handleSubmit = useCallback((e: React.FormEvent) => {
4646
e.preventDefault();

content-gen/src/app/frontend/src/components/ChatPanel.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { WelcomeCard } from './WelcomeCard';
99
import { MessageBubble } from './MessageBubble';
1010
import { TypingIndicator } from './TypingIndicator';
1111
import { ChatInput } from './ChatInput';
12-
import { useAutoScroll } from '../hooks/useAutoScroll';
12+
import { useAutoScroll } from '../hooks';
1313
import {
1414
useAppSelector,
1515
selectMessages,
1616
selectIsLoading,
17-
selectGenerationStatus,
17+
selectGenerationStatusLabel,
1818
selectPendingBrief,
1919
selectConfirmedBrief,
2020
selectGeneratedContent,
@@ -46,7 +46,7 @@ export const ChatPanel = memo(function ChatPanel({
4646
}: ChatPanelProps) {
4747
const messages = useAppSelector(selectMessages);
4848
const isLoading = useAppSelector(selectIsLoading);
49-
const generationStatus = useAppSelector(selectGenerationStatus);
49+
const generationStatus = useAppSelector(selectGenerationStatusLabel);
5050
const pendingBrief = useAppSelector(selectPendingBrief);
5151
const confirmedBrief = useAppSelector(selectConfirmedBrief);
5252
const generatedContent = useAppSelector(selectGeneratedContent);
@@ -84,8 +84,6 @@ export const ChatPanel = memo(function ChatPanel({
8484
setInputValue(prompt);
8585
}, []);
8686

87-
const isInputDisabled = useMemo(() => isLoading, [isLoading]);
88-
8987
return (
9088
<div className="chat-container">
9189
{/* Messages Area */}
@@ -171,7 +169,7 @@ export const ChatPanel = memo(function ChatPanel({
171169
<ChatInput
172170
onSendMessage={onSendMessage}
173171
onNewConversation={onNewConversation}
174-
disabled={isInputDisabled}
172+
disabled={isLoading}
175173
value={inputValue}
176174
onChange={setInputValue}
177175
/>

content-gen/src/app/frontend/src/components/ConversationItem.tsx

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ import {
2323
} from '@fluentui/react-icons';
2424
import type { ConversationSummary } from '../store';
2525

26+
/* ------------------------------------------------------------------ */
27+
/* Validation constants & helper */
28+
/* ------------------------------------------------------------------ */
29+
30+
const NAME_MIN_LENGTH = 5;
31+
const NAME_MAX_LENGTH = 50;
32+
33+
/** Returns an error message, or `''` when the value is valid. */
34+
function validateConversationName(value: string): string {
35+
const trimmed = value.trim();
36+
if (trimmed === '') return 'Conversation name cannot be empty or contain only spaces';
37+
if (trimmed.length < NAME_MIN_LENGTH) return `Conversation name must be at least ${NAME_MIN_LENGTH} characters`;
38+
if (value.length > NAME_MAX_LENGTH) return `Conversation name cannot exceed ${NAME_MAX_LENGTH} characters`;
39+
if (!/[a-zA-Z0-9]/.test(trimmed)) return 'Conversation name must contain at least one letter or number';
40+
return '';
41+
}
42+
2643
export interface ConversationItemProps {
2744
conversation: ConversationSummary;
2845
isActive: boolean;
@@ -61,21 +78,13 @@ export const ConversationItem = memo(function ConversationItem({
6178
}, [conversation.title]);
6279

6380
const handleRenameConfirm = useCallback(async () => {
64-
const trimmedValue = renameValue.trim();
65-
66-
if (trimmedValue.length < 5) {
67-
setRenameError('Conversation name must be at least 5 characters');
68-
return;
69-
}
70-
if (trimmedValue.length > 50) {
71-
setRenameError('Conversation name cannot exceed 50 characters');
72-
return;
73-
}
74-
if (!/[a-zA-Z0-9]/.test(trimmedValue)) {
75-
setRenameError('Conversation name must contain at least one letter or number');
81+
const error = validateConversationName(renameValue);
82+
if (error) {
83+
setRenameError(error);
7684
return;
7785
}
7886

87+
const trimmedValue = renameValue.trim();
7988
if (trimmedValue === conversation.title) {
8089
setIsRenameDialogOpen(false);
8190
setRenameError('');
@@ -194,21 +203,11 @@ export const ConversationItem = memo(function ConversationItem({
194203
<Input
195204
ref={renameInputRef}
196205
value={renameValue}
197-
maxLength={50}
206+
maxLength={NAME_MAX_LENGTH}
198207
onChange={(e) => {
199208
const newValue = e.target.value;
200209
setRenameValue(newValue);
201-
if (newValue.trim() === '') {
202-
setRenameError('Conversation name cannot be empty or contain only spaces');
203-
} else if (newValue.trim().length < 5) {
204-
setRenameError('Conversation name must be at least 5 characters');
205-
} else if (!/[a-zA-Z0-9]/.test(newValue)) {
206-
setRenameError('Conversation name must contain at least one letter or number');
207-
} else if (newValue.length > 50) {
208-
setRenameError('Conversation name cannot exceed 50 characters');
209-
} else {
210-
setRenameError('');
211-
}
210+
setRenameError(validateConversationName(newValue));
212211
}}
213212
onKeyDown={(e) => {
214213
if (e.key === 'Enter' && renameValue.trim()) {
@@ -228,7 +227,7 @@ export const ConversationItem = memo(function ConversationItem({
228227
display: 'block',
229228
}}
230229
>
231-
Maximum 50 characters ({renameValue.length}/50)
230+
Maximum {NAME_MAX_LENGTH} characters ({renameValue.length}/{NAME_MAX_LENGTH})
232231
</Text>
233232
{renameError && (
234233
<Text
@@ -257,11 +256,7 @@ export const ConversationItem = memo(function ConversationItem({
257256
<Button
258257
appearance="primary"
259258
onClick={handleRenameConfirm}
260-
disabled={
261-
renameValue.trim().length < 5 ||
262-
!/[a-zA-Z0-9]/.test(renameValue) ||
263-
renameValue.length > 50
264-
}
259+
disabled={!!validateConversationName(renameValue)}
265260
>
266261
Rename
267262
</Button>

content-gen/src/app/frontend/src/components/InlineContentPreview.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {
66
} from '@fluentui/react-components';
77
import { ShieldError20Regular } from '@fluentui/react-icons';
88
import type { GeneratedContent, Product } from '../types';
9-
import { useWindowSize } from '../hooks/useWindowSize';
9+
import { useWindowSize, useCopyToClipboard } from '../hooks';
1010
import { isContentFilterError, getErrorMessage, downloadImage } from '../utils';
11-
import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
1211
import { ImagePreviewCard } from './ImagePreviewCard';
1312
import { ComplianceSection } from './ComplianceSection';
1413

content-gen/src/app/frontend/src/components/MessageBubble.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { Copy20Regular } from '@fluentui/react-icons';
1010
import ReactMarkdown from 'react-markdown';
1111
import type { ChatMessage } from '../types';
12-
import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
12+
import { useCopyToClipboard } from '../hooks';
1313

1414
export interface MessageBubbleProps {
1515
message: ChatMessage;

content-gen/src/app/frontend/src/components/SuggestionCard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77

88
export interface SuggestionCardProps {
99
title: string;
10-
prompt: string;
1110
icon: string;
1211
isSelected?: boolean;
1312
onClick: () => void;

content-gen/src/app/frontend/src/components/WelcomeCard.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { memo, useMemo, useCallback } from 'react';
1+
import { memo, useMemo } from 'react';
22
import {
33
Text,
44
tokens,
@@ -7,13 +7,13 @@ import { SuggestionCard } from './SuggestionCard';
77
import FirstPromptIcon from '../styles/images/firstprompt.png';
88
import SecondPromptIcon from '../styles/images/secondprompt.png';
99

10-
interface SuggestionCard {
10+
interface SuggestionData {
1111
title: string;
1212
prompt: string;
1313
icon: string;
1414
}
1515

16-
const suggestions: SuggestionCard[] = [
16+
const suggestions: SuggestionData[] = [
1717
{
1818
title: "I need to create a social media post about paint products for home remodels. The campaign is titled \"Brighten Your Springtime\" and the audience is new homeowners. I need marketing copy plus an image. The image should be an informal living room with tasteful furnishings.",
1919
prompt: "I need to create a social media post about paint products for home remodels. The campaign is titled \"Brighten Your Springtime\" and the audience is new homeowners. I need marketing copy plus an image. The image should be an informal living room with tasteful furnishings.",
@@ -37,9 +37,6 @@ export const WelcomeCard = memo(function WelcomeCard({ onSuggestionClick, curren
3737
[currentInput],
3838
);
3939

40-
const handleSuggestionClick = useCallback((prompt: string) => {
41-
onSuggestionClick(prompt);
42-
}, [onSuggestionClick]);
4340
return (
4441
<div style={{
4542
display: 'flex',
@@ -100,10 +97,9 @@ export const WelcomeCard = memo(function WelcomeCard({ onSuggestionClick, curren
10097
<SuggestionCard
10198
key={index}
10299
title={suggestion.title}
103-
prompt={suggestion.prompt}
104100
icon={suggestion.icon}
105101
isSelected={isSelected}
106-
onClick={() => handleSuggestionClick(suggestion.prompt)}
102+
onClick={() => onSuggestionClick(suggestion.prompt)}
107103
/>
108104
);
109105
})}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Barrel export for all custom hooks.
3+
* Import hooks from '../hooks' instead of individual files.
4+
*/
5+
export { useAutoScroll } from './useAutoScroll';
6+
export { useChatOrchestrator } from './useChatOrchestrator';
7+
export { useContentGeneration } from './useContentGeneration';
8+
export { useConversationActions } from './useConversationActions';
9+
export { useCopyToClipboard } from './useCopyToClipboard';
10+
export { useWindowSize } from './useWindowSize';

0 commit comments

Comments
 (0)