Skip to content

feat: add fb-rent-filter tool#1

Open
pathors wants to merge 21 commits into
mainfrom
feat/fb-rent-filter
Open

feat: add fb-rent-filter tool#1
pathors wants to merge 21 commits into
mainfrom
feat/fb-rent-filter

Conversation

@pathors
Copy link
Copy Markdown

@pathors pathors commented Mar 14, 2026

fb-rent-filter 🏠

把 FB 租屋貼文丟進去,AI 幫你洗出結構化資料。

功能

  • 貼文輸入:一次可貼多篇(空白行分隔),按「分析」送出
  • AI 萃取(gpt-4o via Vercel AI SDK generateObject):
    • 月租金、押金、地區、地址、坪數、房型、樓層
    • 特色 tags(近捷運、附冷氣、寵物友善 etc.)
    • 聯絡方式、可入住時間
  • 結果表格:支援價格 / 坪數排序、刪除列;手機改 card layout
  • 持久化:localStorage(重整不掉)
  • 匯出:CSV / JSON 下載
  • 分享:資料 encode 到 URL query param,丟給朋友就能直接看

啟動

cd tools/fb-rent-filter
cp .env.example .env.local
# 填入 OPENAI_API_KEY
bun install && bun dev

之後可加

  • Cloudflare Workers + D1 做雲端清單共享
  • 換 GPT-5 model
  • 地圖標點

pathors added 2 commits March 14, 2026 16:40
Next.js 15 app that extracts structured rental info from FB posts using OpenAI gpt-4o via Vercel AI SDK.

Features:
- Paste FB rental posts → AI extracts price, location, size, room type, features, contact, etc.
- Results displayed in sortable table (price/size), card layout on mobile
- localStorage persistence across page refreshes
- Export as CSV or JSON download
- Share via URL (data encoded in query param)
- Multi-post input (separated by blank lines)
- D1 database: fb-rent-filter-db (ae874d9d)
- New API routes: POST /api/lists, GET/DELETE /api/lists/[id]
- Shared list view: /list/[id]
- Cloud save button on main page
- wrangler.jsonc config
- D1 migration: migrations/0001_init.sql
- All routes set to edge runtime
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented Mar 14, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
28714994 Triggered Generic High Entropy Secret 27bafa0 tools/fb-rent-filter/.vercel/output/functions/index.rsc.prerender-config.json View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

pathors added 19 commits March 14, 2026 18:48
- Switch from @cloudflare/next-on-pages (Pages) to @opennextjs/cloudflare (Workers)
- Update wrangler.jsonc: Worker main + assets binding
- Add open-next.config.ts
- API routes use nodejs runtime (nodejs_compat flag)
- OPENAI_API_KEY stored as Worker secret (never reaches client)
- Update scripts: worker:build, worker:dev, worker:deploy
- Add edgeExternals: ['node:crypto'] to open-next.config.ts
- Migrate getRequestContext() -> getCloudflareContext() from @opennextjs/cloudflare
- Remove @cloudflare/next-on-pages dependency
- Editorial design: Playfair Display + DM Sans fonts
- Warm white bg (#FAFAF8), stone-900 buttons, no gradients
- Feature tags → dot-separated text
- Toast system replacing alert()/confirm()
- Share URL fix: btoa(encodeURIComponent) / decodeURIComponent(atob)
- Cloud save inline panel (no prompt())
- SVG logo (earthy accent)
- AI model: gpt-4o → gpt-5-mini
- New endpoint: POST /api/lists/[id]/records (append records)
- /list/[id] page is now interactive: has RentInput, appends to D1 list
- Remove base64 URL sharing entirely — all sharing via D1
- ExportBar: onCloudSave is now optional (not shown on shared list page)
- Main page: removed ?data= URL param handling
- D1 migration: add status, notes columns to records
- New endpoint: PATCH /api/lists/[id]/records/[recordId]
- Status: interested/contacted/visited/rejected with color badges
- Click badge to cycle through statuses
- Inline notes editing with auto-save
- Filter bar: filter cards by status
- Main page: localStorage sync
- Shared list page: D1 sync on status/notes change
- D1 post_cache table: hash(post) -> structured_result, hit_count
- analyze API: check cache first, skip OpenAI if hit
- Auto-create D1 list on first analysis, redirect to /list/<uuid>
- /list/<uuid>: record count / 30 limit, share button, editable name
- Remove localStorage as primary storage
- manifest.json (MV3), content.js, popup.html/js
- Auto-scrapes FB group posts, AI analyzes, saves to D1 list
- Filter by max price and district
- Auto-scroll to load more posts
- Progress tracking in popup
- icons (16/48/128px)
- PUBLISHING.md with local testing + Chrome Web Store guide
- notes: z.string().nullable() (was optional().nullable() which creates invalid anyOf schema)
- omit status and notes from generateObject schema, add defaults manually
- OpenAI structured output rejects anyOf+not patterns from Zod optional/nullable combo
- Shimmer skeleton for list page (navbar, header, cards, count bar)
- Skeleton textarea + button on main page while creating list
- Dot pulse animation on analyze button (replaces spinner icon)
- dotPulse + shimmer keyframes in globals.css
- D1 rate_limits table (migration 0004)
- lib/rateLimit.ts: per-minute window, D1 upsert counter
- /api/analyze: max 5/min per IP → 429 with Retry-After header
- /api/lists POST: max 20/min per IP
- CF-Connecting-IP header for real IP (Cloudflare edge)
- Fails open on D1 error (don't block legit traffic)
- Auto-cleanup of 2h+ old windows
PWA:
- public/manifest.json (share_target, shortcuts, icons)
- public/sw.js (network-first, cache homepage)
- public/icons/pwa-{192,512}.png
- components/RegisterSW.tsx (client-side SW registration)
- app/layout.tsx: manifest meta, apple-web-app, viewport, themeColor

Install prompt:
- components/PWAInstallToast.tsx
- Shows on /list/[id] page (2s delay, once)
- Chrome/Android: native beforeinstallprompt + install button
- iOS: shows share → add to home screen instructions
- Dismissed state in localStorage

New fields:
- subsidyEligible (可租補), parking — schema + D1 + API + card UI
- migrations/0005_subsidy.sql

Map overview:
- app/list/[id]/map/page.tsx — Leaflet CDN + Nominatim geocoding
- Navbar map button on list page
- Brand: FB 租屋過濾器 → 租多好室 (layout, manifest, pages, navbar)
- Map: explicit height calc(100vh-100px), invalidateSize(), tile URL fix
- PWA icons: house silhouette SVG → sharp rendered PNG (192/512)
- logo.svg: house design
- iOS PWA: navigator.standalone check in install toast
- Mobile-only bottom tab bar: 首頁/新增/地圖/分享
- Homepage: ← 返回清單 when navigated from list page
- List page content: bottom padding calc(80px + safe-area) for tab bar
- Mobile navbar: map button hidden (moved to tab bar)
- Delete button: opacity 1 on mobile (no hover needed)
- Scroll-to-top FAB: appears after 300px scroll, above tab bar
… banner

- Homepage: OpenListInput - paste URL/UUID to navigate to list in PWA
- Homepage: Share Target handler - auto-redirect when friend shares list URL to PWA
- Lucide icons: tab bar, PWAInstallToast, RecentLists, RentTable (Car/MapPin)
- BrowserOpenBanner: orange banner when viewing in browser (not PWA)
…dling

- Inline full Leaflet CSS to avoid CDN timing issue (tiles not showing)
- Map container uses flex:1 instead of calc(100vh - Xpx)
- main uses height:100dvh + flexDirection:column + overflow:hidden
- requestAnimationFrame + setTimeout before map init
- mapInitRef to prevent double-init in StrictMode
- fitBounds after all markers added
- Better error handling + catch block
- Remove sticky navbar from homepage
- Logo (56px) + app name centered at top
- Clean tagline, no noise
- Input as main CTA, maxWidth 640, centered
- Below-fold: open friend list + recent lists
- hint text centered below input
- Status bar: ⏳→Loader2(spinning), 📍→MapPin (Lucide)
- Bottom nav bar: ChevronLeft/ChevronRight buttons
- Click next/prev: map.setView(coords, zoom=16) + marker.openPopup()
- First open: ➡ button highlighted orange to hint interaction
- Shows current listing: price, title, N/M counter
- Markers collected into state array for navigation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant