350b393457
- Rework ToolWorkshopMobileView to support full desktop functionality: definition type selection (Compose/Dockerfile/Manifest), manifest editor, conditional port, startup command, readiness probe, required variables, and validation feedback. - Add ProfileMobileView and wire ProfilePage to render it on mobile. - Update useToolWorkshop hook to return boolean success from submit. - Add responsive CSS for mobile forms, edit views, and manifest editor. - Update project maps. Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed) Refs: openspec/changes/mobile-tool-profile-ui
120 lines
3.1 KiB
TypeScript
120 lines
3.1 KiB
TypeScript
import { useNavigate } from "react-router-dom";
|
|
import { MobileEditView } from "../mobile/mobile-edit-view";
|
|
import { Icon } from "../../icon";
|
|
import type { UserProfile } from "../../../api/profile";
|
|
|
|
interface ProfileMobileViewProps {
|
|
profile: UserProfile;
|
|
name: string;
|
|
email: string;
|
|
error: string | null;
|
|
isSaving: boolean;
|
|
avatarUrl: string | null;
|
|
fileInputRef: React.RefObject<HTMLInputElement>;
|
|
onNameChange: (value: string) => void;
|
|
onEmailChange: (value: string) => void;
|
|
onAvatarButtonClick: () => void;
|
|
onAvatarChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
onSave: () => void;
|
|
}
|
|
|
|
export const ProfileMobileView = ({
|
|
profile,
|
|
name,
|
|
email,
|
|
error,
|
|
isSaving,
|
|
avatarUrl,
|
|
fileInputRef,
|
|
onNameChange,
|
|
onEmailChange,
|
|
onAvatarButtonClick,
|
|
onAvatarChange,
|
|
onSave,
|
|
}: ProfileMobileViewProps) => {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<MobileEditView
|
|
title="Profile"
|
|
onCancel={() => navigate(-1)}
|
|
onSave={onSave}
|
|
isSaving={isSaving}
|
|
>
|
|
<div className="profile-mobile-avatar-section">
|
|
<div className="profile-mobile-avatar">
|
|
{avatarUrl ? (
|
|
<img alt="Avatar" className="profile-mobile-avatar-image" src={avatarUrl} />
|
|
) : (
|
|
<div className="profile-mobile-avatar-placeholder">
|
|
{profile.name.charAt(0).toUpperCase()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<button
|
|
className="secondary-button"
|
|
disabled={isSaving}
|
|
onClick={onAvatarButtonClick}
|
|
type="button"
|
|
>
|
|
{isSaving ? (
|
|
<>
|
|
<Icon name="loading" size="sm" />
|
|
Uploading...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon name="edit" size="sm" />
|
|
Change Avatar
|
|
</>
|
|
)}
|
|
</button>
|
|
<input
|
|
accept="image/png,image/jpeg"
|
|
onChange={onAvatarChange}
|
|
ref={fileInputRef}
|
|
style={{ display: "none" }}
|
|
type="file"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mobile-form-group">
|
|
<label className="mobile-form-label" htmlFor="profile-mobile-name">
|
|
Name *
|
|
</label>
|
|
<input
|
|
className="mobile-form-input"
|
|
disabled={isSaving}
|
|
id="profile-mobile-name"
|
|
onChange={(e) => onNameChange(e.target.value)}
|
|
placeholder="Your name"
|
|
type="text"
|
|
value={name}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mobile-form-group">
|
|
<label className="mobile-form-label" htmlFor="profile-mobile-email">
|
|
Email *
|
|
</label>
|
|
<input
|
|
className="mobile-form-input"
|
|
disabled={isSaving}
|
|
id="profile-mobile-email"
|
|
onChange={(e) => onEmailChange(e.target.value)}
|
|
placeholder="your.email@example.com"
|
|
type="email"
|
|
value={email}
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="mobile-form-error">
|
|
<Icon name="warning" size="sm" />
|
|
{error}
|
|
</p>
|
|
)}
|
|
</MobileEditView>
|
|
);
|
|
};
|