/opt/canhelp/apps/api/src/routes
NameSizeModeActions
admin.ts950570644editdlrm
auto-match.ts74060644editdlrm
auto-response.ts81110644editdlrm
availability.ts72940644editdlrm
categories.ts18670644editdlrm
chat.ts93770644editdlrm
dashboard.ts51420644editdlrm
favorites.ts44410644editdlrm
fomo.ts68440644editdlrm
google.ts48720644editdlrm
health.ts12140644editdlrm
i18n.ts1670860644editdlrm
locations.ts17810644editdlrm
notifications.ts28620644editdlrm
offers.ts100470644editdlrm
payments.ts211670644editdlrm
plans.ts13520644editdlrm
portfolio.ts66580644editdlrm
price-list.ts144170644editdlrm
reports.ts20070644editdlrm
reviews.ts27830644editdlrm
skill-suggestions.ts10350644editdlrm
specialist-cards.ts186410644editdlrm
stats.ts10930644editdlrm
statuses.ts37310644editdlrm
support.ts84520644editdlrm
tasks.ts300180644editdlrm
uploads.ts15260644editdlrm
users.ts371570644editdlrm
Edit: /opt/canhelp/apps/api/src/routes/fomo.ts (6844B)
import { Hono } from 'hono' import { requireAuth, type AuthVariables } from '../middleware/auth.js' import { withPlan } from '../middleware/plan.js' import { getSpecialistFomoDashboard, getCustomerDashboard } from '../lib/fomo.js' import { db } from '../db.js' import { autoResponseSettings } from '@canhelp/db' import { eq } from 'drizzle-orm' import { getHelperCapability } from '../lib/helper-capability.js' const app = new Hono<{ Variables: AuthVariables }>() // GET /fomo/specialist — FOMO dashboard for the current specialist app.get('/specialist', requireAuth, withPlan, async (c) => { const user = c.get('user') const isHelperReady = await getHelperCapability(user.id) if (!isHelperReady) { return c.json({ error: 'Specialists only' }, 403) } const plan = c.get('plan' as never) as { tier: string } | null const dashboard = await getSpecialistFomoDashboard(user.id) // Tier-specific messaging const tier = plan?.tier ?? 'free' let fomoMessage: string | null = null if (tier === 'free') { if (dashboard.today.missedTotal > 0) { fomoMessage = `Σήμερα χάσατε ${dashboard.today.missedTotal} παραγγελίες (~€${dashboard.today.missedRevenue}). ${dashboard.today.missedByPro > 0 ? `${dashboard.today.missedByPro} πήγαν σε PRO ειδικούς.` : ''} Αναβαθμίστε για να μην χάνετε!` } } else if (tier === 'pro') { if (dashboard.today.receivedOrders > 0) { fomoMessage = `Καλά πάτε! ${dashboard.today.receivedOrders} παραγγελίες σήμερα. Κάλυψη: ${dashboard.today.coveragePercent}%.` } } else if (tier === 'ultimate') { if (dashboard.today.receivedOrders > 0 || dashboard.today.missedTotal > 0) { fomoMessage = `Στατιστικά: ${dashboard.today.receivedOrders} ληφθείσες, ${dashboard.today.missedTotal} χαμένες. Κάλυψη: ${dashboard.today.coveragePercent}%.` } } return c.json({ ...dashboard, fomoMessage, tier }) }) // GET /fomo/customer — customer conversion dashboard (task stats overview) app.get('/customer', requireAuth, withPlan, async (c) => { const user = c.get('user') const isHelperReady = await getHelperCapability(user.id) if (isHelperReady) { return c.json({ error: 'Customers only' }, 403) } const plan = c.get('plan' as never) as { tier: string } | null const tier = plan?.tier ?? 'free' const dashboard = await getCustomerDashboard(user.id) // Tier-specific messaging let conversionMessage: string | null = null if (tier === 'free') { const totalOffers = dashboard.activeTasks.reduce((sum, t) => sum + t.totalOffers, 0) const totalViews = dashboard.activeTasks.reduce((sum, t) => sum + t.totalViews, 0) if (totalViews > 0 || totalOffers > 0) { conversionMessage = `Τα tasks σας είδαν ${totalViews} ειδικοί και λάβατε ${totalOffers} προσφορές. Με PRO θα βλέπετε ποιοι είδαν τα tasks σας!` } } else if (tier === 'pro' || tier === 'ultimate') { const proViews = dashboard.activeTasks.reduce((sum, t) => sum + t.proViews, 0) if (proViews > 0) { conversionMessage = `${proViews} PRO ειδικοί είδαν τα tasks σας.` } } return c.json({ ...dashboard, conversionMessage, tier }) }) // GET /fomo/recommendations — tier-specific feature recommendations app.get('/recommendations', requireAuth, withPlan, async (c) => { const user = c.get('user') const plan = c.get('plan' as never) as { tier: string hasAutoResponse: boolean hasAutoMatch: boolean hasFavorites: boolean hasGoogleCalendar: boolean canShowContactInfo: boolean hasPersonalSite: boolean } | null const tier = plan?.tier ?? 'free' const recommendations: Array<{ key: string; title: string; description: string; action: string }> = [] const isHelperReady = await getHelperCapability(user.id) if (isHelperReady) { if (tier === 'free') { recommendations.push( { key: 'upgrade_pro', title: 'Αναβαθμίστε σε Pro', description: 'Λάβετε περισσότερους πελάτες, δείξτε τα στοιχεία επικοινωνίας και προβληθείτε ψηλότερα στην αναζήτηση.', action: '/plans' }, { key: 'add_portfolio', title: 'Προσθέστε portfolio', description: 'Οι ειδικοί με portfolio λαμβάνουν 3x περισσότερα αιτήματα.', action: '/portfolio' }, ) } else if (tier === 'pro') { recommendations.push( { key: 'upgrade_ultimate', title: 'Αναβαθμίστε σε Ultimate', description: 'Ενεργοποιήστε αυτόματη απόκριση, badge επαλήθευσης και απεριόριστες δεξιότητες.', action: '/plans' }, ) if (!plan?.hasGoogleCalendar) { recommendations.push( { key: 'enable_calendar', title: 'Συνδέστε Google Calendar', description: 'Συγχρονίστε το πρόγραμμά σας αυτόματα.', action: '/schedule' }, ) } } else if (tier === 'ultimate') { // Check if auto-response is configured const [autoResp] = await db .select({ isEnabled: autoResponseSettings.isEnabled }) .from(autoResponseSettings) .where(eq(autoResponseSettings.specialistId, user.id)) if (!autoResp?.isEnabled) { recommendations.push( { key: 'enable_auto_response', title: 'Ενεργοποιήστε αυτόματη απόκριση', description: 'Μην χάνετε παραγγελίες — απαντήστε αυτόματα σε κατάλληλα αιτήματα.', action: '/auto-response' }, ) } } } else { if (tier === 'free') { recommendations.push( { key: 'upgrade_pro', title: 'Αναβαθμίστε σε Pro', description: 'Επικοινωνήστε με όλους τους ειδικούς και δείτε στατιστικά αναζήτησης.', action: '/plans' }, ) } else if (tier === 'pro') { recommendations.push( { key: 'upgrade_ultimate', title: 'Αναβαθμίστε σε Ultimate', description: 'Αυτόματος αντιστοίχιση με τον καλύτερο ειδικό για κάθε εργασία.', action: '/plans' }, ) } else if (tier === 'ultimate') { recommendations.push( { key: 'try_auto_match', title: 'Δοκιμάστε αυτόματη αντιστοίχιση', description: 'Βρείτε τον τέλειο ειδικό αυτόματα βάσει βαθμολογίας και εμπειρίας.', action: '/auto-match' }, ) } } return c.json({ tier, recommendations }) }) export default app