Skip to content

Commit 4f7ed7c

Browse files
committed
feat: image cropping in admin dashboard
- Added react-image-crop to ImageUpload.tsx (admin only) - Square crop (1:1) for team photos - Landscape crop (1200:630) for blog and product covers - Free crop for inline blog images - Crop overlay with Cancel and Apply Crop buttons - admin-only — never imported on public pages
1 parent d8300fc commit 4f7ed7c

6 files changed

Lines changed: 271 additions & 114 deletions

File tree

AGENTS.md

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Do not change any of these without explicit instruction from the user.
3030
| Blog editor | TipTap (rich text, stores JSON) |
3131
| File storage | Cloudinary |
3232
| Email | Resend |
33+
| Image cropping | react-image-crop (admin only — never on public pages) |
3334
| Fonts | JetBrains Mono + IBM Plex Sans |
3435
| Hosting | Vercel |
3536
| Package manager | pnpm — NEVER use npm or yarn |
@@ -105,7 +106,7 @@ Footer: bg-[#F4F5F8] border-t border-[#C4CAD6]
105106
- **Light theme only.** No dark mode. No `dark:` Tailwind variants.
106107
- **No animations.** Nothing moves. No keyframes, no motion libraries.
107108
- **Minimal hover effects.** Color or opacity changes only.
108-
- **No UI libraries.** Build everything from scratch with Tailwind.
109+
- **No UI component libraries.** Build everything from scratch with Tailwind.
109110
- **No gradients.** Solid colors only.
110111
- **Use borders sparingly.** Prefer spacing and background contrast.
111112
- **No shadows** except subtle `shadow-sm` on cards where needed.
@@ -124,28 +125,20 @@ Logo files in `public/logos/`:
124125
### Icons
125126

126127
- **Never install an icon library** (no lucide-react, heroicons package, react-icons, etc.)
127-
- All icons are **inline SVGs** written directly in the component
128-
- To find icon SVG paths: browse heroicons.com or lucide.dev, copy the raw SVG code only — not the package import
129-
- All inline SVGs: width 24, height 24, stroke="currentColor" or fill="#121F38"
130-
- Never use emojis as UI icons — always use inline SVGs
128+
- All icons use the **shared icons file** at `src/components/ui/icons.tsx`
129+
- Only add icons to icons.tsx that are actually used — never pre-populate with unused icons
130+
- Import only what you need in each component — named imports only
131+
- SVG paths sourced from heroicons.com or lucide.dev — copy raw SVG markup only, no package imports
132+
- All SVGs: width and height appropriate to context, stroke="currentColor" or fill="#121F38"
133+
- **Never use emojis as UI icons** — always use inline SVGs from icons.tsx
131134

132135
```tsx
133-
// CORRECT — inline SVG
134-
function TrophyIcon() {
135-
return (
136-
<svg width="24" height="24" viewBox="0 0 24 24"
137-
fill="none" stroke="currentColor" strokeWidth="2">
138-
<path d="M6 9H4a2 2 0 0 1-2-2V5h4"/>
139-
<path d="M18 9h2a2 2 0 0 0 2-2V5h-4"/>
140-
<path d="M12 17v4"/>
141-
<path d="M8 21h8"/>
142-
<path d="M6 9a6 6 0 0 0 12 0V5H6z"/>
143-
</svg>
144-
)
145-
}
146-
147-
// WRONG — never do this
136+
// CORRECT — named import from shared icons file
137+
import { TrophyIcon, ArrowRightIcon } from '@/components/ui/icons'
138+
139+
// WRONG — never install or import from icon libraries
148140
import { Trophy } from 'lucide-react'
141+
import * as Icons from '@/components/ui/icons' // never import everything
149142
```
150143

151144
---
@@ -206,7 +199,8 @@ codeddevs-website/
206199
│ │ │ ├── Badge.tsx
207200
│ │ │ ├── Card.tsx
208201
│ │ │ ├── Input.tsx
209-
│ │ │ └── Textarea.tsx
202+
│ │ │ ├── Textarea.tsx
203+
│ │ │ └── icons.tsx # shared inline SVG icons
210204
│ │ ├── sections/
211205
│ │ │ ├── HeroSection.tsx
212206
│ │ │ ├── ProductsSection.tsx
@@ -221,7 +215,7 @@ codeddevs-website/
221215
│ │ │ └── ContactForm.tsx
222216
│ │ └── admin/
223217
│ │ ├── RichTextEditor.tsx
224-
│ │ ├── ImageUpload.tsx
218+
│ │ ├── ImageUpload.tsx # includes react-image-crop
225219
│ │ └── DataTable.tsx
226220
│ ├── db/
227221
│ │ ├── index.ts
@@ -281,7 +275,7 @@ Admin toggles this manually per post.
281275
Controls the placement badge shown on the Recognition card.
282276
Values: `'1st' | '2nd' | '3rd' | 'winner' | null`
283277
Only relevant when show_in_recognition is true.
284-
Displayed as an inline SVG icon + label — never as an emoji.
278+
Displayed as an inline SVG icon + label from icons.tsx — never as an emoji.
285279

286280
### careers
287281
```ts
@@ -409,13 +403,13 @@ Five sections in order:
409403
- Fetches blog posts where `show_in_recognition = true` AND `is_published = true`
410404
- Ordered by published_at DESC, limit 3
411405
- Cards — text only, no cover image:
412-
- Inline SVG placement icon + placement label (from `placement` field)
406+
- Inline SVG placement icon (from icons.tsx) + placement label
413407
- Category badge
414408
- Blog post title (JetBrains Mono, bold)
415409
- Excerpt (IBM Plex Sans, 2 lines max, truncated)
416410
- Date (formatted, muted, small)
417411
- "Read the story →" → links to /blog/[slug]
418-
- Placement display uses inline SVG icons — never emojis
412+
- Placement display uses inline SVG icons from icons.tsx — never emojis
419413
- This is curated — admin manually toggles show_in_recognition per post
420414
- If no recognition posts exist, section does not render
421415
@@ -500,6 +494,17 @@ By [author] · [formatted date] · [X min read]
500494
- Blog covers: `codeddevs-website/blogs/` — 1200x630px
501495
- Inline article images: 1200x800px
502496
497+
### Image cropping (admin dashboard only)
498+
The `ImageUpload.tsx` component uses `react-image-crop` to let admins
499+
crop images before uploading to Cloudinary:
500+
- Team photo uploads → square crop locked (1:1 aspect ratio)
501+
- Blog cover uploads → landscape crop locked (1200:630 aspect ratio)
502+
- Product cover uploads → landscape crop locked (1200:630 aspect ratio)
503+
- Inline blog images → free crop (no aspect ratio lock)
504+
505+
`react-image-crop` is imported ONLY in `ImageUpload.tsx`.
506+
Never import it in any public page or component.
507+
503508
### Cloudinary URL transformations
504509
Use helpers from `src/lib/cloudinary.ts`. Never use raw Cloudinary URLs.
505510
@@ -575,7 +580,7 @@ const [products, posts] = await Promise.all([
575580
6. Cloudinary for all content images — use transformation helpers
576581
7. Resend for all email — never nodemailer or sendgrid
577582
8. next/font/google for fonts — no CDN link tags
578-
9. No UI libraries — build from scratch with Tailwind
583+
9. No UI component libraries on public pages — build from scratch with Tailwind. Exception: `react-image-crop` is permitted in `ImageUpload.tsx` (admin only) for image cropping before upload. Never import it in any public page or component.
579584
10. cn() for all conditional classNames
580585
11. No animations — nothing moves
581586
12. Light theme only — no dark: variants
@@ -589,8 +594,8 @@ const [products, posts] = await Promise.all([
589594
20. Blog URL /blog, displayed as "Updates" in all user-facing labels
590595
21. Use borders sparingly — prefer spacing and background contrast
591596
22. Design must feel human, not AI-generated — avoid generic layouts
592-
23. No emojis in UI components — use inline SVG icons only
593-
24. Never install icon libraries — copy raw SVG paths from heroicons.com or lucide.dev
597+
23. No emojis in UI components — use inline SVG icons from icons.tsx only
598+
24. Never install icon libraries — use shared icons.tsx with raw SVG paths sourced from heroicons.com or lucide.dev
594599
595600
---
596601
@@ -623,4 +628,4 @@ const [products, posts] = await Promise.all([
623628
624629
---
625630
626-
*Last updated: May 2026*
631+
*Last updated: May 2026*

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"react": "18.3.1",
2727
"react-dom": "18.3.1",
2828
"react-email": "^6.0.5",
29+
"react-image-crop": "^11.0.10",
2930
"resend": "^6.12.2",
3031
"tailwind-merge": "^3.5.0",
3132
"zod": "^4.4.1"

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)