# Social Media App — Frontend React Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a React + Vite frontend that exactly matches the provided HTML/CSS designs, with auth, infinite-scroll post feed, comments, replies, and optimistic likes wired to the Laravel backend.

**Architecture:** Feature-based folder structure with Redux Toolkit slices + async thunks for state, Axios with CSRF interceptor for API calls, React Router v6 for routing with a ProtectedRoute guard.

**Tech Stack:** React 18, Vite, Redux Toolkit, React Router v6, Axios

**Working directory:** `D:/assesment-react/assesment-react`

**Backend URL:** `http://localhost` (Laragon) — proxied via Vite dev server

---

### Task 1: Install Dependencies + Vite Proxy Config

**Files:**
- Modify: `package.json`
- Modify: `vite.config.js`
- Modify: `src/main.jsx`

- [ ] **Step 1: Install required packages**

```bash
cd "D:/assesment-react/assesment-react"
npm install axios react-router-dom @reduxjs/toolkit react-redux
```

Expected: packages installed with no peer dependency errors.

- [ ] **Step 2: Update `vite.config.js` with proxy**

```js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/api/, '/assesment-backend/public/api'),
      },
      '/sanctum': {
        target: 'http://localhost',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/sanctum/, '/assesment-backend/public/sanctum'),
      },
    },
  },
})
```

> **Note:** Adjust the rewrite paths if your Laragon virtual host is configured differently (e.g., `assesment-backend.test`). If using a virtual host like `http://assesment-backend.test`, set `target` to that URL and remove the `rewrite`.

- [ ] **Step 3: Verify dev server starts**

```bash
npm run dev
```

Expected: `Local: http://localhost:5173/` — open browser, see default Vite+React page.

- [ ] **Step 4: Commit**

```bash
git add vite.config.js package.json package-lock.json
git commit -m "chore: install deps and configure Vite proxy for Laravel backend"
```

---

### Task 2: Folder Structure + Copy Design Assets

**Files:**
- Create: `src/api/`, `src/app/`, `src/features/auth/`, `src/features/posts/`, `src/features/comments/`, `src/features/replies/`, `src/components/`, `src/layouts/`, `src/routes/`, `src/hooks/`, `src/utils/`
- Create: `src/assets/css/` and `src/assets/images/` (copied from design assets)

- [ ] **Step 1: Create folder structure**

```bash
cd "D:/assesment-react/assesment-react/src"
mkdir -p api app features/auth features/posts features/comments features/replies
mkdir -p components layouts routes hooks utils assets/css assets/images
```

- [ ] **Step 2: Copy design CSS files**

```bash
cp "C:/Users/shipo/Downloads/assets/assets/css/bootstrap.min.css" "D:/assesment-react/assesment-react/src/assets/css/"
cp "C:/Users/shipo/Downloads/assets/assets/css/main.css"          "D:/assesment-react/assesment-react/src/assets/css/"
cp "C:/Users/shipo/Downloads/assets/assets/css/common.css"        "D:/assesment-react/assesment-react/src/assets/css/"
cp "C:/Users/shipo/Downloads/assets/assets/css/responsive.css"    "D:/assesment-react/assesment-react/src/assets/css/"
```

- [ ] **Step 3: Copy all design images**

```bash
cp -r "C:/Users/shipo/Downloads/assets/assets/images/." "D:/assesment-react/assesment-react/src/assets/images/"
```

- [ ] **Step 4: Import CSS in `src/main.jsx`**

```jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import '@/assets/css/bootstrap.min.css'
import '@/assets/css/common.css'
import '@/assets/css/main.css'
import '@/assets/css/responsive.css'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
```

- [ ] **Step 5: Clear default App.css and index.css**

Replace `src/App.css` with empty file content:
```css
/* intentionally empty */
```

Replace `src/index.css` with empty file content:
```css
/* intentionally empty */
```

- [ ] **Step 6: Commit**

```bash
git add src/
git commit -m "chore: create folder structure and import design assets"
```

---

### Task 3: Axios Instance + Redux Store

**Files:**
- Create: `src/api/axios.js`
- Create: `src/app/store.js`

- [ ] **Step 1: Create `src/api/axios.js`**

```js
import axios from 'axios'

const api = axios.create({
  baseURL: '/',
  withCredentials: true,
  withXSRFToken: true,
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
})

// Ensure CSRF cookie is fetched before state-changing requests
let csrfFetched = false

api.interceptors.request.use(async (config) => {
  const safeMethods = ['get', 'head', 'options']
  if (!csrfFetched && !safeMethods.includes(config.method)) {
    await axios.get('/sanctum/csrf-cookie', { withCredentials: true })
    csrfFetched = true
  }
  return config
})

export default api
```

- [ ] **Step 2: Create `src/app/store.js`**

```js
import { configureStore } from '@reduxjs/toolkit'
import authReducer from '@/features/auth/authSlice'
import postsReducer from '@/features/posts/postsSlice'
import commentsReducer from '@/features/comments/commentsSlice'
import repliesReducer from '@/features/replies/repliesSlice'

export const store = configureStore({
  reducer: {
    auth:     authReducer,
    posts:    postsReducer,
    comments: commentsReducer,
    replies:  repliesReducer,
  },
})
```

- [ ] **Step 3: Commit**

```bash
git add src/api/axios.js src/app/store.js
git commit -m "chore: Axios instance with CSRF interceptor and Redux store"
```

---

### Task 4: Auth Slice + Thunks

**Files:**
- Create: `src/features/auth/authSlice.js`

- [ ] **Step 1: Create `src/features/auth/authSlice.js`**

```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import api from '@/api/axios'

export const registerUser = createAsyncThunk(
  'auth/register',
  async (data, { rejectWithValue }) => {
    try {
      const res = await api.post('/api/register', data)
      return res.data.user
    } catch (err) {
      return rejectWithValue(err.response?.data ?? { message: 'Registration failed.' })
    }
  }
)

export const loginUser = createAsyncThunk(
  'auth/login',
  async (data, { rejectWithValue }) => {
    try {
      const res = await api.post('/api/login', data)
      return res.data.user
    } catch (err) {
      return rejectWithValue(err.response?.data ?? { message: 'Login failed.' })
    }
  }
)

export const logoutUser = createAsyncThunk('auth/logout', async () => {
  await api.post('/api/logout')
})

export const fetchCurrentUser = createAsyncThunk(
  'auth/fetchMe',
  async (_, { rejectWithValue }) => {
    try {
      const res = await api.get('/api/me')
      return res.data.user
    } catch {
      return rejectWithValue(null)
    }
  }
)

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user:            null,
    isAuthenticated: false,
    loading:         false,
    initialized:     false,
    error:           null,
  },
  reducers: {
    clearError(state) { state.error = null },
  },
  extraReducers: (builder) => {
    // Register
    builder
      .addCase(registerUser.pending,  (s) => { s.loading = true; s.error = null })
      .addCase(registerUser.fulfilled,(s, a) => { s.loading = false; s.user = a.payload; s.isAuthenticated = true })
      .addCase(registerUser.rejected, (s, a) => { s.loading = false; s.error = a.payload })
    // Login
    builder
      .addCase(loginUser.pending,  (s) => { s.loading = true; s.error = null })
      .addCase(loginUser.fulfilled,(s, a) => { s.loading = false; s.user = a.payload; s.isAuthenticated = true })
      .addCase(loginUser.rejected, (s, a) => { s.loading = false; s.error = a.payload })
    // Logout
    builder
      .addCase(logoutUser.fulfilled,(s) => { s.user = null; s.isAuthenticated = false })
    // Fetch me (app startup)
    builder
      .addCase(fetchCurrentUser.pending,  (s) => { s.loading = true })
      .addCase(fetchCurrentUser.fulfilled,(s, a) => {
        s.loading = false; s.initialized = true
        s.user = a.payload; s.isAuthenticated = true
      })
      .addCase(fetchCurrentUser.rejected, (s) => {
        s.loading = false; s.initialized = true
        s.user = null; s.isAuthenticated = false
      })
  },
})

export const { clearError } = authSlice.actions
export default authSlice.reducer
```

- [ ] **Step 2: Commit**

```bash
git add src/features/auth/authSlice.js
git commit -m "feat: auth slice with register/login/logout/fetchMe thunks"
```

---

### Task 5: AuthLayout + LoginPage

**Files:**
- Create: `src/layouts/AuthLayout.jsx`
- Create: `src/features/auth/LoginPage.jsx`

- [ ] **Step 1: Create `src/layouts/AuthLayout.jsx`**

```jsx
import { Outlet } from 'react-router-dom'
import shape1Img from '@/assets/images/shape1.svg'
import darkShape from '@/assets/images/dark_shape.svg'
import shape2 from '@/assets/images/shape2.svg'
import darkShape1 from '@/assets/images/dark_shape1.svg'
import shape3 from '@/assets/images/shape3.svg'
import darkShape2 from '@/assets/images/dark_shape2.svg'

export default function AuthLayout() {
  return (
    <div className="_layout_main_wrapper">
      <div className="_shape_one">
        <img src={shape1Img} alt="" className="_shape_img" />
        <img src={darkShape} alt="" className="_dark_shape" />
      </div>
      <div className="_shape_two">
        <img src={shape2} alt="" className="_shape_img" />
        <img src={darkShape1} alt="" className="_dark_shape _dark_shape_opacity" />
      </div>
      <div className="_shape_three">
        <img src={shape3} alt="" className="_shape_img" />
        <img src={darkShape2} alt="" className="_dark_shape _dark_shape_opacity" />
      </div>
      <Outlet />
    </div>
  )
}
```

- [ ] **Step 2: Create `src/features/auth/LoginPage.jsx`**

```jsx
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { loginUser, clearError } from './authSlice'
import loginImg from '@/assets/images/login.png'
import googleImg from '@/assets/images/google.svg'

export default function LoginPage() {
  const dispatch  = useDispatch()
  const navigate  = useNavigate()
  const { loading, error } = useSelector((s) => s.auth)

  const [form, setForm] = useState({ email: '', password: '', remember: false })

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target
    setForm((f) => ({ ...f, [name]: type === 'checkbox' ? checked : value }))
    if (error) dispatch(clearError())
  }

  const handleSubmit = async (e) => {
    e.preventDefault()
    const result = await dispatch(loginUser(form))
    if (loginUser.fulfilled.match(result)) navigate('/feed')
  }

  return (
    <section className="_social_login_wrapper">
      <div className="_social_login_wrap">
        <div className="container">
          <div className="row align-items-center">
            <div className="col-xl-8 col-lg-8 col-md-12 col-sm-12">
              <div className="_social_login_left">
                <div className="_social_login_left_image">
                  <img src={loginImg} alt="" className="_left_img" />
                </div>
              </div>
            </div>
            <div className="col-xl-4 col-lg-4 col-md-12 col-sm-12">
              <div className="_social_login_content">
                <p className="_social_login_content_para _mar_b8">Welcome back</p>
                <h4 className="_social_login_content_title _titl4 _mar_b50">Login to your account</h4>
                <button type="button" className="_social_login_content_btn _mar_b40">
                  <img src={googleImg} alt="" className="_google_img" />
                  <span>Or sign-in with google</span>
                </button>
                <div className="_social_login_content_bottom_txt _mar_b40"><span>Or</span></div>

                {error && (
                  <div className="alert alert-danger py-2 mb-3" role="alert">
                    {error.message || 'Login failed. Please try again.'}
                  </div>
                )}

                <form className="_social_login_form" onSubmit={handleSubmit}>
                  <div className="row">
                    <div className="col-xl-12">
                      <div className="_social_login_form_input _mar_b14">
                        <label className="_social_login_label _mar_b8">Email</label>
                        <input
                          type="email"
                          name="email"
                          value={form.email}
                          onChange={handleChange}
                          className="form-control _social_login_input"
                          required
                        />
                      </div>
                    </div>
                    <div className="col-xl-12">
                      <div className="_social_login_form_input _mar_b14">
                        <label className="_social_login_label _mar_b8">Password</label>
                        <input
                          type="password"
                          name="password"
                          value={form.password}
                          onChange={handleChange}
                          className="form-control _social_login_input"
                          required
                        />
                      </div>
                    </div>
                  </div>
                  <div className="row">
                    <div className="col-lg-6">
                      <div className="form-check _social_login_form_check">
                        <input
                          className="form-check-input _social_login_form_check_input"
                          type="checkbox"
                          name="remember"
                          id="rememberMe"
                          checked={form.remember}
                          onChange={handleChange}
                        />
                        <label className="form-check-label _social_login_form_check_label" htmlFor="rememberMe">
                          Remember me
                        </label>
                      </div>
                    </div>
                    <div className="col-lg-6">
                      <div className="_social_login_form_left">
                        <p className="_social_login_form_left_para">Forgot password?</p>
                      </div>
                    </div>
                  </div>
                  <div className="row">
                    <div className="col-lg-12">
                      <div className="_social_login_form_btn _mar_t40 _mar_b60">
                        <button
                          type="submit"
                          className="_social_login_form_btn_link _btn1"
                          disabled={loading}
                        >
                          {loading ? 'Logging in...' : 'Login now'}
                        </button>
                      </div>
                    </div>
                  </div>
                </form>

                <div className="row">
                  <div className="col-xl-12">
                    <div className="_social_login_bottom_txt">
                      <p className="_social_login_bottom_txt_para">
                        Don't have an account?{' '}
                        <Link to="/register">Create New Account</Link>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  )
}
```

- [ ] **Step 3: Commit**

```bash
git add src/layouts/AuthLayout.jsx src/features/auth/LoginPage.jsx
git commit -m "feat: AuthLayout and LoginPage matching design"
```

---

### Task 6: RegisterPage

**Files:**
- Create: `src/features/auth/RegisterPage.jsx`

- [ ] **Step 1: Create `src/features/auth/RegisterPage.jsx`**

```jsx
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { registerUser, clearError } from './authSlice'
import registrationImg from '@/assets/images/registration.png'
import googleImg from '@/assets/images/google.svg'

export default function RegisterPage() {
  const dispatch = useDispatch()
  const navigate = useNavigate()
  const { loading, error } = useSelector((s) => s.auth)

  const [form, setForm] = useState({
    first_name: '', last_name: '', email: '',
    password: '', password_confirmation: '', agree: false,
  })

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target
    setForm((f) => ({ ...f, [name]: type === 'checkbox' ? checked : value }))
    if (error) dispatch(clearError())
  }

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!form.agree) return
    const result = await dispatch(registerUser(form))
    if (registerUser.fulfilled.match(result)) navigate('/feed')
  }

  const fieldError = (field) => error?.errors?.[field]?.[0]

  return (
    <section className="_social_registration_wrapper">
      <div className="_social_registration_wrap">
        <div className="container">
          <div className="row align-items-center">
            <div className="col-xl-8 col-lg-8 col-md-12 col-sm-12">
              <div className="_social_registration_right">
                <div className="_social_registration_right_image">
                  <img src={registrationImg} alt="" />
                </div>
              </div>
            </div>
            <div className="col-xl-4 col-lg-4 col-md-12 col-sm-12">
              <div className="_social_registration_content">
                <p className="_social_registration_content_para _mar_b8">Get Started Now</p>
                <h4 className="_social_registration_content_title _titl4 _mar_b50">Registration</h4>
                <button type="button" className="_social_registration_content_btn _mar_b40">
                  <img src={googleImg} alt="" className="_google_img" />
                  <span>Register with google</span>
                </button>
                <div className="_social_registration_content_bottom_txt _mar_b40"><span>Or</span></div>

                <form className="_social_registration_form" onSubmit={handleSubmit}>
                  <div className="row">
                    <div className="col-xl-6">
                      <div className="_social_registration_form_input _mar_b14">
                        <label className="_social_registration_label _mar_b8">First Name</label>
                        <input
                          type="text" name="first_name" value={form.first_name}
                          onChange={handleChange}
                          className={`form-control _social_registration_input${fieldError('first_name') ? ' is-invalid' : ''}`}
                          required
                        />
                        {fieldError('first_name') && <div className="invalid-feedback">{fieldError('first_name')}</div>}
                      </div>
                    </div>
                    <div className="col-xl-6">
                      <div className="_social_registration_form_input _mar_b14">
                        <label className="_social_registration_label _mar_b8">Last Name</label>
                        <input
                          type="text" name="last_name" value={form.last_name}
                          onChange={handleChange}
                          className={`form-control _social_registration_input${fieldError('last_name') ? ' is-invalid' : ''}`}
                          required
                        />
                        {fieldError('last_name') && <div className="invalid-feedback">{fieldError('last_name')}</div>}
                      </div>
                    </div>
                    <div className="col-xl-12">
                      <div className="_social_registration_form_input _mar_b14">
                        <label className="_social_registration_label _mar_b8">Email</label>
                        <input
                          type="email" name="email" value={form.email}
                          onChange={handleChange}
                          className={`form-control _social_registration_input${fieldError('email') ? ' is-invalid' : ''}`}
                          required
                        />
                        {fieldError('email') && <div className="invalid-feedback">{fieldError('email')}</div>}
                      </div>
                    </div>
                    <div className="col-xl-12">
                      <div className="_social_registration_form_input _mar_b14">
                        <label className="_social_registration_label _mar_b8">Password</label>
                        <input
                          type="password" name="password" value={form.password}
                          onChange={handleChange}
                          className={`form-control _social_registration_input${fieldError('password') ? ' is-invalid' : ''}`}
                          required
                        />
                        {fieldError('password') && <div className="invalid-feedback">{fieldError('password')}</div>}
                      </div>
                    </div>
                    <div className="col-xl-12">
                      <div className="_social_registration_form_input _mar_b14">
                        <label className="_social_registration_label _mar_b8">Confirm Password</label>
                        <input
                          type="password" name="password_confirmation" value={form.password_confirmation}
                          onChange={handleChange}
                          className="form-control _social_registration_input"
                          required
                        />
                      </div>
                    </div>
                  </div>
                  <div className="row">
                    <div className="col-lg-12">
                      <div className="form-check _social_registration_form_check">
                        <input
                          className="form-check-input _social_registration_form_check_input"
                          type="checkbox" name="agree" id="agreeTerms"
                          checked={form.agree} onChange={handleChange}
                        />
                        <label className="form-check-label _social_registration_form_check_label" htmlFor="agreeTerms">
                          I agree to terms &amp; conditions
                        </label>
                      </div>
                    </div>
                  </div>
                  <div className="row">
                    <div className="col-lg-12">
                      <div className="_social_registration_form_btn _mar_t40 _mar_b60">
                        <button
                          type="submit"
                          className="_social_registration_form_btn_link _btn1"
                          disabled={loading || !form.agree}
                        >
                          {loading ? 'Creating account...' : 'Register now'}
                        </button>
                      </div>
                    </div>
                  </div>
                </form>

                <div className="row">
                  <div className="col-xl-12">
                    <div className="_social_registration_bottom_txt">
                      <p className="_social_registration_bottom_txt_para">
                        Already have an account? <Link to="/login">Login</Link>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  )
}
```

- [ ] **Step 2: Commit**

```bash
git add src/features/auth/RegisterPage.jsx
git commit -m "feat: RegisterPage with first_name, last_name fields matching design"
```

---

### Task 7: Router + ProtectedRoute + App Bootstrap

**Files:**
- Create: `src/routes/ProtectedRoute.jsx`
- Create: `src/routes/router.jsx`
- Modify: `src/App.jsx`

- [ ] **Step 1: Create `src/routes/ProtectedRoute.jsx`**

```jsx
import { useSelector } from 'react-redux'
import { Navigate, Outlet } from 'react-router-dom'

export default function ProtectedRoute() {
  const { isAuthenticated, initialized } = useSelector((s) => s.auth)

  if (!initialized) return null // Show nothing while checking auth

  return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />
}
```

- [ ] **Step 2: Create `src/routes/router.jsx`**

```jsx
import { createBrowserRouter, Navigate } from 'react-router-dom'
import AuthLayout from '@/layouts/AuthLayout'
import MainLayout from '@/layouts/MainLayout'
import LoginPage from '@/features/auth/LoginPage'
import RegisterPage from '@/features/auth/RegisterPage'
import FeedPage from '@/features/posts/FeedPage'
import ProtectedRoute from './ProtectedRoute'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Navigate to="/feed" replace />,
  },
  {
    element: <AuthLayout />,
    children: [
      { path: '/login',    element: <LoginPage /> },
      { path: '/register', element: <RegisterPage /> },
    ],
  },
  {
    element: <ProtectedRoute />,
    children: [
      {
        element: <MainLayout />,
        children: [
          { path: '/feed', element: <FeedPage /> },
        ],
      },
    ],
  },
])

export default router
```

- [ ] **Step 3: Update `src/App.jsx`**

```jsx
import { RouterProvider } from 'react-router-dom'
import { Provider } from 'react-redux'
import { useEffect } from 'react'
import { store } from '@/app/store'
import { fetchCurrentUser } from '@/features/auth/authSlice'
import router from '@/routes/router'

function AppInit() {
  useEffect(() => {
    store.dispatch(fetchCurrentUser())
  }, [])

  return <RouterProvider router={router} />
}

export default function App() {
  return (
    <Provider store={store}>
      <AppInit />
    </Provider>
  )
}
```

- [ ] **Step 4: Commit**

```bash
git add src/routes/ src/App.jsx
git commit -m "feat: router with ProtectedRoute and app-start auth check"
```

---

### Task 8: MainLayout + Navbar

**Files:**
- Create: `src/layouts/MainLayout.jsx`
- Create: `src/components/Avatar.jsx`

- [ ] **Step 1: Create `src/components/Avatar.jsx`**

```jsx
import avatarImg from '@/assets/images/Avatar.png'

export default function Avatar({ src, alt = '', className = '_nav_profile_img' }) {
  return (
    <img
      src={src || avatarImg}
      alt={alt}
      className={className}
      onError={(e) => { e.target.src = avatarImg }}
    />
  )
}
```

- [ ] **Step 2: Create `src/layouts/MainLayout.jsx`**

```jsx
import { Outlet, Link, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { useState } from 'react'
import { logoutUser } from '@/features/auth/authSlice'
import Avatar from '@/components/Avatar'
import logoImg from '@/assets/images/logo-copy.svg'

export default function MainLayout() {
  const dispatch  = useDispatch()
  const navigate  = useNavigate()
  const { user }  = useSelector((s) => s.auth)
  const [profileDropOpen, setProfileDropOpen] = useState(false)

  const handleLogout = async () => {
    await dispatch(logoutUser())
    navigate('/login')
  }

  return (
    <div className="_layout _layout_main_wrapper">
      <div className="_main_layout">
        {/* Desktop Navbar */}
        <nav className="navbar navbar-expand-lg navbar-light _header_nav _padd_t10">
          <div className="container _custom_container">
            <div className="_logo_wrap">
              <Link className="navbar-brand" to="/feed">
                <img src={logoImg} alt="BuddyScript" className="_nav_logo" />
              </Link>
            </div>
            <button
              className="navbar-toggler bg-light"
              type="button"
              data-bs-toggle="collapse"
              data-bs-target="#navbarSupportedContent"
            >
              <span className="navbar-toggler-icon"></span>
            </button>
            <div className="collapse navbar-collapse" id="navbarSupportedContent">
              <div className="_header_form ms-auto">
                <form className="_header_form_grp" onSubmit={(e) => e.preventDefault()}>
                  <svg className="_header_form_svg" xmlns="http://www.w3.org/2000/svg" width="17" height="17" fill="none" viewBox="0 0 17 17">
                    <circle cx="7" cy="7" r="6" stroke="#666" />
                    <path stroke="#666" strokeLinecap="round" d="M16 16l-3-3" />
                  </svg>
                  <input className="form-control me-2 _inpt1" type="search" placeholder="Search..." />
                </form>
              </div>
              <ul className="navbar-nav mb-2 mb-lg-0 _header_nav_list ms-auto _mar_r8">
                <li className="nav-item _header_nav_item">
                  <Link className="nav-link _header_nav_link_active _header_nav_link" to="/feed">
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="21" fill="none" viewBox="0 0 18 21">
                      <path className="_home_active" stroke="#000" strokeWidth="1.5" strokeOpacity=".6" d="M1 9.924c0-1.552 0-2.328.314-3.01.313-.682.902-1.187 2.08-2.196l1.143-.98C6.667 1.913 7.732 1 9 1c1.268 0 2.333.913 4.463 2.738l1.142.98c1.179 1.01 1.768 1.514 2.081 2.196.314.682.314 1.458.314 3.01v4.846c0 2.155 0 3.233-.67 3.902-.669.67-1.746.67-3.901.67H5.57c-2.155 0-3.232 0-3.902-.67C1 18.002 1 16.925 1 14.77V9.924z" />
                      <path className="_home_active" stroke="#000" strokeOpacity=".6" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M11.857 19.341v-5.857a1 1 0 00-1-1H7.143a1 1 0 00-1 1v5.857" />
                    </svg>
                  </Link>
                </li>
              </ul>
              <div className="_header_nav_profile">
                <div className="_header_nav_profile_image">
                  <Avatar src={user?.avatar_url} className="_nav_profile_img" />
                </div>
                <div className="_header_nav_dropdown">
                  <p className="_header_nav_para">{user?.full_name}</p>
                  <button
                    className="_header_nav_dropdown_btn _dropdown_toggle"
                    type="button"
                    onClick={() => setProfileDropOpen((o) => !o)}
                  >
                    <svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" fill="none" viewBox="0 0 10 6">
                      <path fill="#112032" d="M5 5l.354.354L5 5.707l-.354-.353L5 5zm4.354-3.646l-4 4-.708-.708 4-4 .708.708zm-4.708 4l-4-4 .708-.708 4 4-.708.708z" />
                    </svg>
                  </button>
                </div>
                {profileDropOpen && (
                  <div className="_nav_profile_dropdown _profile_dropdown" style={{ display: 'block' }}>
                    <div className="_nav_profile_dropdown_info">
                      <div className="_nav_profile_dropdown_image">
                        <Avatar src={user?.avatar_url} className="_nav_drop_img" />
                      </div>
                      <div className="_nav_profile_dropdown_info_txt">
                        <h4 className="_nav_dropdown_title">{user?.full_name}</h4>
                        <span className="_nav_drop_profile">{user?.email}</span>
                      </div>
                    </div>
                    <hr />
                    <ul className="_nav_dropdown_list">
                      <li className="_nav_dropdown_list_item">
                        <button className="_nav_dropdown_link w-100 border-0 bg-transparent text-start" onClick={handleLogout}>
                          <div className="_nav_drop_info">
                            <span>
                              <svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="none" viewBox="0 0 19 19">
                                <path stroke="#377DFF" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M6.667 18H2.889A1.889 1.889 0 011 16.111V2.89A1.889 1.889 0 012.889 1h3.778M13.277 14.222L18 9.5l-4.723-4.722M18 9.5H6.667"/>
                              </svg>
                            </span>
                            Log Out
                          </div>
                        </button>
                      </li>
                    </ul>
                  </div>
                )}
              </div>
            </div>
          </div>
        </nav>

        {/* Page content */}
        <div className="container _custom_container">
          <div className="_layout_inner_wrap">
            <Outlet />
          </div>
        </div>
      </div>
    </div>
  )
}
```

- [ ] **Step 3: Commit**

```bash
git add src/layouts/MainLayout.jsx src/components/Avatar.jsx
git commit -m "feat: MainLayout with navbar, profile dropdown, logout"
```

---

### Task 9: Posts Slice + Thunks

**Files:**
- Create: `src/features/posts/postsSlice.js`

- [ ] **Step 1: Create `src/features/posts/postsSlice.js`**

```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import api from '@/api/axios'

export const fetchPosts = createAsyncThunk(
  'posts/fetch',
  async (page = 1, { rejectWithValue }) => {
    try {
      const res = await api.get(`/api/posts?page=${page}`)
      return res.data
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const createPost = createAsyncThunk(
  'posts/create',
  async (formData, { rejectWithValue }) => {
    try {
      const res = await api.post('/api/posts', formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
      })
      return res.data.post
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const updatePost = createAsyncThunk(
  'posts/update',
  async ({ id, data }, { rejectWithValue }) => {
    try {
      const res = await api.put(`/api/posts/${id}`, data)
      return res.data.post
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const deletePost = createAsyncThunk(
  'posts/delete',
  async (id, { rejectWithValue }) => {
    try {
      await api.delete(`/api/posts/${id}`)
      return id
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const togglePostLike = createAsyncThunk(
  'posts/like',
  async ({ postId, currentlyLiked, currentCount }, { rejectWithValue }) => {
    try {
      const res = await api.post(`/api/posts/${postId}/like`)
      return { postId, ...res.data }
    } catch (err) {
      // Return the reverted values on error
      return rejectWithValue({ postId, liked: currentlyLiked, like_count: currentCount })
    }
  }
)

const postsSlice = createSlice({
  name: 'posts',
  initialState: {
    items:   [],
    page:    1,
    hasMore: true,
    loading: false,
    error:   null,
  },
  reducers: {
    optimisticToggleLike(state, action) {
      const { postId, liked, like_count } = action.payload
      const post = state.items.find((p) => p.id === postId)
      if (post) {
        post.liked_by_me = liked
        post.like_count  = like_count
      }
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending,   (s) => { s.loading = true })
      .addCase(fetchPosts.fulfilled, (s, a) => {
        s.loading = false
        const { data, meta } = a.payload
        if (meta.current_page === 1) {
          s.items = data
        } else {
          s.items = [...s.items, ...data]
        }
        s.page    = meta.current_page
        s.hasMore = meta.current_page < meta.last_page
      })
      .addCase(fetchPosts.rejected,  (s, a) => { s.loading = false; s.error = a.payload })
      .addCase(createPost.fulfilled, (s, a) => { s.items = [a.payload, ...s.items] })
      .addCase(updatePost.fulfilled, (s, a) => {
        const idx = s.items.findIndex((p) => p.id === a.payload.id)
        if (idx !== -1) s.items[idx] = a.payload
      })
      .addCase(deletePost.fulfilled, (s, a) => {
        s.items = s.items.filter((p) => p.id !== a.payload)
      })
      .addCase(togglePostLike.fulfilled, (s, a) => {
        const post = s.items.find((p) => p.id === a.payload.postId)
        if (post) {
          post.liked_by_me = a.payload.liked
          post.like_count  = a.payload.like_count
        }
      })
      .addCase(togglePostLike.rejected, (s, a) => {
        // Revert optimistic update
        const post = s.items.find((p) => p.id === a.payload.postId)
        if (post) {
          post.liked_by_me = a.payload.liked
          post.like_count  = a.payload.like_count
        }
      })
  },
})

export const { optimisticToggleLike } = postsSlice.actions
export default postsSlice.reducer
```

- [ ] **Step 2: Commit**

```bash
git add src/features/posts/postsSlice.js
git commit -m "feat: posts slice with fetch/create/update/delete/like thunks"
```

---

### Task 10: FeedPage (3-Column Layout)

**Files:**
- Create: `src/features/posts/FeedPage.jsx`
- Create: `src/components/Spinner.jsx`
- Create: `src/utils/formatDate.js`

- [ ] **Step 1: Create `src/utils/formatDate.js`**

```js
export function formatDate(isoString) {
  if (!isoString) return ''
  const date = new Date(isoString)
  const now  = new Date()
  const diff = Math.floor((now - date) / 1000)

  if (diff < 60)   return `${diff} seconds ago`
  if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`
  if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`
  return `${Math.floor(diff / 86400)} days ago`
}
```

- [ ] **Step 2: Create `src/components/Spinner.jsx`**

```jsx
export default function Spinner() {
  return (
    <div className="d-flex justify-content-center py-4">
      <div className="spinner-border text-primary" role="status">
        <span className="visually-hidden">Loading...</span>
      </div>
    </div>
  )
}
```

- [ ] **Step 3: Create `src/features/posts/FeedPage.jsx`**

```jsx
import { useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from './postsSlice'
import PostCard from './PostCard'
import PostComposer from './PostComposer'
import Spinner from '@/components/Spinner'
import peopleImg1 from '@/assets/images/people1.png'
import peopleImg2 from '@/assets/images/people2.png'
import peopleImg3 from '@/assets/images/people3.png'
import eventImg from '@/assets/images/feed_event1.png'

export default function FeedPage() {
  const dispatch           = useDispatch()
  const { items, loading, hasMore, page } = useSelector((s) => s.posts)
  const sentinelRef        = useRef(null)

  // Initial load
  useEffect(() => {
    dispatch(fetchPosts(1))
  }, [dispatch])

  // Infinite scroll
  useEffect(() => {
    if (!sentinelRef.current) return
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasMore && !loading) {
          dispatch(fetchPosts(page + 1))
        }
      },
      { threshold: 0.1 }
    )
    observer.observe(sentinelRef.current)
    return () => observer.disconnect()
  }, [dispatch, hasMore, loading, page])

  return (
    <div className="row">
      {/* Left Sidebar */}
      <div className="col-xl-3 col-lg-3 col-md-12 col-sm-12">
        <div className="_layout_left_sidebar_wrap">
          <div className="_layout_left_sidebar_inner">
            <div className="_left_inner_area_explore _padd_t24 _padd_b6 _padd_r24 _padd_l24 _b_radious6 _feed_inner_area">
              <h4 className="_left_inner_area_explore_title _title5 _mar_b24">Explore</h4>
              <ul className="_left_inner_area_explore_list">
                {[
                  { label: 'Learning', badge: 'New' },
                  { label: 'Insights' },
                  { label: 'Find friends' },
                  { label: 'Bookmarks' },
                  { label: 'Group' },
                  { label: 'Gaming', badge: 'New' },
                  { label: 'Settings' },
                ].map(({ label, badge }) => (
                  <li key={label} className="_left_inner_area_explore_item">
                    <a href="#0" className="_left_inner_area_explore_link">{label}</a>
                    {badge && <span className="_left_inner_area_explore_link_txt">{badge}</span>}
                  </li>
                ))}
              </ul>
            </div>
          </div>

          <div className="_layout_left_sidebar_inner">
            <div className="_left_inner_area_suggest _padd_t24 _padd_b6 _padd_r24 _padd_l24 _b_radious6 _feed_inner_area">
              <div className="_left_inner_area_suggest_content _mar_b24">
                <h4 className="_left_inner_area_suggest_content_title _title5">Suggested People</h4>
                <span className="_left_inner_area_suggest_content_txt">
                  <a href="#0" className="_left_inner_area_suggest_content_txt_link">See All</a>
                </span>
              </div>
              {[
                { img: peopleImg1, name: 'Steve Jobs',      role: 'CEO of Apple' },
                { img: peopleImg2, name: 'Ryan Roslansky',  role: 'CEO of LinkedIn' },
                { img: peopleImg3, name: 'Dylan Field',     role: 'CEO of Figma' },
              ].map(({ img, name, role }) => (
                <div key={name} className="_left_inner_area_suggest_info">
                  <div className="_left_inner_area_suggest_info_box">
                    <div className="_left_inner_area_suggest_info_image">
                      <img src={img} alt={name} className="_info_img" />
                    </div>
                    <div className="_left_inner_area_suggest_info_txt">
                      <h4 className="_left_inner_area_suggest_info_title">{name}</h4>
                      <p className="_left_inner_area_suggest_info_para">{role}</p>
                    </div>
                  </div>
                  <div className="_left_inner_area_suggest_info_link">
                    <a href="#0" className="_info_link">Connect</a>
                  </div>
                </div>
              ))}
            </div>
          </div>

          <div className="_layout_left_sidebar_inner">
            <div className="_left_inner_area_event _padd_t24 _padd_b6 _padd_r24 _padd_l24 _b_radious6 _feed_inner_area">
              <div className="_left_inner_event_content">
                <h4 className="_left_inner_event_title _title5">Events</h4>
                <a href="#0" className="_left_inner_event_link">See all</a>
              </div>
              <div className="_left_inner_event_card">
                <div className="_left_inner_event_card_iamge">
                  <img src={eventImg} alt="" className="_card_img" />
                </div>
                <div className="_left_inner_event_card_content">
                  <div className="_left_inner_card_date">
                    <p className="_left_inner_card_date_para">10</p>
                    <p className="_left_inner_card_date_para1">Jul</p>
                  </div>
                  <div className="_left_inner_card_txt">
                    <h4 className="_left_inner_event_card_title">No more terrorism no more cry</h4>
                  </div>
                </div>
                <hr className="_underline" />
                <div className="_left_inner_event_bottom">
                  <p className="_left_iner_event_bottom">17 People Going</p>
                  <a href="#0" className="_left_iner_event_bottom_link">Going</a>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Center Feed */}
      <div className="col-xl-6 col-lg-6 col-md-12 col-sm-12">
        <div className="_layout_middle_wrap">
          <div className="_layout_middle_inner">
            <PostComposer />
            {items.map((post) => (
              <PostCard key={post.id} post={post} />
            ))}
            {loading && <Spinner />}
            <div ref={sentinelRef} style={{ height: 1 }} />
            {!hasMore && items.length > 0 && (
              <p className="text-center text-muted py-3">No more posts</p>
            )}
          </div>
        </div>
      </div>

      {/* Right Sidebar */}
      <div className="col-xl-3 col-lg-3 col-md-12 col-sm-12">
        <div className="_layout_right_sidebar_wrap">
          <div className="_layout_right_sidebar_inner _feed_inner_area _padd_t24 _padd_b6 _padd_r24 _padd_l24 _b_radious6">
            <h4 className="_title5 _mar_b24">Who to follow</h4>
            {[
              { img: peopleImg1, name: 'Steve Jobs',     role: 'CEO of Apple' },
              { img: peopleImg2, name: 'Ryan Roslansky', role: 'CEO of LinkedIn' },
            ].map(({ img, name, role }) => (
              <div key={name} className="_left_inner_area_suggest_info">
                <div className="_left_inner_area_suggest_info_box">
                  <div className="_left_inner_area_suggest_info_image">
                    <img src={img} alt={name} className="_info_img" />
                  </div>
                  <div className="_left_inner_area_suggest_info_txt">
                    <h4 className="_left_inner_area_suggest_info_title">{name}</h4>
                    <p className="_left_inner_area_suggest_info_para">{role}</p>
                  </div>
                </div>
                <div className="_left_inner_area_suggest_info_link">
                  <a href="#0" className="_info_link">Follow</a>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  )
}
```

- [ ] **Step 4: Commit**

```bash
git add src/features/posts/FeedPage.jsx src/components/Spinner.jsx src/utils/formatDate.js
git commit -m "feat: FeedPage 3-column layout with infinite scroll sentinel"
```

---

### Task 11: PostCard Component

**Files:**
- Create: `src/features/posts/PostCard.jsx`

- [ ] **Step 1: Create `src/features/posts/PostCard.jsx`**

```jsx
import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { deletePost, optimisticToggleLike, togglePostLike, updatePost } from './postsSlice'
import CommentList from '@/features/comments/CommentList'
import Avatar from '@/components/Avatar'
import { formatDate } from '@/utils/formatDate'

export default function PostCard({ post }) {
  const dispatch              = useDispatch()
  const { user: currentUser } = useSelector((s) => s.auth)
  const [showComments, setShowComments] = useState(false)
  const [dropOpen, setDropOpen]         = useState(false)
  const [editing, setEditing]           = useState(false)
  const [editContent, setEditContent]   = useState(post.content)
  const [editVis, setEditVis]           = useState(post.visibility)

  const isOwner = currentUser?.id === post.user?.id

  const handleLike = () => {
    const newLiked = !post.liked_by_me
    const newCount = post.like_count + (newLiked ? 1 : -1)
    dispatch(optimisticToggleLike({ postId: post.id, liked: newLiked, like_count: newCount }))
    dispatch(togglePostLike({ postId: post.id, currentlyLiked: post.liked_by_me, currentCount: post.like_count }))
  }

  const handleDelete = () => {
    if (window.confirm('Delete this post?')) dispatch(deletePost(post.id))
  }

  const handleUpdate = (e) => {
    e.preventDefault()
    dispatch(updatePost({ id: post.id, data: { content: editContent, visibility: editVis } }))
    setEditing(false)
  }

  return (
    <div className="_feed_inner_timeline_post_area _b_radious6 _padd_b24 _padd_t24 _mar_b16">
      <div className="_feed_inner_timeline_content _padd_r24 _padd_l24">
        {/* Post Header */}
        <div className="_feed_inner_timeline_post_top">
          <div className="_feed_inner_timeline_post_box">
            <div className="_feed_inner_timeline_post_box_image">
              <Avatar src={post.user?.avatar_url} className="_post_img" />
            </div>
            <div className="_feed_inner_timeline_post_box_txt">
              <h4 className="_feed_inner_timeline_post_box_title">{post.user?.full_name}</h4>
              <p className="_feed_inner_timeline_post_box_para">
                {formatDate(post.created_at)} · <span>{post.visibility}</span>
              </p>
            </div>
          </div>

          {isOwner && (
            <div className="_feed_inner_timeline_post_box_dropdown" style={{ position: 'relative' }}>
              <button
                className="_feed_timeline_post_dropdown_link"
                onClick={() => setDropOpen((o) => !o)}
              >
                <svg xmlns="http://www.w3.org/2000/svg" width="4" height="17" fill="none" viewBox="0 0 4 17">
                  <circle cx="2" cy="2" r="2" fill="#C4C4C4" />
                  <circle cx="2" cy="8" r="2" fill="#C4C4C4" />
                  <circle cx="2" cy="15" r="2" fill="#C4C4C4" />
                </svg>
              </button>
              {dropOpen && (
                <div className="_feed_timeline_dropdown _timeline_dropdown" style={{ display: 'block' }}>
                  <ul className="_feed_timeline_dropdown_list">
                    <li className="_feed_timeline_dropdown_item">
                      <button className="_feed_timeline_dropdown_link border-0 bg-transparent w-100 text-start"
                        onClick={() => { setEditing(true); setDropOpen(false) }}>
                        Edit Post
                      </button>
                    </li>
                    <li className="_feed_timeline_dropdown_item">
                      <button className="_feed_timeline_dropdown_link border-0 bg-transparent w-100 text-start"
                        onClick={handleDelete}>
                        Delete Post
                      </button>
                    </li>
                  </ul>
                </div>
              )}
            </div>
          )}
        </div>

        {/* Post Content */}
        {editing ? (
          <form onSubmit={handleUpdate} className="mt-3">
            <textarea
              className="form-control mb-2"
              value={editContent}
              onChange={(e) => setEditContent(e.target.value)}
              rows={3}
            />
            <select
              className="form-select mb-2"
              value={editVis}
              onChange={(e) => setEditVis(e.target.value)}
            >
              <option value="public">Public</option>
              <option value="private">Private</option>
            </select>
            <button type="submit" className="_btn1 me-2">Save</button>
            <button type="button" className="btn btn-secondary btn-sm" onClick={() => setEditing(false)}>Cancel</button>
          </form>
        ) : (
          <>
            <p className="mt-2">{post.content}</p>
            {post.image_url && (
              <div className="_feed_inner_timeline_image">
                <img src={post.image_url} alt="Post" className="_time_img" style={{ maxWidth: '100%' }} />
              </div>
            )}
          </>
        )}
      </div>

      {/* Reaction counts */}
      <div className="_feed_inner_timeline_total_reacts _padd_r24 _padd_l24 _mar_b26">
        <div className="_feed_inner_timeline_total_reacts_image">
          {post.likers?.slice(0, 5).map((liker) => (
            <Avatar key={liker.id} src={liker.avatar_url} className="_react_img" alt={liker.full_name} />
          ))}
          {post.like_count > 0 && (
            <p className="_feed_inner_timeline_total_reacts_para">{post.like_count}</p>
          )}
        </div>
        <div className="_feed_inner_timeline_total_reacts_txt">
          <p className="_feed_inner_timeline_total_reacts_para1">
            <button
              className="border-0 bg-transparent p-0"
              onClick={() => setShowComments((v) => !v)}
            >
              <span>{post.comment_count}</span> Comment{post.comment_count !== 1 ? 's' : ''}
            </button>
          </p>
        </div>
      </div>

      {/* Reaction bar */}
      <div className="_feed_inner_timeline_reaction">
        <button
          className={`_feed_inner_timeline_reaction_emoji _feed_reaction${post.liked_by_me ? ' _feed_reaction_active' : ''}`}
          onClick={handleLike}
        >
          <span className="_feed_inner_timeline_reaction_link">
            {post.liked_by_me ? '❤️ Liked' : '🤍 Like'}
          </span>
        </button>
        <button
          className="_feed_inner_timeline_reaction_emoji _feed_reaction"
          onClick={() => setShowComments((v) => !v)}
        >
          <span className="_feed_inner_timeline_reaction_link">💬 Comment</span>
        </button>
      </div>

      {/* Comments */}
      {showComments && <CommentList postId={post.id} />}
    </div>
  )
}
```

- [ ] **Step 2: Commit**

```bash
git add src/features/posts/PostCard.jsx
git commit -m "feat: PostCard with like toggle, edit/delete dropdown, comment toggle"
```

---

### Task 12: PostComposer + CreatePostModal

**Files:**
- Create: `src/features/posts/PostComposer.jsx`
- Create: `src/components/Modal.jsx`

- [ ] **Step 1: Create `src/components/Modal.jsx`**

```jsx
import { useEffect } from 'react'

export default function Modal({ isOpen, onClose, title, children }) {
  useEffect(() => {
    if (isOpen) document.body.style.overflow = 'hidden'
    else document.body.style.overflow = ''
    return () => { document.body.style.overflow = '' }
  }, [isOpen])

  if (!isOpen) return null

  return (
    <div
      className="modal show d-block"
      style={{ background: 'rgba(0,0,0,0.5)' }}
      onClick={onClose}
    >
      <div className="modal-dialog modal-dialog-centered" onClick={(e) => e.stopPropagation()}>
        <div className="modal-content">
          <div className="modal-header">
            <h5 className="modal-title">{title}</h5>
            <button type="button" className="btn-close" onClick={onClose} />
          </div>
          <div className="modal-body">{children}</div>
        </div>
      </div>
    </div>
  )
}
```

- [ ] **Step 2: Create `src/features/posts/PostComposer.jsx`**

```jsx
import { useState, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { createPost } from './postsSlice'
import Modal from '@/components/Modal'
import Avatar from '@/components/Avatar'

export default function PostComposer() {
  const dispatch           = useDispatch()
  const { user }           = useSelector((s) => s.auth)
  const [open, setOpen]    = useState(false)
  const [content, setContent]     = useState('')
  const [visibility, setVisible]  = useState('public')
  const [image, setImage]         = useState(null)
  const [preview, setPreview]     = useState(null)
  const [loading, setLoading]     = useState(false)
  const fileRef                   = useRef()

  const handleImageChange = (e) => {
    const file = e.target.files[0]
    if (!file) return
    setImage(file)
    setPreview(URL.createObjectURL(file))
  }

  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!content.trim()) return
    setLoading(true)
    const fd = new FormData()
    fd.append('content', content)
    fd.append('visibility', visibility)
    if (image) fd.append('image', image)
    const result = await dispatch(createPost(fd))
    setLoading(false)
    if (createPost.fulfilled.match(result)) {
      setContent(''); setVisible('public'); setImage(null); setPreview(null)
      setOpen(false)
    }
  }

  return (
    <>
      <div
        className="_feed_inner_text_area _b_radious6 _padd_b24 _padd_t24 _padd_r24 _padd_l24 _mar_b16"
        style={{ cursor: 'pointer' }}
        onClick={() => setOpen(true)}
      >
        <div className="_feed_inner_text_area_box">
          <div className="_feed_inner_text_area_box_image">
            <Avatar src={user?.avatar_url} className="_txt_img" />
          </div>
          <div className="_feed_inner_text_area_box_form" style={{ flex: 1, paddingLeft: 12 }}>
            <span style={{ color: '#999' }}>Write something...</span>
          </div>
        </div>
      </div>

      <Modal isOpen={open} onClose={() => setOpen(false)} title="Create Post">
        <form onSubmit={handleSubmit}>
          <div className="d-flex align-items-center mb-3">
            <Avatar src={user?.avatar_url} className="_txt_img me-2" />
            <div>
              <strong>{user?.full_name}</strong>
              <div>
                <select
                  className="form-select form-select-sm"
                  value={visibility}
                  onChange={(e) => setVisible(e.target.value)}
                >
                  <option value="public">🌍 Public</option>
                  <option value="private">🔒 Private</option>
                </select>
              </div>
            </div>
          </div>
          <textarea
            className="form-control mb-3"
            rows={4}
            placeholder="What's on your mind?"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            autoFocus
          />
          {preview && (
            <div className="mb-3 position-relative">
              <img src={preview} alt="Preview" style={{ maxWidth: '100%', borderRadius: 8 }} />
              <button
                type="button"
                className="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"
                onClick={() => { setImage(null); setPreview(null) }}
              >✕</button>
            </div>
          )}
          <div className="d-flex justify-content-between align-items-center">
            <button type="button" className="btn btn-light" onClick={() => fileRef.current.click()}>
              📷 Add Photo
            </button>
            <input ref={fileRef} type="file" accept="image/*" className="d-none" onChange={handleImageChange} />
            <button type="submit" className="_btn1" disabled={loading || !content.trim()}>
              {loading ? 'Posting...' : 'Post'}
            </button>
          </div>
        </form>
      </Modal>
    </>
  )
}
```

- [ ] **Step 3: Commit**

```bash
git add src/features/posts/PostComposer.jsx src/components/Modal.jsx
git commit -m "feat: PostComposer modal with image upload and visibility selector"
```

---

### Task 13: Comments Slice + CommentList + CommentItem

**Files:**
- Create: `src/features/comments/commentsSlice.js`
- Create: `src/features/comments/CommentList.jsx`
- Create: `src/features/comments/CommentItem.jsx`

- [ ] **Step 1: Create `src/features/comments/commentsSlice.js`**

```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import api from '@/api/axios'

export const fetchComments = createAsyncThunk(
  'comments/fetch',
  async (postId, { rejectWithValue }) => {
    try {
      const res = await api.get(`/api/posts/${postId}/comments`)
      return { postId, comments: res.data.comments }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const createComment = createAsyncThunk(
  'comments/create',
  async ({ postId, content }, { rejectWithValue }) => {
    try {
      const res = await api.post(`/api/posts/${postId}/comments`, { content })
      return { postId, comment: res.data.comment }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const updateComment = createAsyncThunk(
  'comments/update',
  async ({ commentId, postId, content }, { rejectWithValue }) => {
    try {
      const res = await api.put(`/api/comments/${commentId}`, { content })
      return { postId, comment: res.data.comment }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const deleteComment = createAsyncThunk(
  'comments/delete',
  async ({ commentId, postId }, { rejectWithValue }) => {
    try {
      await api.delete(`/api/comments/${commentId}`)
      return { postId, commentId }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const toggleCommentLike = createAsyncThunk(
  'comments/like',
  async ({ commentId, postId }, { rejectWithValue }) => {
    try {
      const res = await api.post(`/api/comments/${commentId}/like`)
      return { postId, commentId, ...res.data }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

const commentsSlice = createSlice({
  name: 'comments',
  initialState: { byPostId: {}, loading: {} },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchComments.pending,   (s, a) => { s.loading[a.meta.arg] = true })
      .addCase(fetchComments.fulfilled, (s, a) => {
        s.loading[a.payload.postId] = false
        s.byPostId[a.payload.postId] = a.payload.comments
      })
      .addCase(fetchComments.rejected,  (s, a) => { s.loading[a.meta.arg] = false })
      .addCase(createComment.fulfilled, (s, a) => {
        const list = s.byPostId[a.payload.postId] || []
        s.byPostId[a.payload.postId] = [a.payload.comment, ...list]
      })
      .addCase(updateComment.fulfilled, (s, a) => {
        const { postId, comment } = a.payload
        const list = s.byPostId[postId] || []
        const idx  = list.findIndex((c) => c.id === comment.id)
        if (idx !== -1) list[idx] = { ...list[idx], ...comment }
      })
      .addCase(deleteComment.fulfilled, (s, a) => {
        const { postId, commentId } = a.payload
        s.byPostId[postId] = (s.byPostId[postId] || []).filter((c) => c.id !== commentId)
      })
      .addCase(toggleCommentLike.fulfilled, (s, a) => {
        const { postId, commentId, liked, like_count } = a.payload
        const list = s.byPostId[postId] || []
        const comment = list.find((c) => c.id === commentId)
        if (comment) { comment.liked_by_me = liked; comment.like_count = like_count }
      })
  },
})

export default commentsSlice.reducer
```

- [ ] **Step 2: Create `src/features/comments/CommentItem.jsx`**

```jsx
import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { updateComment, deleteComment, toggleCommentLike } from './commentsSlice'
import ReplyList from '@/features/replies/ReplyList'
import Avatar from '@/components/Avatar'
import { formatDate } from '@/utils/formatDate'

export default function CommentItem({ comment, postId }) {
  const dispatch              = useDispatch()
  const { user: currentUser } = useSelector((s) => s.auth)
  const [showReplies, setShowReplies] = useState(false)
  const [editing, setEditing]         = useState(false)
  const [editVal, setEditVal]         = useState(comment.content)

  const isOwner = currentUser?.id === comment.user?.id

  const handleUpdate = (e) => {
    e.preventDefault()
    dispatch(updateComment({ commentId: comment.id, postId, content: editVal }))
    setEditing(false)
  }

  return (
    <div className="d-flex gap-2 mb-3">
      <Avatar src={comment.user?.avatar_url} className="_info_img" />
      <div style={{ flex: 1 }}>
        <div className="_feed_inner_area _b_radious6 p-2" style={{ background: '#f8f9fa' }}>
          <strong>{comment.user?.full_name}</strong>
          {editing ? (
            <form onSubmit={handleUpdate}>
              <input
                className="form-control form-control-sm mt-1"
                value={editVal}
                onChange={(e) => setEditVal(e.target.value)}
              />
              <button type="submit" className="btn btn-sm btn-primary mt-1 me-1">Save</button>
              <button type="button" className="btn btn-sm btn-secondary mt-1" onClick={() => setEditing(false)}>Cancel</button>
            </form>
          ) : (
            <p className="mb-0">{comment.content}</p>
          )}
        </div>
        <div className="d-flex gap-3 mt-1" style={{ fontSize: 12, color: '#666' }}>
          <span>{formatDate(comment.created_at)}</span>
          <button
            className="border-0 bg-transparent p-0"
            style={{ fontSize: 12, color: comment.liked_by_me ? '#1890FF' : '#666' }}
            onClick={() => dispatch(toggleCommentLike({ commentId: comment.id, postId }))}
          >
            {comment.liked_by_me ? '❤️' : '🤍'} {comment.like_count || 0} Like
          </button>
          <button
            className="border-0 bg-transparent p-0"
            style={{ fontSize: 12 }}
            onClick={() => setShowReplies((v) => !v)}
          >
            💬 Reply {comment.replies?.length > 0 ? `(${comment.replies.length})` : ''}
          </button>
          {isOwner && (
            <>
              <button className="border-0 bg-transparent p-0" style={{ fontSize: 12 }} onClick={() => setEditing(true)}>Edit</button>
              <button
                className="border-0 bg-transparent p-0 text-danger"
                style={{ fontSize: 12 }}
                onClick={() => dispatch(deleteComment({ commentId: comment.id, postId }))}
              >Delete</button>
            </>
          )}
        </div>
        {showReplies && <ReplyList commentId={comment.id} postId={postId} initialReplies={comment.replies || []} />}
      </div>
    </div>
  )
}
```

- [ ] **Step 3: Create `src/features/comments/CommentList.jsx`**

```jsx
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchComments, createComment } from './commentsSlice'
import CommentItem from './CommentItem'
import Avatar from '@/components/Avatar'
import Spinner from '@/components/Spinner'

export default function CommentList({ postId }) {
  const dispatch  = useDispatch()
  const { user }  = useSelector((s) => s.auth)
  const comments  = useSelector((s) => s.comments.byPostId[postId] || [])
  const loading   = useSelector((s) => s.comments.loading[postId])
  const [text, setText] = useState('')

  useEffect(() => {
    dispatch(fetchComments(postId))
  }, [dispatch, postId])

  const handleSubmit = (e) => {
    e.preventDefault()
    if (!text.trim()) return
    dispatch(createComment({ postId, content: text }))
    setText('')
  }

  return (
    <div className="_padd_r24 _padd_l24 _padd_t16">
      <form onSubmit={handleSubmit} className="d-flex gap-2 mb-3">
        <Avatar src={user?.avatar_url} className="_info_img" />
        <div style={{ flex: 1, display: 'flex', gap: 8 }}>
          <input
            className="form-control form-control-sm"
            placeholder="Write a comment..."
            value={text}
            onChange={(e) => setText(e.target.value)}
          />
          <button type="submit" className="_btn1" disabled={!text.trim()}>Post</button>
        </div>
      </form>
      {loading ? <Spinner /> : comments.map((c) => (
        <CommentItem key={c.id} comment={c} postId={postId} />
      ))}
    </div>
  )
}
```

- [ ] **Step 4: Commit**

```bash
git add src/features/comments/
git commit -m "feat: comments slice, CommentList, CommentItem with like/edit/delete"
```

---

### Task 14: Replies Slice + ReplyList + ReplyItem

**Files:**
- Create: `src/features/replies/repliesSlice.js`
- Create: `src/features/replies/ReplyList.jsx`
- Create: `src/features/replies/ReplyItem.jsx`

- [ ] **Step 1: Create `src/features/replies/repliesSlice.js`**

```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import api from '@/api/axios'

export const createReply = createAsyncThunk(
  'replies/create',
  async ({ commentId, content }, { rejectWithValue }) => {
    try {
      const res = await api.post(`/api/comments/${commentId}/replies`, { content })
      return { commentId, reply: res.data.reply }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const updateReply = createAsyncThunk(
  'replies/update',
  async ({ replyId, commentId, content }, { rejectWithValue }) => {
    try {
      const res = await api.put(`/api/replies/${replyId}`, { content })
      return { commentId, reply: res.data.reply }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const deleteReply = createAsyncThunk(
  'replies/delete',
  async ({ replyId, commentId }, { rejectWithValue }) => {
    try {
      await api.delete(`/api/replies/${replyId}`)
      return { commentId, replyId }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

export const toggleReplyLike = createAsyncThunk(
  'replies/like',
  async ({ replyId, commentId }, { rejectWithValue }) => {
    try {
      const res = await api.post(`/api/replies/${replyId}/like`)
      return { commentId, replyId, ...res.data }
    } catch (err) {
      return rejectWithValue(err.response?.data)
    }
  }
)

const repliesSlice = createSlice({
  name: 'replies',
  initialState: { byCommentId: {} },
  reducers: {
    initReplies(state, action) {
      const { commentId, replies } = action.payload
      if (!state.byCommentId[commentId]) {
        state.byCommentId[commentId] = replies
      }
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(createReply.fulfilled, (s, a) => {
        const { commentId, reply } = a.payload
        const list = s.byCommentId[commentId] || []
        s.byCommentId[commentId] = [...list, reply]
      })
      .addCase(updateReply.fulfilled, (s, a) => {
        const { commentId, reply } = a.payload
        const list = s.byCommentId[commentId] || []
        const idx  = list.findIndex((r) => r.id === reply.id)
        if (idx !== -1) list[idx] = reply
      })
      .addCase(deleteReply.fulfilled, (s, a) => {
        const { commentId, replyId } = a.payload
        s.byCommentId[commentId] = (s.byCommentId[commentId] || []).filter((r) => r.id !== replyId)
      })
      .addCase(toggleReplyLike.fulfilled, (s, a) => {
        const { commentId, replyId, liked, like_count } = a.payload
        const reply = (s.byCommentId[commentId] || []).find((r) => r.id === replyId)
        if (reply) { reply.liked_by_me = liked; reply.like_count = like_count }
      })
  },
})

export const { initReplies } = repliesSlice.actions
export default repliesSlice.reducer
```

- [ ] **Step 2: Create `src/features/replies/ReplyItem.jsx`**

```jsx
import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { updateReply, deleteReply, toggleReplyLike } from './repliesSlice'
import Avatar from '@/components/Avatar'
import { formatDate } from '@/utils/formatDate'

export default function ReplyItem({ reply, commentId }) {
  const dispatch              = useDispatch()
  const { user: currentUser } = useSelector((s) => s.auth)
  const [editing, setEditing] = useState(false)
  const [editVal, setEditVal] = useState(reply.content)

  const isOwner = currentUser?.id === reply.user?.id

  const handleUpdate = (e) => {
    e.preventDefault()
    dispatch(updateReply({ replyId: reply.id, commentId, content: editVal }))
    setEditing(false)
  }

  return (
    <div className="d-flex gap-2 mb-2 ms-4">
      <Avatar src={reply.user?.avatar_url} className="_info_img" style={{ width: 28, height: 28 }} />
      <div style={{ flex: 1 }}>
        <div className="p-2 _b_radious6" style={{ background: '#f0f2f5' }}>
          <strong style={{ fontSize: 13 }}>{reply.user?.full_name}</strong>
          {editing ? (
            <form onSubmit={handleUpdate}>
              <input className="form-control form-control-sm mt-1" value={editVal} onChange={(e) => setEditVal(e.target.value)} />
              <button type="submit" className="btn btn-sm btn-primary mt-1 me-1">Save</button>
              <button type="button" className="btn btn-sm btn-secondary mt-1" onClick={() => setEditing(false)}>Cancel</button>
            </form>
          ) : (
            <p className="mb-0" style={{ fontSize: 13 }}>{reply.content}</p>
          )}
        </div>
        <div className="d-flex gap-3 mt-1" style={{ fontSize: 11, color: '#666' }}>
          <span>{formatDate(reply.created_at)}</span>
          <button
            className="border-0 bg-transparent p-0"
            style={{ fontSize: 11, color: reply.liked_by_me ? '#1890FF' : '#666' }}
            onClick={() => dispatch(toggleReplyLike({ replyId: reply.id, commentId }))}
          >
            {reply.liked_by_me ? '❤️' : '🤍'} {reply.like_count || 0}
          </button>
          {isOwner && (
            <>
              <button className="border-0 bg-transparent p-0" style={{ fontSize: 11 }} onClick={() => setEditing(true)}>Edit</button>
              <button
                className="border-0 bg-transparent p-0 text-danger"
                style={{ fontSize: 11 }}
                onClick={() => dispatch(deleteReply({ replyId: reply.id, commentId }))}
              >Delete</button>
            </>
          )}
        </div>
      </div>
    </div>
  )
}
```

- [ ] **Step 3: Create `src/features/replies/ReplyList.jsx`**

```jsx
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { createReply, initReplies } from './repliesSlice'
import ReplyItem from './ReplyItem'
import Avatar from '@/components/Avatar'

export default function ReplyList({ commentId, initialReplies }) {
  const dispatch  = useDispatch()
  const { user }  = useSelector((s) => s.auth)
  const replies   = useSelector((s) => s.replies.byCommentId[commentId] || [])
  const [text, setText] = useState('')

  useEffect(() => {
    dispatch(initReplies({ commentId, replies: initialReplies }))
  }, [dispatch, commentId])

  const handleSubmit = (e) => {
    e.preventDefault()
    if (!text.trim()) return
    dispatch(createReply({ commentId, content: text }))
    setText('')
  }

  return (
    <div className="mt-2">
      {replies.map((r) => (
        <ReplyItem key={r.id} reply={r} commentId={commentId} />
      ))}
      <form onSubmit={handleSubmit} className="d-flex gap-2 mt-2 ms-4">
        <Avatar src={user?.avatar_url} className="_info_img" />
        <input
          className="form-control form-control-sm"
          placeholder="Write a reply..."
          value={text}
          onChange={(e) => setText(e.target.value)}
        />
        <button type="submit" className="_btn1" disabled={!text.trim()}>Reply</button>
      </form>
    </div>
  )
}
```

- [ ] **Step 4: Commit**

```bash
git add src/features/replies/
git commit -m "feat: replies slice, ReplyList, ReplyItem with like/edit/delete"
```

---

### Task 15: Final Wiring + Smoke Test

**Files:**
- Verify: `src/app/store.js` has all 4 reducers (auth, posts, comments, replies)

- [ ] **Step 1: Confirm store imports all slices**

Open `src/app/store.js` and confirm it matches exactly:

```js
import { configureStore } from '@reduxjs/toolkit'
import authReducer     from '@/features/auth/authSlice'
import postsReducer    from '@/features/posts/postsSlice'
import commentsReducer from '@/features/comments/commentsSlice'
import repliesReducer  from '@/features/replies/repliesSlice'

export const store = configureStore({
  reducer: {
    auth:     authReducer,
    posts:    postsReducer,
    comments: commentsReducer,
    replies:  repliesReducer,
  },
})
```

- [ ] **Step 2: Start the dev server**

```bash
cd "D:/assesment-react/assesment-react"
npm run dev
```

- [ ] **Step 3: Smoke test the full app flow**

1. Open `http://localhost:5173`
2. Should redirect to `/login` — verify login page renders matching design
3. Click "Create New Account" — verify register page renders with first/last name fields
4. Register a new user — should redirect to `/feed`
5. Feed loads — verify 3-column layout renders
6. Click "Write something..." — verify create post modal opens
7. Create a post with text + photo, set visibility to Public — post appears at top of feed
8. Like the post — verify like count increments immediately (optimistic)
9. Click "Comment" — verify comment input appears
10. Add a comment — verify it appears
11. Click "Reply" on the comment — verify reply input and submission works
12. Click the edit dropdown on your post — verify edit/delete work
13. Click profile dropdown → Log Out — verify redirect to login

- [ ] **Step 4: Fix any visual issues**

If any element has broken CSS class names, compare to the original HTML file and fix the `className` in the JSX.

- [ ] **Step 5: Final commit**

```bash
git add -A
git commit -m "feat: complete social media app — auth, feed, posts, comments, replies, likes"
```
