Axis MVP Junior Frontend Engineer - シンプルなUIコンポーネント実装、バグ修正、スタイリング、テストコード。トリガー:UIコンポーネント、簡単な実装、バグ修正、スタイリング調整、テスト追加
役割: Axis MVP Frontend のシンプルなUI実装とバグ修正
作業範囲: axis-agent/ ディレクトリのみ
禁止事項:
axis-api/ バックエンド変更kagemusha-program/ Solanaプログラム変更axis-mobile/ モバイルアプリ変更Issue確認
gh issue view {issue_number}
理解できるか判断
ブランチ作成
cd /Users/yusukekikuta/.openclaw/workspace/Axis_MVP
git checkout main
git pull origin main
git checkout -b feat/engineer-b-{task-description}
スクリプト使用:
cd /Users/yusukekikuta/.openclaw/workspace/my-company/.company/development/axis-team/skills/axis-engineer-b
node scripts/create-component.js UserAvatar
// src/components/common/UserAvatar.tsx
import React from 'react';
interface UserAvatarProps {
address: string;
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export const UserAvatar: React.FC<UserAvatarProps> = ({
address,
size = 'md',
className = '',
}) => {
// サイズクラス
const sizeClasses = {
sm: 'w-8 h-8 text-sm',
md: 'w-12 h-12 text-base',
lg: 'w-16 h-16 text-lg',
};
// 最初の2文字
const initials = address.slice(0, 2).toUpperCase();
return (
<div
className={`
${sizeClasses[size]}
rounded-full
bg-gradient-to-r from-axis-purple to-axis-pink
flex items-center justify-center
text-white font-semibold
${className}
`}
>
{initials}
</div>
);
};
// src/components/common/Button.tsx
import React from 'react';
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
children,
onClick,
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
}) => {
const baseClasses = 'font-semibold rounded-xl transition-all duration-200';
const variantClasses = {
primary: 'bg-gradient-to-r from-axis-purple to-axis-pink text-white hover:shadow-lg hover:scale-105',
secondary: 'bg-axis-gray text-white border border-axis-light-gray hover:border-axis-purple',
danger: 'bg-red-500 text-white hover:bg-red-600',
};
const sizeClasses = {
sm: 'px-4 py-2 text-sm',
md: 'px-6 py-3 text-base',
lg: 'px-8 py-4 text-lg',
};
return (
<button
onClick={onClick}
disabled={disabled || loading}
className={`
${baseClasses}
${variantClasses[variant]}
${sizeClasses[size]}
${(disabled || loading) && 'opacity-50 cursor-not-allowed'}
`}
>
{loading ? 'Loading...' : children}
</button>
);
};
// src/components/common/Card.tsx
import React from 'react';
interface CardProps {
children: React.ReactNode;
title?: string;
className?: string;
}
export const Card: React.FC<CardProps> = ({
children,
title,
className = '',
}) => {
return (
<div
className={`
bg-axis-gray
rounded-xl
p-6
border border-axis-light-gray
hover:border-axis-purple
transition-all duration-200
${className}
`}
>
{title && (
<h3 className="text-xl font-semibold text-white mb-4">
{title}
</h3>
)}
{children}
</div>
);
};
// src/components/common/Badge.tsx
import React from 'react';
interface BadgeProps {
children: React.ReactNode;
variant?: 'success' | 'warning' | 'error' | 'info';
}
export const Badge: React.FC<BadgeProps> = ({
children,
variant = 'info',
}) => {
const variantClasses = {
success: 'bg-green-500 text-white',
warning: 'bg-yellow-500 text-white',
error: 'bg-red-500 text-white',
info: 'bg-blue-500 text-white',
};
return (
<span
className={`
${variantClasses[variant]}
px-3 py-1
rounded-full
text-sm font-medium
inline-block
`}
>
{children}
</span>
);
};
バグ再現
npm run dev
# ブラウザで該当ページを開く
# バグを確認
原因特定
修正実装
// Before
<div className="flex"> {/* flex-col が抜けている */}
<p>Item 1</p>
<p>Item 2</p>
</div>
// After
<div className="flex flex-col">
<p>Item 1</p>
<p>Item 2</p>
</div>
動作確認
npm run dev
# ブラウザで修正確認
// ❌ Bad - モバイルで崩れる
<div className="w-96"> {/* 固定幅 */}
<p>Content</p>
</div>
// ✅ Good - レスポンシブ
<div className="w-full max-w-96">
<p>Content</p>
</div>
// ❌ Bad - undefinedの場合にエラー
<p>{user.name}</p>
// ✅ Good - null check
<p>{user?.name || 'Unknown'}</p>
// ❌ Bad - keyなし
{items.map(item => <div>{item.name}</div>)}
// ✅ Good - key追加
{items.map(item => <div key={item.id}>{item.name}</div>)}
// Before(インラインスタイル)
<div style={{ backgroundColor: '#7B3FF2', padding: '16px' }}>
Content
</div>
// After(Tailwind)
<div className="bg-axis-purple p-4">
Content
</div>
<div className="
w-full
px-4 sm:px-6 lg:px-8
text-sm md:text-base lg:text-lg
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
gap-4 md:gap-6
">
{/* Content */}
</div>
<button className="
bg-axis-purple
hover:bg-axis-pink
hover:scale-105
transition-all duration-200
">
Hover me
</button>
// src/components/common/__tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '../Button';
describe('Button', () => {
it('renders children correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Disabled</Button>);
const button = screen.getByText('Disabled');
expect(button).toBeDisabled();
});
it('shows loading state', () => {
render(<Button loading>Loading</Button>);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
});
cd /Users/yusukekikuta/.openclaw/workspace/my-company/.company/development/axis-team/skills/axis-engineer-b
node scripts/run-tests.js Button
# ファイル確認
git status
# 変更差分確認
git diff
# Staging
git add axis-agent/src/components/common/UserAvatar.tsx
# Commit
git commit -m "feat(components): Add UserAvatar component
- Display user initials from wallet address
- Support sm/md/lg sizes
- Gradient background
- Fully responsive"
# Push
git push origin feat/engineer-b-user-avatar
gh pr create \
--title "feat: Add UserAvatar component" \
--body "## 変更内容
- UserAvatarコンポーネント追加
- wallet addressの最初の2文字を表示
- サイズ3種類対応(sm/md/lg)
- グラデーション背景
## スクリーンショット
(可能であれば添付)
## テスト
- [x] デスクトップ表示確認
- [x] モバイル表示確認
- [x] ユニットテスト追加・パス
- [x] ESLint/Prettierパス
シンプルなコンポーネントです。レビューお願いします! 🙏" \
--base main
axis-agent/ のみ# エラー確認
npm run type-check
# よくあるエラー
# 1. Props型定義忘れ
interface MyComponentProps { // 追加
title: string;
}
# 2. Optional型
title?: string; // ? を追加
# 開発サーバー再起動
npm run dev
# 詳細エラー確認
npm test -- Button.test.tsx --verbose
scripts/create-component.js - コンポーネント生成scripts/run-tests.js - テスト実行axis-agent/src/components/common/ - 既存の共通コンポーネントaxis-agent/src/components/create/ - 作成フローのコンポーネントaxis-agent/src/components/discover/ - 発見フローのコンポーネントdesign-system.md, color-palette.mdstate-patterns.md小さなステップから、確実に成長する 🌱