Перейти до вмісту

«Мої пріоритети»

ПолеЗначення
Статус🚧 в розробці — гілка vladbandurin/priorities-dock (9 комітів попереду origin/main, не запушена, гілки немає на remote). Реалізована лише поверхня калькулятора. Верифіковано локально: десктоп — немодальний пристикований <aside> справа, мобілка — нижній sheet, dockState персиститься між навігаціями.
Поверхніweb — постійний док на всіх публічних локаль-сторінках (рейка/нижній бар → панель/sheet); прихований у кабінеті /profile/*, де живе повний конструктор «Симуляції». MVP-точка входу — тизер шансів калькулятора. Поза MVP (закладено архітектурно): офер-картки пошуку (Фаза 2) і рейтингові списки (Фаза 3).
Доступністьбезкоштовно, без акаунта — гість збирає, сортує і бачить статус локально (localStorage). Акаунт потрібен лише щоб зберегти/синхронізувати список. Платний анлок — тільки вердикт у «Симуляції вступу» (entitlement simulation, premium one-off).
Власникфронт — Шура (за дизайн-спекою)
Останнє підтвердження2026-06-27 · гілка:vladbandurin/priorities-dock (85b457ca, локальна) + локальний Playwright-smoke (десктоп немодальний aside + мобільний bottom-sheet, dockState персиститься) + дизайн-спека .context/priorities-dock/

«Мої пріоритети» — це безкоштовний верх воронки до платної «Симуляції вступу». Він закриває три тривоги абітурієнта-2026 ще до пейволу: «чи я взагалі проходжу хоч кудись на бюджет?», «як зібрати короткий список без 40 вкладок?» і «який порядок подати, поки це ще можна змінити?» (порядок заяв у ЄДЕБО після подачі незмінний — це головний емоційний важіль усього сезону).

Замість збирати стратегію вручну (Excel, зошит, скріни) абітурієнт одним кліком кидає вступи з тизера шансів калькулятора у постійний бічний док, бачить по кожному рядку статус «куди проходжу» (на бюджет / на контракт / не проходиш), перетягує порядок — і коли готовий, апселиться у повну (платну) симуляцію за справжнім алгоритмом розподілу.

Принцип «один список, скрізь». Док і сторінка-конструктор /profile/simulation показують той самий список (simulation_application на сервері / локальний дзеркальний для гостя). Док = легкий завжди-доступний вид + reorder; конструктор = повний верстак. Ніколи не два списки.

Свідомо ≠ «Вступлісти». «Вступлісти» = збережені каталоги оферів (публічні/приватні колекції). «Мої пріоритети» = один впорядкований робочий список заяв із наміром і статусом. На MVP вони лишаються окремими сутностями.

Межа free / платне (визначає всю цінність доку)

Section titled “Межа free / платне (визначає всю цінність доку)”
ДіяДоступ
Зібрати список (додати/видалити, намір бюджет/контракт)безкоштовно (гість або акаунт)
Перетягнути порядок пріоритетів (drag + ▲▼)безкоштовно
Статус «куди проходжу» по кожній заяві (на бюджет / на контракт / не проходиш / введи бал)безкоштовно (клієнтська евристика)
Безкоштовний cascade-preview («за цим порядком бюджет спрацював би на №N — нижчі не розглядатимуться»)безкоштовно
Повна симуляція-вердикт (deferred-acceptance, широкий конкурс, прохідний-2026)платно (entitlement simulation)

Чому так можна: пейвол сидить лише на рендері сторінки /profile/simulation (useSimulationGate). Ендпоінти add/reorder/hydrate не за пейволом; гість ніколи не isBlocked. Док використовує лише ці безкоштовні шляхи + апселить платний вердикт.

⚠️ Статус-чіп — евристика, не вердикт. Він рахується клієнтсько проти прохідного балу 2025 (derivePriorityStatus.ts), а не за живими даними 2026. Футер доку це чесно називає: «Статуси — орієнтовно, за торішнім балом. Точний вердикт 2026 — у повній симуляції.» Це і є демо платної цінності, тому довіра до чіпа — головний шарнір конверсії.

Точка входу (MVP): https://abitly.org/nmt-calculator → тизер шансів «куди проходиш». Док присутній на кожній публічній сторінці (змонтований у кореневому [locale]/layout.tsx); прихований лише в кабінеті /profile/*.

  1. Гість рахує бал на калькуляторі НМТ → ChancesTeaserSection показує оффери «куди проходиш» (кожна ChanceCard уже несе offerId, probability high=бюджет / medium=контракт, passingScore2025, delta).
  2. «+ у пріоритети» (AddToPrioritiesButton, сусід картки) → usePriorities.add пише { offerId, intent, offerMeta } у usePrioritiesStore (localStorage abitly_priorities). Намір авто: high→budget, medium→contract. Дедуп + кап ≤10 (на ліміті картка → «Список повний (10)»; дубль → «№N у пріоритетах»). Не тригерить реєстраційний CTA. Подія priorities_item_added.
  3. Док засвічується. Десктоп — вертикальна рейка на правому краї (іконка + лічильник + кольорова смужка «N на бюджет»); мобілка — тонкий нижній бар. Мікро-пульс на щойно доданому рядку (1.4 с), aria-live анонс.
  4. Відкриття доку (клік рейки / Alt+P з будь-де, фізична клавіша KeyP → працює і на укр-розкладці) → dockState='expanded', подія priorities_dock_opened. Десктоп розгортає немодальний пристикований <aside> ~380px (без scrim, без focus-trap, без body-scroll-lock — сторінка позаду повністю клікабельна); мобілка — bottom-sheet із ручкою.
  5. Перегляд списку — драг-рядки зі статус-чіпом «куди проходжу» (рахується клієнтсько: КБ із калькулятора vs passingScore2025; без балу → «введи бал»). Хедер показує N + «X на бюджет / Y контракт», банер при 5 на бюджет / 10 всього.
  6. Reorder — drag (framer Reorder, debounce 400 мс → priorities_reordered method='drag') або ▲▼ на рядку (movepriorities_reordered method='arrows'); порядок = priority (index+1), оптимістично, автозбереження локально.
  7. Безкоштовний cascade-preview під списком: «За цим порядком бюджет спрацював би на №N — нижчі вступи не розглядатимуться» + (якщо №N > 1) «Мрія першою — це безпечно. Бюджет однаково спрацює на наступному вступі, де проходиш.» (вбиває міф «висока мрія спалює бюджет»). Рядки нижче першого бюджетного позначаються belowBudget (приглушені).
  8. Видалення + undoremove фаєрить тост «Прибрано … · Скасувати» (5 с, insertAt повертає на місце), подія priorities_item_removed.
  9. Гість → збереження — картка «💾 Збережи список — зараз лише на цьому пристрої» → goSave: rememberRegCta('priorities_dock') + storeReturnUrl()/auth/register. Подія priorities_save_prompt_clicked.
  10. Міграція на акаунт — після auth CallbackPage повертає на місце; usePrioritiesMigration робить replay локального списку → simulationApi.add({items}) (bulk, у порядку пріоритету; сервер дедупить). Подія priorities_migrated_on_auth {count, dropped}. Локальний список лишається джерелом відображення доку (read-side серверна гідрація — наступна фаза).
  11. Апсел — футер доку «▸ Повна симуляція вступу» → goFullSim (priorities_full_sim_cta_clicked) → router.push('/profile/simulation') → платний вердикт «Симуляції вступу» (entitlement simulation, Monobank-checkout).
flowchart TD
    A[Калькулятор НМТ → тизер «куди проходиш»] -->|+ у пріоритети\npriorities_item_added| B[(usePrioritiesStore\nlocalStorage гостя)]
    B --> C[Рейка засвітилась\nдесктоп: правий край · мобілка: нижній бар]
    C -->|клік / Alt+P\npriorities_dock_opened| D[Док розгорнуто\nнемодальний aside / bottom-sheet]
    D --> E[Статус-чіп «куди проходжу»\nевристика КБ vs прохідний-2025]
    D -->|drag / ▲▼\npriorities_reordered| F[Reorder + cascade-preview\n«бюджет на №N — нижчі не розглянуть»]
    F --> G{Гість?}
    G -->|так\npriorities_save_prompt_clicked| H[/auth/register\nstoreReturnUrl]
    H -->|після auth\npriorities_migrated_on_auth| I[replay → simulationApi.add\nсписок у кабінеті]
    G -->|ні| I
    F -->|футер: Повна симуляція\npriorities_full_sim_cta_clicked| J[/profile/simulation]
    I -->|готовий → апсел| J
    J -->|платний| K[«Симуляція вступу»\nentitlement simulation → Monobank]

Воронки та аналітика (sales funnels)

Section titled “Воронки та аналітика (sales funnels)”

Це серце фічі: безкоштовна звичка (зібрав → відсортував → побачив статус) конвертується у платний вердикт. Канал як у решти сайту: track()window.dataLayer → GTM GTM-W64R6XXK → GA4 G-XKDRSM3T0Z (dataLayer фаєриться лише на хості abitly.org — QA через GA4 DebugView). Усі події — IDs-only / PII-safe: лише offer_id, count, булеві прапори; ніколи назв ЗВО/спеціальностей чи сирих балів.

ПоверхняЩо цеПодія / source
Тизер шансів калькулятора (ChanceCardAddToPrioritiesButton)єдина точка входу MVP; PrioritySource має лише 'calculator_chances'priorities_item_added {offer_id, intent, source:'calculator_chances', count, is_authenticated}
Рейка/нижній бар докузавжди-доступний тригер на всіх публічних сторінкахподія лише на відкриття (priorities_dock_opened), не на показ рейки
Картка «Збережи список» (гість)м’який прес у природний моментpriorities_save_prompt_clicked {count}/auth/register
Футер доку «Повна симуляція вступу»головний апсел у платну воронкуpriorities_full_sim_cta_clicked {count}/profile/simulation
Офер-картки пошуку / рейтингові спискиФаза 2/3 — закладено в типах, ще не підключеноTODO: нові source (потрібно розширити PrioritySource)

Шлях конверсії (free habit → paid verdict)

Section titled “Шлях конверсії (free habit → paid verdict)”
flowchart LR
    subgraph FREE[Безкоштовна звичка]
      ADD[priorities_item_added] --> OPEN[priorities_dock_opened]
      OPEN --> REORD[priorities_reordered]
      REORD --> SAVE[priorities_save_prompt_clicked]
      SAVE --> MIG[priorities_migrated_on_auth]
    end
    REORD -->|апсел| CTA[priorities_full_sim_cta_clicked]
    MIG -->|апсел| CTA
    CTA --> SIM[/profile/simulation]
    subgraph PAID[Платна воронка «Симуляції»]
      SIM --> PAYWALL[paywall-модалка]
      PAYWALL --> BC[simulation_begin_checkout]
      BC --> PUR[simulation_purchase]
    end

Типізовані події цієї фічі (7, src/lib/analytics/events/priorities.ts)

Section titled “Типізовані події цієї фічі (7, src/lib/analytics/events/priorities.ts)”
ПодіяДе фаєриться (чокпоінт)PropertiesПризначення у воронці
priorities_item_addedusePriorities.add (тільки якщо список реально виріс — дедуп/кап = no-op без події)offer_id, intent, source, count, is_authenticatedактивація; count===1 = перше додавання
priorities_item_removedusePriorities.removeoffer_id, count, is_authenticatedвідтік зі списку
priorities_reorderedusePriorities.reorder (drag, debounce 400 мс) і usePriorities.move (▲▼)count, method:'drag'|'arrows'головна цінна дія — репетиція незмінного порядку
priorities_dock_openedPrioritiesDock.open (клік рейки / Alt+P)countзалученість (рейка→відкриття)
priorities_save_prompt_clickedPrioritiesDock.goSave (картка «Збережи список»)countгість → реєстрація (capture)
priorities_migrated_on_authusePrioritiesMigration після authcount, droppedуспішність переносу гостьового списку на акаунт
priorities_full_sim_cta_clickedPrioritiesDock.goFullSim (футер-апсел)countперехід у платну воронку

Стик із платною воронкою «Симуляції»

Section titled “Стик із платною воронкою «Симуляції»”

Після priorities_full_sim_cta_clicked користувач потрапляє у воронку «Симуляції вступу» та її paywall-події: simulation_constructor_viewedsimulation_begin_checkout {source, value, items} → (Monobank) → simulation_purchase {transaction_id, value} (+ simulation_payment_init_failed на помилці). Покупку-по-поверхні редірект Monobank стирає → читається когортно через лійку begin_checkout(source) → purchase.

Відомі прогалини інструментарію (із sales-аудиту)

Section titled “Відомі прогалини інструментарію (із sales-аудиту)”
  • Немає impression-події на показ AddToPrioritiesButton і на появу рейки → add-rate і rail→open рахуються неточно (знаменник невидимий).
  • priorities_save_prompt_shown описаний у дизайн-спеці (§9), але в коді не реалізований — фаєриться лише *_clicked; прес пасивний (внизу відкритої панелі, не на рейці).
  • dropped у міграції — all-or-nothing: будь-яка помилка → dropped = увесь батч; реальні часткові дропи на серверних капах (5/10) атрибутуються невірно.
  • method:'arrows' фаєриться лише з ▲▼ на рядку; решта reorder — 'drag'.
  • Кап/дедуп — тихий no-op без події → користувач на ліміті 10 (саме high-intent для апселу) не вимірюється окремо.

Стан GTM/GA4 та публікація

Section titled “Стан GTM/GA4 та публікація”
  • Не зроблено: події не зареєстровані як custom dimensions у GA4 і GTM-workspace не опубліковано (TODO: — окремий людський крок, як і для simulation_*; ⚠️ GA4-property вже на 49/50 event-scoped CD — близько ліміту). До публікації події летять у dataLayer, але в GA4-звітах їх не видно.
  • Свідома прогалина: маркетингові лінки Meta/TikTok на калькулятор мусять нести UTM (fbclid невидимий для GA4), інакше джерело трафіку у воронку доку губиться.
ДжерелоЕндпоінт / entityПризначення
localStorageключ abitly_priorities (zustand+persist, skipHydration + ручний rehydrate)гостьовий список + персист dockState (док лишається відкритим між навігаціями)
api-v2POST /profile/simulation (simulationApi.add, bulk {items:[{offerId,intent}]})logged-in add (fire-and-forget) + replay при міграції; сервер дедупить по (user, offer, intent)
api-v2POST /profile/simulation/hydrate · PUT /…/orderплан — серверний статус «куди проходжу» і серверний reorder для logged-in (read-side, ще не підключено в доці)
Postgressimulation_applicationспільна сутність списку з «Симуляцією»: userId + offerId + intent + priority; ліміти ≤10 / ≤5 на бюджет

Жодного нового бекенду в MVP. Док переюзає наявні ендпоінти «Симуляції» через фасад usePriorities(): гість — локальний стор, акаунт — simulationApi. Статус гостя рахується клієнтсько з offerMeta (кешується з картки, з якої додали). Можлива оптимізація (Фаза 2+) — серверний POST /profile/simulation/import замість клієнтського replay, якщо replay виявиться крихким на лімітах/дедупі.

Зв’язки з іншими фічами

Section titled “Зв’язки з іншими фічами”
  • «Симуляція вступу»платна вершина цієї воронки; той самий список (simulation_application), апсел-CTA веде в конструктор /profile/simulation; статус-чіп доку = безкоштовне демо платного вердикту.
  • Калькулятор конкурсного балаєдина точка входу MVP: тизер шансів («куди проходиш») рендерить «+ у пріоритети»; КБ із калькулятора живить статус-чіп.
  • Checkout Monobank — апсел далі веде через спільний premium-флоу (entitlement simulation).
  • Збережене / Вступлістисвідомо інша сутність (збережені каталоги), не зливається з «Моїми пріоритетами» на MVP.
  • Автентифікація — гостьовий список мігрує на акаунт при логіні/реєстрації (storeReturnUrl повертає на місце).
РепоШляхПримітка
abitly-frontend-v2src/components/features/priorities/PrioritiesDock.tsxroot-mounted: рейка/бар + немодальний aside / bottom-sheet; route-aware (прихований у /profile/*); Alt+P/ESC/клік-поза
abitly-frontend-v2…/priorities/{PrioritiesRail,PrioritiesAside,PrioritiesMobileSheet,PrioritiesPanel,PriorityRow}.tsxрейка · немодальний <aside> (без scrim/focus-trap) · мобільний sheet · панель із cascade-preview · рядок із drag/▲▼/видалити
abitly-frontend-v2…/priorities/usePrioritiesStore.tszustand+persist, localStorage abitly_priorities, MAX_TOTAL=10/MAX_BUDGET=5, дедуп + renumber
abitly-frontend-v2…/priorities/usePriorities.tsxфасад гість-локальний ↔ серверний; володіє аналітикою, aria-live, undo-тостом, just-added-пульсом
abitly-frontend-v2…/priorities/{derivePriorityStatus.ts,StatusChip.tsx,priorities.types.ts}клієнтський 4-стан статусу (vs прохідний-2025), типи (PrioritySource='calculator_chances')
abitly-frontend-v2…/priorities/{useDockHotkey.ts,usePrioritiesMigration.ts}Alt+P toggle (фізична клавіша) · replay гостьового списку на сервер при auth
abitly-frontend-v2…/priorities/AddToPrioritiesButton.tsx«+ у пріоритети» на ChanceCard (free, локально; data-priorities-keepalive тримає aside відкритим)
abitly-frontend-v2src/components/pages/calculator/components/registration-funnel/ChancesTeaserSection/**MVP-поверхня: тизер шансів, де живе кнопка додавання
abitly-frontend-v2src/lib/analytics/events/priorities.ts7 типізованих подій (окремо від simulation.ts)
  • Не запушено / не в проді. Уся фіча — на локальній гілці vladbandurin/priorities-dock (9 комітів попереду origin/main, гілки немає на remote). Статус 🚧; верифікація — локальний Playwright-smoke, не prod.
  • MVP — лише поверхня калькулятора. PrioritySource має єдине значення 'calculator_chances'; офер-картки пошуку (Фаза 2) і рейтингові списки (Фаза 3) закладено архітектурно, але не підключено.
  • Статус — клієнтська евристика на даних 2025, не вердикт 2026 (derivePriorityStatus.ts vs passingScore2025). Футер це чесно фреймить. Ризик довіри: гість, який повірив «на бюджет» і потім не вступив, — бренд-катастрофа в одношотовому сезоні.
  • Logged-in remove/reorder — лише локальні. usePriorities.remove має серверне видалення deferred (локальний стор ключований по offerId, а серверу потрібен application-id) → серверний список logged-in може розійтися з доком. Read-side серверна гідрація (hydrate) теж відкладена на пошуково-рейтингову фазу.
  • Міграція — best-effort клієнтський replay. Без серверного bulk-import; dropped рахується all-or-nothing; на серверних капах (5/10) можливі тихі часткові дропи. Можлива оптимізація — серверний POST /profile/simulation/import.
  • Події не опубліковані. GTM-workspace не запаблішено, CD у GA4 не зареєстровано → у GA4-звітах подій ще не видно. Немає impression-події (add-rate/rail→open неточні); priorities_save_prompt_shown зі спеки в коді відсутній.
  • Немодальний overlay не штовхає контент. Десктоп-aside — co-presence (як Claude-артефакт), а не Notion-push: накладається поверх правого краю, контент під ним не зсувається (свідоме рішення, але може перекривати правий стовпчик на вузьких лейаутах). TODO: координація z-index/offset із кабінетним BottomNav і тригером FilterDrawer на пошуку.
ДатаПодіяДжерело
2026-06-27дизайн-спека доку + план MVP калькулятора (free/paid межа, фасад, міграція, аналітика).context/priorities-dock/2026-06-27-priorities-dock-design.md + …-calculator-mvp-plan.md
2026-06-27JTBD + UX-патерни + sales-funnel аудит (entry points, leak-гіпотези, backlog).context/priorities-dock/2026-06-27-research-design-bundle.json
2026-06-27реалізація: персистентний sidebar + гостьовий стор + 7 подій + калькулятор-поверхнягілка vladbandurin/priorities-dock 85b457ca (локальна)
2026-06-27створено картку 🚧; статус за матрицею (гілка не запушена); локальний Playwright-smokeця картка