testimonials 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +26 -6
  4. data/app/controllers/testimonials/collection_controller.rb +20 -6
  5. data/app/views/layouts/testimonials/application.html.erb +2 -7
  6. data/app/views/layouts/testimonials/collection.html.erb +2 -1
  7. data/app/views/testimonials/collection/nps.html.erb +14 -0
  8. data/app/views/testimonials/nps_responses/index.html.erb +12 -1
  9. data/app/views/testimonials/testimonials/index.html.erb +11 -1
  10. data/config/locales/testimonials.ar.yml +5 -2
  11. data/config/locales/testimonials.bg.yml +5 -2
  12. data/config/locales/testimonials.bn.yml +5 -2
  13. data/config/locales/testimonials.de.yml +5 -2
  14. data/config/locales/testimonials.el.yml +5 -2
  15. data/config/locales/testimonials.en.yml +5 -2
  16. data/config/locales/testimonials.es.yml +5 -2
  17. data/config/locales/testimonials.fr.yml +5 -2
  18. data/config/locales/testimonials.hi.yml +5 -2
  19. data/config/locales/testimonials.hr.yml +5 -2
  20. data/config/locales/testimonials.id.yml +5 -2
  21. data/config/locales/testimonials.it.yml +5 -2
  22. data/config/locales/testimonials.ja.yml +5 -2
  23. data/config/locales/testimonials.ko.yml +5 -2
  24. data/config/locales/testimonials.lb.yml +5 -2
  25. data/config/locales/testimonials.nl.yml +5 -2
  26. data/config/locales/testimonials.pl.yml +5 -2
  27. data/config/locales/testimonials.pt.yml +5 -2
  28. data/config/locales/testimonials.ro.yml +5 -2
  29. data/config/locales/testimonials.ru.yml +5 -2
  30. data/config/locales/testimonials.th.yml +5 -2
  31. data/config/locales/testimonials.tr.yml +5 -2
  32. data/config/locales/testimonials.uk.yml +5 -2
  33. data/config/locales/testimonials.ur.yml +5 -2
  34. data/config/locales/testimonials.vi.yml +5 -2
  35. data/config/locales/testimonials.zh-CN.yml +5 -2
  36. data/config/routes.rb +1 -0
  37. data/lib/generators/testimonials/install/install_generator.rb +1 -0
  38. data/lib/tasks/testimonials_tasks.rake +11 -0
  39. data/lib/testimonials/dashboard.css +6 -2
  40. data/lib/testimonials/demo/README.md +7 -0
  41. data/lib/testimonials/demo/flower.mp4 +0 -0
  42. data/lib/testimonials/engine.rb +4 -0
  43. data/lib/testimonials/seeds.rb +164 -0
  44. data/lib/testimonials/version.rb +1 -1
  45. data/lib/testimonials/widget.js +28 -9
  46. data/lib/testimonials.rb +1 -0
  47. metadata +6 -1
@@ -50,6 +50,8 @@ nl:
50
50
  page:
51
51
  title: "Deel je ervaring met %{app}"
52
52
  subtitle: "Jouw woorden helpen anderen beslissen — bedankt!"
53
+ nps_title: "Help ons %{app} te verbeteren"
54
+ nps_subtitle: "Eén korte vraag — het kost tien seconden."
53
55
  kinds:
54
56
  text: "Tekst"
55
57
  video: "Video"
@@ -57,8 +59,6 @@ nl:
57
59
  widget: "In-app widget"
58
60
  page: "Verzamelpagina"
59
61
  nps: "NPS-opvolging"
60
- collection_page: "Vraag om testimonials"
61
- download_video: "Video downloaden"
62
62
  statuses:
63
63
  pending: "In afwachting"
64
64
  approved: "Goedgekeurd"
@@ -66,6 +66,9 @@ nl:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Om NPS-scores vragen"
70
+ collection_page: "Vraag om testimonials"
71
+ download_video: "Video downloaden"
69
72
  all_kinds: "Alle soorten"
70
73
  search: "Zoeken"
71
74
  search_placeholder: "Zoek tekst, naam, e-mail…"
@@ -50,6 +50,8 @@ pl:
50
50
  page:
51
51
  title: "Podziel się swoim doświadczeniem z %{app}"
52
52
  subtitle: "Twoje słowa pomagają innym podjąć decyzję — dziękujemy!"
53
+ nps_title: "Pomóż nam ulepszyć %{app}"
54
+ nps_subtitle: "Jedno krótkie pytanie — zajmie dziesięć sekund."
53
55
  kinds:
54
56
  text: "Tekst"
55
57
  video: "Wideo"
@@ -57,8 +59,6 @@ pl:
57
59
  widget: "Widżet w aplikacji"
58
60
  page: "Strona zbierania"
59
61
  nps: "Kontynuacja NPS"
60
- collection_page: "Poproś o opinie"
61
- download_video: "Pobierz wideo"
62
62
  statuses:
63
63
  pending: "Oczekujące"
64
64
  approved: "Zatwierdzone"
@@ -66,6 +66,9 @@ pl:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Poproś o oceny NPS"
70
+ collection_page: "Poproś o opinie"
71
+ download_video: "Pobierz wideo"
69
72
  all_kinds: "Wszystkie rodzaje"
70
73
  search: "Szukaj"
71
74
  search_placeholder: "Szukaj tekstu, imienia, e-maila…"
@@ -50,6 +50,8 @@ pt:
50
50
  page:
51
51
  title: "Compartilhe sua experiência com %{app}"
52
52
  subtitle: "Suas palavras ajudam outras pessoas a decidir — obrigado!"
53
+ nps_title: "Ajude-nos a melhorar o %{app}"
54
+ nps_subtitle: "Uma pergunta rápida — leva dez segundos."
53
55
  kinds:
54
56
  text: "Texto"
55
57
  video: "Vídeo"
@@ -57,8 +59,6 @@ pt:
57
59
  widget: "Widget no app"
58
60
  page: "Página de coleta"
59
61
  nps: "Acompanhamento NPS"
60
- collection_page: "Pedir depoimentos"
61
- download_video: "Baixar o vídeo"
62
62
  statuses:
63
63
  pending: "Pendente"
64
64
  approved: "Aprovado"
@@ -66,6 +66,9 @@ pt:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Pedir pontuações NPS"
70
+ collection_page: "Pedir depoimentos"
71
+ download_video: "Baixar o vídeo"
69
72
  all_kinds: "Todos os tipos"
70
73
  search: "Buscar"
71
74
  search_placeholder: "Buscar texto, nome, e-mail…"
@@ -50,6 +50,8 @@ ro:
50
50
  page:
51
51
  title: "Împărtășiți-vă experiența cu %{app}"
52
52
  subtitle: "Cuvintele dvs. îi ajută pe alții să decidă — mulțumim!"
53
+ nps_title: "Ajutați-ne să îmbunătățim %{app}"
54
+ nps_subtitle: "O întrebare rapidă — durează zece secunde."
53
55
  kinds:
54
56
  text: "Text"
55
57
  video: "Video"
@@ -57,8 +59,6 @@ ro:
57
59
  widget: "Widget în aplicație"
58
60
  page: "Pagină de colectare"
59
61
  nps: "Continuare NPS"
60
- collection_page: "Cere testimoniale"
61
- download_video: "Descarcă videoclipul"
62
62
  statuses:
63
63
  pending: "În așteptare"
64
64
  approved: "Aprobat"
@@ -66,6 +66,9 @@ ro:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Cere scoruri NPS"
70
+ collection_page: "Cere testimoniale"
71
+ download_video: "Descarcă videoclipul"
69
72
  all_kinds: "Toate tipurile"
70
73
  search: "Căutare"
71
74
  search_placeholder: "Căutați text, nume, email…"
@@ -50,6 +50,8 @@ ru:
50
50
  page:
51
51
  title: "Поделитесь своим опытом с %{app}"
52
52
  subtitle: "Ваши слова помогают другим решиться — спасибо!"
53
+ nps_title: "Помогите нам улучшить %{app}"
54
+ nps_subtitle: "Один короткий вопрос — займёт десять секунд."
53
55
  kinds:
54
56
  text: "Текст"
55
57
  video: "Видео"
@@ -57,8 +59,6 @@ ru:
57
59
  widget: "Виджет в приложении"
58
60
  page: "Страница сбора"
59
61
  nps: "После NPS"
60
- collection_page: "Попросить отзывы"
61
- download_video: "Скачать видео"
62
62
  statuses:
63
63
  pending: "Ожидает"
64
64
  approved: "Одобрено"
@@ -66,6 +66,9 @@ ru:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Запросить оценки NPS"
70
+ collection_page: "Попросить отзывы"
71
+ download_video: "Скачать видео"
69
72
  all_kinds: "Все типы"
70
73
  search: "Поиск"
71
74
  search_placeholder: "Искать текст, имя, почту…"
@@ -50,6 +50,8 @@ th:
50
50
  page:
51
51
  title: "แบ่งปันประสบการณ์ของคุณกับ %{app}"
52
52
  subtitle: "คำพูดของคุณช่วยให้คนอื่นตัดสินใจได้ — ขอบคุณ!"
53
+ nps_title: "ช่วยเราปรับปรุง %{app}"
54
+ nps_subtitle: "คำถามเดียวสั้น ๆ — ใช้เวลาสิบวินาที"
53
55
  kinds:
54
56
  text: "ข้อความ"
55
57
  video: "วิดีโอ"
@@ -57,8 +59,6 @@ th:
57
59
  widget: "วิดเจ็ตในแอป"
58
60
  page: "หน้ารวบรวม"
59
61
  nps: "ติดตามผล NPS"
60
- collection_page: "ขอคำรับรอง"
61
- download_video: "ดาวน์โหลดวิดีโอ"
62
62
  statuses:
63
63
  pending: "รอดำเนินการ"
64
64
  approved: "อนุมัติแล้ว"
@@ -66,6 +66,9 @@ th:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "ขอคะแนน NPS"
70
+ collection_page: "ขอคำรับรอง"
71
+ download_video: "ดาวน์โหลดวิดีโอ"
69
72
  all_kinds: "ทุกประเภท"
70
73
  search: "ค้นหา"
71
74
  search_placeholder: "ค้นหาข้อความ ชื่อ อีเมล…"
@@ -50,6 +50,8 @@ tr:
50
50
  page:
51
51
  title: "%{app} deneyiminizi paylaşın"
52
52
  subtitle: "Sözleriniz başkalarının karar vermesine yardımcı olur — teşekkürler!"
53
+ nps_title: "%{app} deneyimini iyileştirmemize yardım edin"
54
+ nps_subtitle: "Tek bir kısa soru — on saniye sürer."
53
55
  kinds:
54
56
  text: "Metin"
55
57
  video: "Video"
@@ -57,8 +59,6 @@ tr:
57
59
  widget: "Uygulama içi bileşen"
58
60
  page: "Toplama sayfası"
59
61
  nps: "NPS takibi"
60
- collection_page: "Görüş isteyin"
61
- download_video: "Videoyu indir"
62
62
  statuses:
63
63
  pending: "Beklemede"
64
64
  approved: "Onaylandı"
@@ -66,6 +66,9 @@ tr:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "NPS puanı isteyin"
70
+ collection_page: "Görüş isteyin"
71
+ download_video: "Videoyu indir"
69
72
  all_kinds: "Tüm türler"
70
73
  search: "Ara"
71
74
  search_placeholder: "Metin, ad, e-posta ara…"
@@ -50,6 +50,8 @@ uk:
50
50
  page:
51
51
  title: "Поділіться своїм досвідом із %{app}"
52
52
  subtitle: "Ваші слова допомагають іншим зважитися — дякуємо!"
53
+ nps_title: "Допоможіть нам покращити %{app}"
54
+ nps_subtitle: "Одне коротке питання — займе десять секунд."
53
55
  kinds:
54
56
  text: "Текст"
55
57
  video: "Відео"
@@ -57,8 +59,6 @@ uk:
57
59
  widget: "Віджет у застосунку"
58
60
  page: "Сторінка збору"
59
61
  nps: "Після NPS"
60
- collection_page: "Попросити відгуки"
61
- download_video: "Завантажити відео"
62
62
  statuses:
63
63
  pending: "Очікує"
64
64
  approved: "Схвалено"
@@ -66,6 +66,9 @@ uk:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Запитати оцінки NPS"
70
+ collection_page: "Попросити відгуки"
71
+ download_video: "Завантажити відео"
69
72
  all_kinds: "Усі типи"
70
73
  search: "Пошук"
71
74
  search_placeholder: "Шукати текст, ім'я, пошту…"
@@ -50,6 +50,8 @@ ur:
50
50
  page:
51
51
  title: "%{app} کے ساتھ اپنا تجربہ شیئر کریں"
52
52
  subtitle: "آپ کے الفاظ دوسروں کو فیصلہ کرنے میں مدد دیتے ہیں — شکریہ!"
53
+ nps_title: "%{app} کو بہتر بنانے میں ہماری مدد کریں"
54
+ nps_subtitle: "ایک مختصر سوال — دس سیکنڈ لگیں گے۔"
53
55
  kinds:
54
56
  text: "متن"
55
57
  video: "ویڈیو"
@@ -57,8 +59,6 @@ ur:
57
59
  widget: "ایپ کے اندر ویجیٹ"
58
60
  page: "جمع کرنے کا صفحہ"
59
61
  nps: "NPS فالو اپ"
60
- collection_page: "تعریفی کلمات طلب کریں"
61
- download_video: "ویڈیو ڈاؤن لوڈ کریں"
62
62
  statuses:
63
63
  pending: "زیر التوا"
64
64
  approved: "منظور شدہ"
@@ -66,6 +66,9 @@ ur:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "NPS اسکور کی درخواست کریں"
70
+ collection_page: "تعریفی کلمات طلب کریں"
71
+ download_video: "ویڈیو ڈاؤن لوڈ کریں"
69
72
  all_kinds: "تمام اقسام"
70
73
  search: "تلاش"
71
74
  search_placeholder: "متن، نام، ای میل تلاش کریں…"
@@ -50,6 +50,8 @@ vi:
50
50
  page:
51
51
  title: "Chia sẻ trải nghiệm của bạn với %{app}"
52
52
  subtitle: "Lời của bạn giúp người khác quyết định — cảm ơn bạn!"
53
+ nps_title: "Giúp chúng tôi cải thiện %{app}"
54
+ nps_subtitle: "Một câu hỏi nhanh — chỉ mất mười giây."
53
55
  kinds:
54
56
  text: "Văn bản"
55
57
  video: "Video"
@@ -57,8 +59,6 @@ vi:
57
59
  widget: "Widget trong ứng dụng"
58
60
  page: "Trang thu thập"
59
61
  nps: "Tiếp nối NPS"
60
- collection_page: "Xin cảm nhận"
61
- download_video: "Tải video xuống"
62
62
  statuses:
63
63
  pending: "Đang chờ"
64
64
  approved: "Đã duyệt"
@@ -66,6 +66,9 @@ vi:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "Yêu cầu điểm NPS"
70
+ collection_page: "Xin cảm nhận"
71
+ download_video: "Tải video xuống"
69
72
  all_kinds: "Tất cả các loại"
70
73
  search: "Tìm kiếm"
71
74
  search_placeholder: "Tìm văn bản, tên, email…"
@@ -50,6 +50,8 @@ zh-CN:
50
50
  page:
51
51
  title: "分享您使用 %{app} 的体验"
52
52
  subtitle: "您的评价能帮助他人做出选择——谢谢!"
53
+ nps_title: "帮助我们改进 %{app}"
54
+ nps_subtitle: "只有一个问题 — 十秒即可完成。"
53
55
  kinds:
54
56
  text: "文字"
55
57
  video: "视频"
@@ -57,8 +59,6 @@ zh-CN:
57
59
  widget: "应用内组件"
58
60
  page: "收集页面"
59
61
  nps: "NPS 跟进"
60
- collection_page: "征集用户评价"
61
- download_video: "下载视频"
62
62
  statuses:
63
63
  pending: "待处理"
64
64
  approved: "已批准"
@@ -66,6 +66,9 @@ zh-CN:
66
66
  dashboard:
67
67
  title: "Testimonials"
68
68
  nps: "NPS"
69
+ nps_page: "邀请填写 NPS"
70
+ collection_page: "征集用户评价"
71
+ download_video: "下载视频"
69
72
  all_kinds: "所有类型"
70
73
  search: "搜索"
71
74
  search_placeholder: "搜索文字、姓名、邮箱…"
data/config/routes.rb CHANGED
@@ -7,6 +7,7 @@ Testimonials::Engine.routes.draw do
7
7
  get 'dashboard.js', to: 'widgets#dashboard', as: :dashboard_script
8
8
  get 'dashboard.css', to: 'widgets#dashboard_stylesheet', as: :dashboard_stylesheet
9
9
  get 'new', to: 'collection#show', as: :collection
10
+ get 'nps/new', to: 'collection#nps', as: :nps_collection
10
11
  resources :events, only: :create
11
12
  resource :nps, only: :create, controller: 'nps'
12
13
  resources :nps_responses, only: %i[index show]
@@ -29,6 +29,7 @@ module Testimonials
29
29
  say "\ntestimonials installed. Run `rails db:migrate`, then add", :green
30
30
  say '`<%= testimonials_tag %>` before </body> in your layout.'
31
31
  say 'Triage testimonials at /testimonials (development only until you set config.authorize_admin).'
32
+ say 'Optional: run `bin/rails testimonials:seed_demo` for sample testimonials and NPS.'
32
33
  say "Collect from outside the app via /testimonials/new.\n"
33
34
  end
34
35
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :testimonials do
4
+ desc 'Create or refresh testimonials demo data'
5
+ task seed_demo: :environment do
6
+ result = Testimonials::Seeds.load!
7
+ puts "Seeded #{result[:testimonials].size} testimonials, " \
8
+ "#{result[:nps_responses].size} NPS responses, and " \
9
+ "#{result[:prompt_events].size} prompt events."
10
+ end
11
+ end
@@ -20,6 +20,10 @@ a:hover { text-decoration: underline; }
20
20
  .container { max-width: 1020px; margin: 0 auto; padding: 24px 16px 64px; }
21
21
  h1 { font-size: 22px; margin: 0 0 16px; }
22
22
  h1.title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
23
+ /* Index heading row: title on the left, the shareable public link on the right. */
24
+ .page-head { display: flex; align-items: center; justify-content: space-between;
25
+ gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
26
+ .page-head h1 { margin: 0; }
23
27
  .nav { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
24
28
  .nav a { padding: 6px 16px; border: 1px solid var(--border); border-radius: 999px;
25
29
  background: var(--surface); color: var(--muted); font-weight: 600; }
@@ -99,9 +103,9 @@ img.avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }
99
103
  .tm-index, .tm-nps-index { overflow: hidden; }
100
104
  .tm-index .container, .tm-nps-index .container { max-width: 1280px; display: flex; flex-direction: column; padding-bottom: 16px; }
101
105
  .tm-index .nav, .tm-nps-index .nav,
102
- .tm-index .container > h1, .tm-nps-index .container > h1,
106
+ .tm-index .container > .page-head, .tm-nps-index .container > .page-head,
103
107
  .tm-nps-index .stat-row { flex: 0 0 auto; }
104
- .tm-index .container > h1, .tm-nps-index .container > h1 { margin-bottom: 14px; }
108
+ .tm-index .container > .page-head, .tm-nps-index .container > .page-head { margin-bottom: 14px; }
105
109
  .tm-nps-index .stat-row { margin-bottom: 14px; }
106
110
  .dashboard-shell { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(330px, 430px) 1fr;
107
111
  gap: 16px; }
@@ -0,0 +1,7 @@
1
+ # Demo Media
2
+
3
+ `flower.mp4` is bundled as a small real MP4 fixture for
4
+ `bin/rails testimonials:seed_demo`.
5
+
6
+ Source: Mozilla Developer Network interactive examples, `cc0-videos`.
7
+ The file is distributed as CC0 demo media.
Binary file
@@ -4,6 +4,10 @@ module Testimonials
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Testimonials
6
6
 
7
+ rake_tasks do
8
+ load File.expand_path('../tasks/testimonials_tasks.rake', __dir__)
9
+ end
10
+
7
11
  initializer 'testimonials.helpers' do
8
12
  ActiveSupport.on_load(:action_view) do
9
13
  include Testimonials::WidgetHelper
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Testimonials
4
+ module Seeds
5
+ VIDEO_PATH = File.expand_path('demo/flower.mp4', __dir__)
6
+
7
+ TESTIMONIALS = [
8
+ {
9
+ seed_id: 'approved-founder',
10
+ kind: 'text',
11
+ body: 'We added the widget after onboarding and collected better customer quotes in the first week.',
12
+ best_line: 'Collected better customer quotes in the first week.',
13
+ rating: 5,
14
+ status: 'approved',
15
+ featured: true,
16
+ consent_given: true,
17
+ consent_text: 'You can use my testimonial publicly in your marketing and sales.',
18
+ name: 'Maya Chen',
19
+ email: 'maya@example.com',
20
+ title_company: 'Founder, DemoCRM',
21
+ source: 'widget',
22
+ page_url: '/onboarding/complete',
23
+ locale: 'en'
24
+ },
25
+ {
26
+ seed_id: 'pending-operator',
27
+ kind: 'text',
28
+ body: 'The NPS prompt felt lightweight and did not interrupt the workflow.',
29
+ rating: 4,
30
+ status: 'pending',
31
+ featured: false,
32
+ consent_given: true,
33
+ consent_text: 'You can use my testimonial publicly in your marketing and sales.',
34
+ name: 'Jon Bell',
35
+ email: 'jon@example.com',
36
+ title_company: 'Operations Lead, ExampleCo',
37
+ source: 'nps',
38
+ page_url: '/reports',
39
+ locale: 'en'
40
+ },
41
+ {
42
+ seed_id: 'private-archive',
43
+ kind: 'text',
44
+ body: 'Useful for our internal rollout notes, but please do not publish my name.',
45
+ rating: 3,
46
+ status: 'archived',
47
+ featured: false,
48
+ consent_given: false,
49
+ consent_text: 'You can only use my testimonial privately in your marketing and sales.',
50
+ name: 'Priya Rao',
51
+ email: 'priya@example.com',
52
+ title_company: 'Customer Success, SampleOps',
53
+ source: 'page',
54
+ page_url: '/testimonials/new',
55
+ locale: 'en'
56
+ },
57
+ {
58
+ seed_id: 'approved-video',
59
+ kind: 'video',
60
+ body: 'Short demo video testimonial with a real MP4 attachment.',
61
+ best_line: 'A real video testimonial is ready to review.',
62
+ rating: 5,
63
+ status: 'approved',
64
+ featured: true,
65
+ consent_given: true,
66
+ consent_text: 'You can use my testimonial publicly in your marketing and sales.',
67
+ name: 'Alex Rivera',
68
+ email: 'alex@example.com',
69
+ title_company: 'Head of Product, VideoDemo',
70
+ source: 'page',
71
+ page_url: '/testimonials/new',
72
+ locale: 'en'
73
+ }
74
+ ].freeze
75
+
76
+ NPS_RESPONSES = [
77
+ { seed_id: 'promoter', score: 10,
78
+ comment: 'Fast to install and easy to trust because the data stays in our app.' },
79
+ { seed_id: 'passive', score: 8, comment: 'The flow is useful, but I would like more display examples.' },
80
+ { seed_id: 'detractor', score: 4, comment: 'I was not sure when the prompt would appear again.' }
81
+ ].freeze
82
+
83
+ PROMPT_EVENTS = [
84
+ { kind: 'testimonial', action: 'shown', visitor_token: 'testimonials-demo-visitor' },
85
+ { kind: 'testimonial', action: 'dismissed', visitor_token: 'testimonials-demo-visitor' },
86
+ { kind: 'nps', action: 'submitted', author_id: 'testimonials-demo:nps-promoter' }
87
+ ].freeze
88
+
89
+ def self.load!(tenant: nil)
90
+ {
91
+ testimonials: load_testimonials!(tenant: tenant),
92
+ nps_responses: load_nps_responses!(tenant: tenant),
93
+ prompt_events: load_prompt_events!(tenant: tenant)
94
+ }
95
+ end
96
+
97
+ def self.load_testimonials!(tenant:)
98
+ TESTIMONIALS.map do |attributes|
99
+ seed_id = attributes.fetch(:seed_id)
100
+ testimonial = Testimonials::Testimonial.find_or_initialize_by(
101
+ author_id: "testimonials-demo:#{seed_id}",
102
+ tenant: tenant.presence
103
+ )
104
+ testimonial.assign_attributes(
105
+ attributes.except(:seed_id).merge(tenant: tenant.presence, user_agent: 'testimonials demo seed')
106
+ )
107
+ testimonial.save!
108
+ attach_demo_video!(testimonial) if seed_id == 'approved-video'
109
+ testimonial
110
+ end
111
+ end
112
+ private_class_method :load_testimonials!
113
+
114
+ def self.load_nps_responses!(tenant:)
115
+ NPS_RESPONSES.map do |attributes|
116
+ seed_id = attributes.fetch(:seed_id)
117
+ response = Testimonials::NpsResponse.find_or_initialize_by(
118
+ author_id: "testimonials-demo:nps-#{seed_id}",
119
+ tenant: tenant.presence
120
+ )
121
+ response.assign_attributes(
122
+ attributes.except(:seed_id).merge(
123
+ name: 'Demo Respondent',
124
+ email: "#{seed_id}@example.com",
125
+ page_url: '/dashboard',
126
+ user_agent: 'testimonials demo seed',
127
+ locale: 'en',
128
+ tenant: tenant.presence
129
+ )
130
+ )
131
+ response.save!
132
+ response
133
+ end
134
+ end
135
+ private_class_method :load_nps_responses!
136
+
137
+ def self.load_prompt_events!(tenant:)
138
+ PROMPT_EVENTS.map do |attributes|
139
+ event = Testimonials::PromptEvent.find_or_initialize_by(
140
+ attributes.slice(:kind, :action, :author_id, :visitor_token).merge(tenant: tenant.presence)
141
+ )
142
+ event.save!
143
+ event
144
+ end
145
+ end
146
+ private_class_method :load_prompt_events!
147
+
148
+ def self.attach_demo_video!(testimonial)
149
+ return unless Testimonials.config.video_enabled?
150
+ return unless testimonial.respond_to?(:video_file)
151
+ return if testimonial.video_file.attached?
152
+ return unless File.exist?(VIDEO_PATH)
153
+
154
+ File.open(VIDEO_PATH, 'rb') do |file|
155
+ testimonial.video_file.attach(
156
+ io: file,
157
+ filename: 'testimonials-demo-flower.mp4',
158
+ content_type: 'video/mp4'
159
+ )
160
+ end
161
+ end
162
+ private_class_method :attach_demo_video!
163
+ end
164
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Testimonials
4
- VERSION = '0.7.5'
4
+ VERSION = '0.7.6'
5
5
  end
@@ -16,8 +16,9 @@
16
16
  * form right after. Auto-opens report shown/dismissed to the throttle
17
17
  * ledger; explicit opens don't.
18
18
  *
19
- * On the public collection page the same code renders the form inline into
20
- * <div data-testimonials-inline> (config.mode === "page").
19
+ * On the public standalone pages the same code renders inline into
20
+ * <div data-testimonials-inline>: the testimonial form (config.mode ===
21
+ * "page") or the NPS card (config.mode === "nps_page").
21
22
  */
22
23
  (function () {
23
24
  "use strict";
@@ -73,7 +74,7 @@
73
74
  // lock lives on documentElement, which survives the swap, so release it.
74
75
  if (!document.getElementById("tml-overlay")) unlockScroll();
75
76
 
76
- if (config.mode === "page") {
77
+ if (pageMode()) {
77
78
  renderInline();
78
79
  return;
79
80
  }
@@ -274,7 +275,13 @@
274
275
  }
275
276
  }
276
277
 
277
- // --- inline (public collection page) ----------------------------------------
278
+ // --- inline (the standalone public pages) -----------------------------------
279
+
280
+ // A page IS the prompt: the visitor followed a link here, so there is no
281
+ // overlay and nothing to dismiss — "Not now", Cancel and the × are omitted.
282
+ function pageMode() {
283
+ return config.mode === "page" || config.mode === "nps_page";
284
+ }
278
285
 
279
286
  function renderInline() {
280
287
  var host = document.querySelector("[data-testimonials-inline]");
@@ -283,7 +290,14 @@
283
290
  card.className = "tml-inline";
284
291
  if (config.rtl) card.setAttribute("dir", "rtl");
285
292
  host.appendChild(card);
286
- state = { kind: "testimonial", auto: false, rating: 0, submitted: false, videoBlob: null, source: "page" };
293
+
294
+ var nps = config.mode === "nps_page" && config.nps.enabled;
295
+ state = { kind: nps ? "nps" : "testimonial", auto: false, rating: 0,
296
+ submitted: false, videoBlob: null, source: "page" };
297
+ if (nps) {
298
+ card.appendChild(npsStage());
299
+ return;
300
+ }
287
301
  if (config.existing) prefillExisting();
288
302
  card.appendChild(formStage(config.existing ? config.labels.updateTitle : config.labels.shareTitle));
289
303
  }
@@ -388,7 +402,7 @@
388
402
  form.appendChild(error);
389
403
 
390
404
  var actions = el("div", "tml-actions");
391
- actions.appendChild(secondaryButton(config.labels.cancel, close));
405
+ if (overlay) actions.appendChild(secondaryButton(config.labels.cancel, close));
392
406
  var submit = el("button", "tml-primary", config.labels.submit);
393
407
  submit.type = "submit";
394
408
  actions.appendChild(submit);
@@ -417,7 +431,7 @@
417
431
  title.autocomplete = "organization-title";
418
432
  form.appendChild(field(config.labels.titleCompany + " (" + config.labels.optional + ")", title));
419
433
 
420
- if (config.mode === "page" && config.avatars.enabled) {
434
+ if (pageMode() && config.avatars.enabled) {
421
435
  var avatar = document.createElement("input");
422
436
  avatar.type = "file";
423
437
  avatar.name = "avatar";
@@ -873,7 +887,7 @@
873
887
  legend.appendChild(el("span", null, config.labels.npsHigh));
874
888
  stage.appendChild(legend);
875
889
 
876
- stage.appendChild(notNowButton());
890
+ if (overlay) stage.appendChild(notNowButton());
877
891
  return stage;
878
892
  }
879
893
 
@@ -920,7 +934,12 @@
920
934
  form.appendChild(error);
921
935
 
922
936
  var actions = el("div", "tml-actions");
923
- actions.appendChild(secondaryButton(config.labels.cancel, close));
937
+ // In the widget, Cancel closes. On the page there is nothing to close, so
938
+ // it discards this score and goes back to the scale — a mis-tapped number
939
+ // must not be a dead end.
940
+ actions.appendChild(secondaryButton(config.labels.cancel, overlay ? close : function () {
941
+ showStage(npsStage());
942
+ }));
924
943
  var submit = el("button", "tml-primary", config.labels.submit);
925
944
  submit.type = "submit";
926
945
  actions.appendChild(submit);
data/lib/testimonials.rb CHANGED
@@ -5,6 +5,7 @@ require 'testimonials/configuration'
5
5
  require 'testimonials/widget'
6
6
  require 'testimonials/prompt_helper'
7
7
  require 'testimonials/has_testimonials'
8
+ require 'testimonials/seeds'
8
9
  require 'testimonials/engine'
9
10
 
10
11
  # Testimonials, reviews and NPS for Rails. An iOS-style in-app widget collects