Skip to content

Commit 555a75e

Browse files
committed
feat: updates, recognition, performance and cleanup
Schema: - Added show_in_recognition column to blog_posts - Added placement column to blog_posts - Generated and pushed migrations to Neon Features: - Recognition section on home page (curated, admin-controlled) - show_in_recognition toggle in admin blog form - placement field in admin blog form (shown when recognition is on) - Blog post editorial layout with full width cover image - Reading time calculation from TipTap JSON content - Latest Releases section pulling all categories Performance: - Cloudinary transformation helpers (getBlogCoverUrl etc) - getOptimisedUrl() with context-specific transforms - g_face crop on team photos - Remaining ISR revalidate=3600 on public pages - Full OG and Twitter metadata on all pages - robots.ts and sitemap.ts Cleanup: - HackathonStrip component removed - Inline SVG icons via shared icons.tsx - No emoji usage in UI components - Image upload folder routing (?folder= param) - All content images using Cloudinary helpers
1 parent a05dcc9 commit 555a75e

38 files changed

Lines changed: 2090 additions & 882 deletions

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- [ ] Docs
1010

1111
## Checklist
12-
- [ ] I have read CLAUDE.md
12+
- [ ] I have read AGENTS.md
1313
- [ ] pnpm build passes locally with no errors
1414
- [ ] No TypeScript errors (pnpm tsc --noEmit)
1515
- [ ] No hardcoded secrets or API keys

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ coverage/
1414
.turbo/
1515
drizzle/
1616
*.tsbuildinfo
17+
18+
# AI assistant local files — do not commit
19+
CLAUDE.md
20+
GEMINI.md
21+
.cursorrules
22+
PROMPTS.md

AGENTS.md

Lines changed: 113 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,33 @@ Logo files in `public/logos/`:
121121
- Never recreate the logo in code. Always use the actual files.
122122
- Navbar logo always links to `/`
123123

124+
### Icons
125+
126+
- **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
131+
132+
```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
148+
import { Trophy } from 'lucide-react'
149+
```
150+
124151
---
125152

126153
## 4. Folder Structure
@@ -187,18 +214,19 @@ codeddevs-website/
187214
│ │ │ ├── RecognitionSection.tsx
188215
│ │ │ └── TeamSection.tsx
189216
│ │ ├── blog/
190-
│ │ │ └── PostContent.tsx # TipTap read-only renderer
217+
│ │ │ └── PostContent.tsx
191218
│ │ ├── careers/
192219
│ │ │ └── ApplicationForm.tsx
193-
│ │ ── contact/
194-
│ │ └── ContactForm.tsx
220+
│ │ ── contact/
221+
│ │ └── ContactForm.tsx
195222
│ │ └── admin/
196223
│ │ ├── RichTextEditor.tsx
197224
│ │ ├── ImageUpload.tsx
198225
│ │ └── DataTable.tsx
199226
│ ├── db/
200227
│ │ ├── index.ts
201228
│ │ ├── schema.ts
229+
│ │ ├── queries.ts
202230
│ │ └── migrations/
203231
│ ├── lib/
204232
│ │ ├── auth.ts
@@ -214,7 +242,7 @@ codeddevs-website/
214242
├── tsconfig.json
215243
├── .env.local
216244
├── .env.example
217-
├── CLAUDE.md
245+
├── AGENTS.md
218246
└── package.json
219247
```
220248

@@ -239,22 +267,35 @@ order_index, created_at, updated_at
239267
```ts
240268
id, title, slug, excerpt, content (json — TipTap),
241269
cover_url, author, category, is_published,
242-
show_in_recognition, published_at, created_at, updated_at
270+
show_in_recognition, placement, published_at,
271+
created_at, updated_at
243272
```
244273

245274
**category enum:** `'Product Update' | 'Announcement' | 'Roadmap' | 'Story'`
246-
**show_in_recognition:** `boolean, notNull, default(false)` — controls whether post appears in the Recognition section on the home page. Admin toggles this manually per post.
275+
276+
**show_in_recognition:** `boolean, notNull, default(false)`
277+
Controls whether post appears in the Recognition section on the home page.
278+
Admin toggles this manually per post.
279+
280+
**placement:** `text, nullable`
281+
Controls the placement badge shown on the Recognition card.
282+
Values: `'1st' | '2nd' | '3rd' | 'winner' | null`
283+
Only relevant when show_in_recognition is true.
284+
Displayed as an inline SVG icon + label — never as an emoji.
247285

248286
### careers
249287
```ts
250288
id, title, type, location, description,
251289
requirements, is_open, created_at, updated_at
252290
```
253291

292+
**type enum:** `'full-time' | 'contract' | 'volunteer'`
293+
254294
### career_applications
255295
```ts
256-
id, career_id (→ careers.id), full_name, email,
257-
portfolio_url, github_url, cover_letter, status, created_at
296+
id, career_id (→ careers.id onDelete cascade),
297+
full_name, email, portfolio_url, github_url,
298+
cover_letter, status, created_at
258299
```
259300

260301
**status enum:** `'pending' | 'reviewed' | 'rejected'`
@@ -286,6 +327,9 @@ RESEND_API_KEY=
286327
CONTACT_NOTIFICATION_EMAIL=codeddevs.team@gmail.com
287328
```
288329

330+
Use `DATABASE_URL` for all app queries.
331+
Use `DATABASE_URL_UNPOOLED` only in `drizzle.config.ts` for migrations.
332+
289333
---
290334

291335
## 7. API Routes
@@ -299,7 +343,7 @@ CONTACT_NOTIFICATION_EMAIL=codeddevs.team@gmail.com
299343
### Admin (401 if no session)
300344
| Method | Route | Description |
301345
|---|---|---|
302-
| POST | `/api/upload` | Upload to Cloudinary |
346+
| POST | `/api/upload?folder=[folder]` | Upload to Cloudinary in correct subfolder |
303347
| GET/POST | `/api/admin/team` | List / create |
304348
| GET/PUT/DELETE | `/api/admin/team/[id]` | Read / update / delete |
305349
| GET/POST | `/api/admin/products` | List / create |
@@ -313,6 +357,15 @@ CONTACT_NOTIFICATION_EMAIL=codeddevs.team@gmail.com
313357
| GET | `/api/admin/messages` | List |
314358
| PUT/DELETE | `/api/admin/messages/[id]` | Mark read / delete |
315359

360+
### Upload folder routing
361+
The `/api/upload` route accepts a `?folder=` query param:
362+
- Team photos → `?folder=team`
363+
- Product covers → `?folder=products`
364+
- Blog covers → `?folder=blogs`
365+
- Inline blog images → `?folder=blogs/inline`
366+
367+
All uploads go to `codeddevs-website/[folder]/` in Cloudinary.
368+
316369
---
317370

318371
## 8. Route Protection
@@ -335,6 +388,7 @@ Five sections in order:
335388
- Headline: "Engineering Software That Works for Africa"
336389
- Subtext: "We build AI-first software products for African markets — from first principles, not adaptations."
337390
- CTAs: "See Our Products" → /products | "Get in Touch" → /contact
391+
- Kody mascot (kodyfigma.svg — neutral) featured in hero
338392
339393
**2. Products Section**
340394
- Heading: "What We're Building"
@@ -354,13 +408,15 @@ Five sections in order:
354408
- Heading: "Recognition"
355409
- Fetches blog posts where `show_in_recognition = true` AND `is_published = true`
356410
- Ordered by published_at DESC, limit 3
357-
- Cards (Option B style — no images):
358-
- Placement badge: 🥇 1st Place / 🥉 3rd Place
359-
- Blog post title
360-
- Excerpt (short)
361-
- Date
411+
- Cards — text only, no cover image:
412+
- Inline SVG placement icon + placement label (from `placement` field)
413+
- Category badge
414+
- Blog post title (JetBrains Mono, bold)
415+
- Excerpt (IBM Plex Sans, 2 lines max, truncated)
416+
- Date (formatted, muted, small)
362417
- "Read the story →" → links to /blog/[slug]
363-
- This is curated — admin manually toggles show_in_recognition on specific posts
418+
- Placement display uses inline SVG icons — never emojis
419+
- This is curated — admin manually toggles show_in_recognition per post
364420
- If no recognition posts exist, section does not render
365421
366422
**5. About Teaser**
@@ -372,7 +428,7 @@ Five sections in order:
372428
- Company facts: RC 9426867 | Lagos, Nigeria | Est. March 2026
373429
374430
### Products (/products)
375-
- Lists all products from DB
431+
- Lists all products from DB ordered by order_index
376432
- Each card: name, tagline, status badge
377433
- Links to /products/[slug] (internal) and external_url (external)
378434
@@ -382,28 +438,29 @@ Five sections in order:
382438
- Related blog posts
383439
384440
### Blog (/blog) — displayed as "Updates"
385-
- URL stays /blog. All labels say "Updates"
441+
- URL stays /blog. All user-facing labels say "Updates"
386442
- Lists published posts ordered by published_at DESC
387443
- Filterable by: All | Product Update | Announcement | Roadmap | Story
388444
- Each card: category badge, title, excerpt, author, date, dynamic CTA
389445
390446
### Blog Post (/blog/[slug])
391447
Editorial layout:
392-
```text
393-
[Cover image — full width, 1200x630px]
448+
```
449+
[Cover image — full width, 1200x630px, priority prop for LCP]
394450
CATEGORY BADGE
395451
Title (JetBrains Mono, H1)
396-
By [author] · [date] · [X min read]
397-
─────────────────────────────────
398-
[TipTap rendered content — IBM Plex Sans body]
452+
By [author] · [formatted date] · [X min read]
453+
─────────────────────────────────────────────
454+
[TipTap rendered content — IBM Plex Sans body, max-w-3xl]
399455
```
400-
- Reading time calculated from word count
401-
- Cover image rendered at full width
402-
- Content rendered via PostContent.tsx (TipTap read-only)
456+
- Cover image: full width, no max-w constraint
457+
- Article content: max-w-3xl centered for readable line length
458+
- Reading time calculated from TipTap JSON word count
459+
- Content rendered via PostContent.tsx (TipTap read-only, 'use client')
403460
404461
### Team (/team)
405462
- Fetches team_members where is_active = true, ordered by order_index
406-
- Each card: photo (800x800px from Cloudinary), name, role, bio, social links
463+
- Each card: photo (Cloudinary, g_face crop), name, role, bio, social links
407464
- Founders:
408465
- **Kareem Aliameen — Founder & CEO**
409466
Kareem is the Founder and CEO of CodedDevs Technology LTD, leading the company's strategy, product vision and development, and technical direction. A full-stack engineer working primarily in JavaScript and TypeScript, he is highly skilled at leveraging AI for development, research, and productivity. He brings a background spanning graphic design, digital commerce, and entrepreneurship, and is currently studying at Miva University.
@@ -415,10 +472,12 @@ By [author] · [date] · [X min read]
415472
### Careers (/careers)
416473
- Lists open roles
417474
- Empty state: "No open roles right now. Send us a message." → /contact
418-
- Application form: inline below role card
475+
- Application form: inline below role card, 'use client'
419476
420477
### Contact (/contact)
421-
- Two columns: contact info left, form right
478+
- Two columns desktop, stacked mobile
479+
- Left: company info, email, social links
480+
- Right: contact form
422481
- Subjects: General Inquiry | Partnership | Press | Investment | Other
423482
- Email: codeddevs.team@gmail.com
424483
- Socials: GitHub, X, TikTok, YouTube, Instagram
@@ -433,34 +492,33 @@ By [author] · [date] · [X min read]
433492
- `/public/mascot/kody-smilefigma.svg` — smiling Kody
434493
- `/public/mascot/kodyfigma.svg` — neutral Kody
435494
- `/public/fav-icon/logo.png` — favicon files
436-
- Nothing else in public/
495+
- Nothing else goes in public/
437496
438497
### Content images → Cloudinary always
439-
- Team photos: upload to `codeddevs-website/team/` — 800x800px
440-
- Product covers: upload to `codeddevs-website/products/` — 1200x630px
441-
- Blog covers: upload to `codeddevs-website/blogs/` — 1200x630px
498+
- Team photos: `codeddevs-website/team/` — 800x800px
499+
- Product covers: `codeddevs-website/products/` — 1200x630px
500+
- Blog covers: `codeddevs-website/blogs/` — 1200x630px
442501
- Inline article images: 1200x800px
443502
444503
### Cloudinary URL transformations
445-
The `getOptimisedUrl()` helper in `src/lib/cloudinary.ts` appends
446-
transformations automatically. Never use raw Cloudinary URLs directly.
504+
Use helpers from `src/lib/cloudinary.ts`. Never use raw Cloudinary URLs.
447505
448506
```ts
449-
// Context-specific transformations:
450-
Blog cover banner: f_auto,q_auto,w_1200,h_630,c_fill
451-
Recognition card: f_auto,q_auto,w_600,h_315,c_fill
452-
Blog list thumbnail: f_auto,q_auto,w_800,h_420,c_fill
453-
Team photo: f_auto,q_auto,w_400,h_400,c_fill,g_face
454-
Product cover: f_auto,q_auto,w_1200,h_630,c_fill
507+
getBlogCoverUrl(url) // f_auto,q_auto,w_1200,h_630,c_fill
508+
getBlogThumbnailUrl(url) // f_auto,q_auto,w_800,h_420,c_fill
509+
getRecognitionCardUrl(url) // f_auto,q_auto,w_600,h_315,c_fill
510+
getTeamPhotoUrl(url) // f_auto,q_auto,w_400,h_400,c_fill,g_face
511+
getProductCoverUrl(url) // f_auto,q_auto,w_1200,h_630,c_fill
455512
```
456513
457-
`g_face` on team photos tells Cloudinary to focus the crop on the face.
514+
`g_face` on team photos focuses the crop on the face automatically.
458515
459516
### Image component rules
460517
- Always use Next.js `<Image>` for Cloudinary images
461-
- SVGs from public/ can use `<Image>` or `<img>` — both fine
518+
- SVGs from public/ can use `<Image>` or `<img>`
462519
- Never use raw `<img>` for content images
463520
- Always set meaningful `alt` text
521+
- Add `priority` prop to above-the-fold images (hero, blog cover)
464522
465523
---
466524
@@ -502,7 +560,7 @@ const [products, posts] = await Promise.all([
502560
- Fetch full columns only on detail/single pages
503561
504562
### robots.ts and sitemap.ts
505-
- Block: /admin, /api
563+
- Block: /admin, /api from crawlers
506564
- Expose all public routes + dynamic product/blog slugs
507565
508566
---
@@ -514,23 +572,25 @@ const [products, posts] = await Promise.all([
514572
3. Auth check first on every admin route — 401 if no session
515573
4. Zod validation on every API route that accepts a body
516574
5. pnpm only — never npm or yarn
517-
6. Cloudinary for all content images
518-
7. Resend for all email
575+
6. Cloudinary for all content images — use transformation helpers
576+
7. Resend for all email — never nodemailer or sendgrid
519577
8. next/font/google for fonts — no CDN link tags
520578
9. No UI libraries — build from scratch with Tailwind
521579
10. cn() for all conditional classNames
522580
11. No animations — nothing moves
523581
12. Light theme only — no dark: variants
524-
13. No gradients
582+
13. No gradients — solid colors only
525583
14. TypeScript strict — no any, no @ts-ignore
526584
15. @/ imports only — no relative ../../ imports
527585
16. Product/external links always target="_blank" rel="noopener noreferrer"
528586
17. migrations/ is read-only — only Drizzle Kit writes here
529-
18. Logo files only — never recreate in code
530-
19. "Products" not "Projects" — everywhere
531-
20. Blog URL /blog, displayed as "Updates" everywhere
587+
18. Logo files only — never recreate logo in code
588+
19. "Products" not "Projects" — everywhere in UI, routes, and code
589+
20. Blog URL /blog, displayed as "Updates" in all user-facing labels
532590
21. Use borders sparingly — prefer spacing and background contrast
533-
22. Design must feel human, not AI-generated
591+
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
534594
535595
---
536596
@@ -555,10 +615,10 @@ const [products, posts] = await Promise.all([
555615
## 15. Security
556616
557617
- Never commit secrets — .env.local is gitignored
558-
- All PRs require review from @onerandomdevv
559-
- Auth, DB schema, deployment changes need explicit approval
618+
- All PRs require review from @onerandomdevv before merging
619+
- Auth, DB schema, deployment changes need explicit human approval
560620
- Never auto-merge agent-generated code
561-
- Rotate keys immediately if exposed
621+
- Rotate keys immediately if credentials are exposed
562622
- Security contact: codeddevs.team@gmail.com
563623
564624
---

0 commit comments

Comments
 (0)