feat: rework mobile UI for Tool Workshop and Profile pages
- 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
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# apps/web/src/components/features/profile (index)
|
||||
dir: apps/web/src/components/features/profile
|
||||
|
||||
## role
|
||||
Provides a mobile-specific UI for users to view and edit their profile information.
|
||||
## parent
|
||||
index: apps/web/src/components/features/.pi-map.index.md
|
||||
map: apps/web/src/components/features/.pi-map.md
|
||||
## children
|
||||
-
|
||||
## files
|
||||
- ProfileMobileView.tsx
|
||||
## links
|
||||
index: apps/web/src/components/features/profile/.pi-map.index.md
|
||||
map: apps/web/src/components/features/profile/.pi-map.md
|
||||
## workflows
|
||||
- change profile behavior
|
||||
read: ProfileMobileView.tsx
|
||||
## dirty
|
||||
-
|
||||
@@ -0,0 +1,20 @@
|
||||
# apps/web/src/components/features/profile
|
||||
dir: apps/web/src/components/features/profile
|
||||
|
||||
index: apps/web/src/components/features/profile/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Provides a mobile-specific UI for users to view and edit their profile information.
|
||||
## files
|
||||
- ProfileMobileView.tsx | Renders a mobile-optimized profile editing form with avatar upload, name/email fields, and save/cancel functionality. | exp: ProfileMobileView | dep: react-router-dom, ../mobile/mobile-edit-view, ../../icon, ../../../api/profile
|
||||
## arch
|
||||
Single feature-focused component with form handling and file upload, likely using controlled inputs and local state management.
|
||||
## tags
|
||||
mobile, profile, view, renders, optimized, editing, form, avatar
|
||||
## symbols
|
||||
- ProfileMobileView
|
||||
## workflows
|
||||
- change profile behavior
|
||||
read: ProfileMobileView.tsx
|
||||
## dirty
|
||||
-
|
||||
@@ -0,0 +1,119 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user