import { useState, useEffect } from "react"; // ============================================= // Mock Data & API (DO NOT MODIFY) // ============================================= interface UserProfile { name: string; username: string; avatar: string; bio: string; location: string; followers: number; following: number; posts: number; } const MOCK_USER: UserProfile = { name: "Jane Cooper", username: "@janecooper", avatar: "https://placehold.co/80x80/e5e7eb/666?text=JC", bio: "Frontend engineer who loves building delightful user experiences. React, TypeScript, and design systems.", location: "San Francisco, CA", followers: 1284, following: 342, posts: 89, }; function fetchUserProfile(): Promise<UserProfile> { return new Promise((resolve) => { setTimeout(() => resolve(MOCK_USER), 2000); }); } // ============================================= // TODO: Implement the SkeletonCard component // ============================================= // // Requirements: // 1. Mimic the layout of ProfileCard with grey placeholder shapes // 2. Add a pulse or shimmer animation to each shape // 3. Match dimensions: circle for avatar, rectangles for text, blocks for stats // // Shape guide: // - Avatar: 64×64 circle (rounded-full) // - Name: h=16px, w=128px, rounded // - Username: h=12px, w=96px, rounded // - Bio: 3 lines — full width, 5/6 width, 2/3 width, h=12px each // - Location: h=12px, w=144px // - Stats: 3 blocks, h=32px, w=64px each // // Animation options: // a) Pulse: fade opacity 1 → 0.5 → 1 (simplest) // b) Shimmer: sweep a gradient highlight left-to-right (more polished) // // Hints: // - For pulse, use CSS: animation: pulse 2s ease-in-out infinite // @keyframes pulse { 0%,100% { opacity:1 } 50% { opacity:0.5 } } // - For shimmer, use a moving linear-gradient background // - Apply the animation class to each grey div function SkeletonCard() { // TODO: Return a card layout with animated grey placeholder shapes return ( <div style={{ maxWidth: 384, margin: "0 auto", border: "1px solid #e5e7eb", borderRadius: 12, padding: 24, }}> {/* TODO: Avatar placeholder (circle) */} <div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 16 }}> <div style={{ width: 64, height: 64, borderRadius: "50%", background: "#e5e7eb", // TODO: Add animation class or style }} /> <div style={{ flex: 1 }}> {/* TODO: Name placeholder */} <div style={{ height: 16, width: 128, borderRadius: 6, background: "#e5e7eb", marginBottom: 8, }} /> {/* TODO: Username placeholder */} <div style={{ height: 12, width: 96, borderRadius: 6, background: "#e5e7eb", }} /> </div> </div> {/* TODO: Bio line placeholders */} <div style={{ marginBottom: 20 }}> <div style={{ height: 12, width: "100%", borderRadius: 6, background: "#e5e7eb", marginBottom: 8 }} /> <div style={{ height: 12, width: "83%", borderRadius: 6, background: "#e5e7eb", marginBottom: 8 }} /> <div style={{ height: 12, width: "66%", borderRadius: 6, background: "#e5e7eb" }} /> </div> {/* TODO: Location placeholder */} <div style={{ height: 12, width: 144, borderRadius: 6, background: "#e5e7eb", marginBottom: 20 }} /> {/* TODO: Stats placeholders */} <div style={{ display: "flex", gap: 24 }}> <div style={{ height: 32, width: 64, borderRadius: 6, background: "#e5e7eb" }} /> <div style={{ height: 32, width: 64, borderRadius: 6, background: "#e5e7eb" }} /> <div style={{ height: 32, width: 64, borderRadius: 6, background: "#e5e7eb" }} /> </div> </div> ); } // ============================================= // ProfileCard (DO NOT MODIFY) // ============================================= function ProfileCard({ user }: { user: UserProfile }) { return ( <div style={{ maxWidth: 384, margin: "0 auto", border: "1px solid #e5e7eb", borderRadius: 12, padding: 24, }}> <div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 16 }}> <img src={user.avatar} alt={user.name} style={{ width: 64, height: 64, borderRadius: "50%", objectFit: "cover", background: "#f3f4f6" }} /> <div> <h3 style={{ fontSize: 16, fontWeight: 600, color: "#111", margin: 0 }}>{user.name}</h3> <p style={{ fontSize: 14, color: "#888", margin: "4px 0 0" }}>{user.username}</p> </div> </div> <p style={{ fontSize: 14, color: "#555", lineHeight: 1.6, margin: "0 0 16px" }}>{user.bio}</p> <p style={{ fontSize: 12, color: "#999", margin: "0 0 20px", display: "flex", alignItems: "center", gap: 4 }}> 📍 {user.location} </p> <div style={{ display: "flex", gap: 24, textAlign: "center" }}> {[ { label: "Followers", value: user.followers }, { label: "Following", value: user.following }, { label: "Posts", value: user.posts }, ].map((stat) => ( <div key={stat.label}> <p style={{ fontSize: 16, fontWeight: 600, color: "#111", margin: 0 }}> {stat.value.toLocaleString()} </p> <p style={{ fontSize: 11, color: "#999", margin: "2px 0 0" }}>{stat.label}</p> </div> ))} </div> </div> ); } export default function App() { const [user, setUser] = useState<UserProfile | null>(null); const [loading, setLoading] = useState(true); const loadUser = () => { setLoading(true); setUser(null); fetchUserProfile().then((data) => { setUser(data); setLoading(false); }); }; useEffect(() => { loadUser(); }, []); return ( <div style={{ maxWidth: 500, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Skeleton Loader </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Add a pulse or shimmer animation to the SkeletonCard placeholders. The mock API has a 2s delay so you can see the skeleton in action. </p> {/* Card area */} <div style={{ marginBottom: 24 }}> {loading ? <SkeletonCard /> : user && <ProfileCard user={user} />} </div> {/* Reload button */} <div style={{ display: "flex", justifyContent: "center" }}> <button onClick={loadUser} disabled={loading} style={{ padding: "10px 24px", background: loading ? "#999" : "#111", color: "#fff", borderRadius: 999, fontSize: 13, fontWeight: 600, cursor: loading ? "not-allowed" : "pointer", border: "none", }} > {loading ? "Loading..." : "Reload Profile"} </button> </div> {/* Hint */} <div style={{ marginTop: 32, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>Steps:</strong> (1) Add a CSS animation (pulse or shimmer) to the grey placeholder divs in <code>SkeletonCard</code>. (2) For pulse, animate opacity between 1 and 0.5. (3) For shimmer, use a moving linear-gradient background. (4) Add the animation via inline styles or a CSS class in the styles tab. </div> </div> ); }