원클릭으로
nextjs-todo-task-ui
nextjs todo component, task card tailwind, add edit todo form, todo list ui pattern, nextjs task management ui
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
nextjs todo component, task card tailwind, add edit todo form, todo list ui pattern, nextjs task management ui
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Generate Helm 3+ chart structure for todo/task applications. Use for: creating Chart.yaml, values.yaml, and templates/ directory with Deployment and Service manifests. Triggers: "helm chart", "helm template", "helm values", "create helm chart", "helm install", "helm upgrade", "package as helm chart". NOT for: plain Kubernetes YAML (use kubernetes-yaml-best-practices), Kustomize, or kubectl commands. NOT for: CRDs, hooks, subcharts, or advanced Helm features unless explicitly requested.
Apply Kubernetes pod security hardening with securityContext, Pod Security Standards, and container isolation. Use for: adding runAsNonRoot, readOnlyRootFilesystem, dropping capabilities, seccomp profiles, and privilege escalation prevention. Triggers: "secure pod", "pod security", "non-root container", "security context", "k8s security", "harden deployment", "pod security standards", "restricted PSS". NOT for: NetworkPolicies, RBAC, Secrets management, or service mesh security. Use alongside kubernetes-yaml-best-practices for complete manifests.
Generate production-ready Kubernetes YAML manifests following best practices. Use when creating or reviewing: Deployments, Services, ConfigMaps, Secrets, Ingress, PersistentVolumeClaims, or any k8s resource YAML. Triggers: "kubernetes manifest", "deployment yaml", "service yaml", "write k8s resource", "create kubernetes", "k8s yaml", "pod spec", "container spec". NOT for Helm charts, Kustomize overlays, or kubectl commands.
Deploy and test applications on local Minikube Kubernetes cluster. Use for: loading local Docker images to Minikube, exposing services locally via port-forward or minikube service, debugging pods (logs, describe, exec), troubleshooting CrashLoopBackOff/ImagePullBackOff errors. Triggers: "deploy to minikube", "minikube image load", "port-forward", "minikube service", "debug pod", "why is pod crashing", "minikube dashboard", "local kubernetes testing". NOT for: minikube installation, minikube start/stop, cloud Kubernetes, Helm charts, or writing YAML manifests (use kubernetes-yaml-best-practices for YAML).
OCI & OKE specialist for the Todo app hackathon project. Provides infrastructure context, authentication patterns, and deployment guidance for Oracle Kubernetes Engine in me-dubai-1. Use when working with: OKE cluster operations, kubectl commands, oci CLI, Helm deploys to OKE, Dapr/Kafka on OKE, Ingress/LoadBalancer setup, Kubernetes secrets, cloud deployment, OCI networking, node pool management, or troubleshooting OKE auth errors. Triggers: "OKE", "oci", "kubectl", "deploy to cloud", "cluster", "OCI", "oracle kubernetes", "ingress on OKE", "secrets on OKE", "Dapr on OKE", "Kafka on OKE", "node pool", "DOKS alternative".
Dapr service invocation pattern for FastAPI Todo app. Use DaprClient.invoke_method to call other services with user_id propagated via metadata. Use when implementing service-to-service communication in FastAPI applications with user context propagation.
| name | nextjs-todo-task-ui |
| description | nextjs todo component, task card tailwind, add edit todo form, todo list ui pattern, nextjs task management ui |
This skill provides standardized UI patterns for Next.js Todo applications using App Router and Tailwind CSS. It enforces consistent component design, proper data fetching with JWT authentication, and follows best practices for server and client components.
Use Next.js App Router with server components as default:
app/
├── layout.tsx
├── page.tsx
├── tasks/
│ ├── page.tsx
│ ├── [id]/
│ │ └── page.tsx
│ └── new/
│ └── page.tsx
Use Tailwind CSS classes consistently:
// components/TaskCard.tsx
'use client';
import { useState } from 'react';
interface Task {
id: number;
title: string;
description?: string;
completed: boolean;
}
interface TaskCardProps {
task: Task;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
onEdit: (id: number) => void;
}
export default function TaskCard({ task, onToggle, onDelete, onEdit }: TaskCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="flex flex-col p-4 mb-3 bg-white shadow rounded-lg border border-gray-200">
<div className="flex items-start gap-3">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
className="mt-1 h-5 w-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<div className="flex-1 min-w-0">
<h3 className={`text-lg font-medium ${task.completed ? 'line-through text-gray-500' : 'text-gray-900'}`}>
{task.title}
</h3>
{task.description && (
<div className="mt-1 text-sm text-gray-500">
{isExpanded ? task.description : `${task.description.substring(0, 100)}${task.description.length > 100 ? '...' : ''}`}
</div>
)}
</div>
<div className="flex space-x-2">
<button
onClick={() => onEdit(task.id)}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Edit
</button>
<button
onClick={() => onDelete(task.id)}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Delete
</button>
</div>
</div>
{task.description && task.description.length > 100 && (
<button
onClick={() => setIsExpanded(!isExpanded)}
className="mt-2 text-xs text-blue-600 hover:text-blue-800"
>
{isExpanded ? 'Show less' : 'Show more'}
</button>
)}
</div>
);
}
Consistent form design with Tailwind:
// components/TaskForm.tsx
'use client';
import { useState } from 'react';
interface TaskFormProps {
onSubmit: (task: { title: string; description?: string }) => void;
onCancel: () => void;
initialData?: { id?: number; title: string; description?: string };
}
export default function TaskForm({ onSubmit, onCancel, initialData }: TaskFormProps) {
const [title, setTitle] = useState(initialData?.title || '');
const [description, setDescription] = useState(initialData?.description || '');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ title, description });
};
return (
<form onSubmit={handleSubmit} className="bg-white p-4 rounded-lg shadow mb-4">
<div className="mb-4">
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
Title *
</label>
<input
type="text"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border"
/>
</div>
<div className="mb-4">
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2 border"
></textarea>
</div>
<div className="flex justify-end space-x-3">
<button
type="button"
onClick={onCancel}
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Cancel
</button>
<button
type="submit"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{initialData ? 'Update Task' : 'Add Task'}
</button>
</div>
</form>
);
}
Use fetch with JWT Authorization header:
// lib/api.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
export async function getTasks(): Promise<Task[]> {
const token = localStorage.getItem('jwt_token');
const response = await fetch(`${API_BASE_URL}/api/tasks`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to fetch tasks');
}
return response.json();
}
export async function createTask(task: { title: string; description?: string }): Promise<Task> {
const token = localStorage.getItem('jwt_token');
const response = await fetch(`${API_BASE_URL}/api/tasks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
if (!response.ok) {
throw new Error('Failed to create task');
}
return response.json();
}
export async function updateTask(id: number, task: { title: string; description?: string }): Promise<Task> {
const token = localStorage.getItem('jwt_token');
const response = await fetch(`${API_BASE_URL}/api/tasks/${id}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
if (!response.ok) {
throw new Error('Failed to update task');
}
return response.json();
}
export async function deleteTask(id: number): Promise<void> {
const token = localStorage.getItem('jwt_token');
const response = await fetch(`${API_BASE_URL}/api/tasks/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to delete task');
}
}
export async function toggleTaskCompletion(id: number): Promise<Task> {
const token = localStorage.getItem('jwt_token');
const response = await fetch(`${API_BASE_URL}/api/tasks/${id}/complete`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to toggle task completion');
}
return response.json();
}
Handle loading and error states properly:
// components/TaskList.tsx
import { Suspense } from 'react';
import TaskCard from './TaskCard';
import LoadingSpinner from './LoadingSpinner';
import { getTasks } from '../lib/api';
async function TaskListContent() {
let tasks = [];
try {
tasks = await getTasks();
} catch (error) {
return <div className="text-red-500">Failed to load tasks: {(error as Error).message}</div>;
}
if (tasks.length === 0) {
return <div className="text-center py-8 text-gray-500">No tasks found</div>;
}
return (
<div>
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
onToggle={async (id: number) => {
// Handle toggle completion
}}
onDelete={async (id: number) => {
// Handle delete task
}}
onEdit={async (id: number) => {
// Handle edit task
}}
/>
))}
</div>
);
}
export default function TaskList() {
return (
<Suspense fallback={<LoadingSpinner />}>
<TaskListContent />
</Suspense>
);
}
Use these Tailwind classes consistently:
bg-white, shadow, rounded-lg, border, border-gray-200flex, flex-col, flex-wrap, items-center, justify-between, gap-4p-4, mb-4, mt-2, px-4, py-2text-lg, font-medium, text-gray-900, text-sm, text-gray-500rounded-md, border, shadow-sm, hover:bg-gray-50, focus:outline-none, focus:ring-2Before implementing any Todo UI component: