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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +26 -6
- data/app/controllers/testimonials/collection_controller.rb +20 -6
- data/app/views/layouts/testimonials/application.html.erb +2 -7
- data/app/views/layouts/testimonials/collection.html.erb +2 -1
- data/app/views/testimonials/collection/nps.html.erb +14 -0
- data/app/views/testimonials/nps_responses/index.html.erb +12 -1
- data/app/views/testimonials/testimonials/index.html.erb +11 -1
- data/config/locales/testimonials.ar.yml +5 -2
- data/config/locales/testimonials.bg.yml +5 -2
- data/config/locales/testimonials.bn.yml +5 -2
- data/config/locales/testimonials.de.yml +5 -2
- data/config/locales/testimonials.el.yml +5 -2
- data/config/locales/testimonials.en.yml +5 -2
- data/config/locales/testimonials.es.yml +5 -2
- data/config/locales/testimonials.fr.yml +5 -2
- data/config/locales/testimonials.hi.yml +5 -2
- data/config/locales/testimonials.hr.yml +5 -2
- data/config/locales/testimonials.id.yml +5 -2
- data/config/locales/testimonials.it.yml +5 -2
- data/config/locales/testimonials.ja.yml +5 -2
- data/config/locales/testimonials.ko.yml +5 -2
- data/config/locales/testimonials.lb.yml +5 -2
- data/config/locales/testimonials.nl.yml +5 -2
- data/config/locales/testimonials.pl.yml +5 -2
- data/config/locales/testimonials.pt.yml +5 -2
- data/config/locales/testimonials.ro.yml +5 -2
- data/config/locales/testimonials.ru.yml +5 -2
- data/config/locales/testimonials.th.yml +5 -2
- data/config/locales/testimonials.tr.yml +5 -2
- data/config/locales/testimonials.uk.yml +5 -2
- data/config/locales/testimonials.ur.yml +5 -2
- data/config/locales/testimonials.vi.yml +5 -2
- data/config/locales/testimonials.zh-CN.yml +5 -2
- data/config/routes.rb +1 -0
- data/lib/generators/testimonials/install/install_generator.rb +1 -0
- data/lib/tasks/testimonials_tasks.rake +11 -0
- data/lib/testimonials/dashboard.css +6 -2
- data/lib/testimonials/demo/README.md +7 -0
- data/lib/testimonials/demo/flower.mp4 +0 -0
- data/lib/testimonials/engine.rb +4 -0
- data/lib/testimonials/seeds.rb +164 -0
- data/lib/testimonials/version.rb +1 -1
- data/lib/testimonials/widget.js +28 -9
- data/lib/testimonials.rb +1 -0
- 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 >
|
|
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 >
|
|
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; }
|
|
Binary file
|
data/lib/testimonials/engine.rb
CHANGED
|
@@ -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
|
data/lib/testimonials/version.rb
CHANGED
data/lib/testimonials/widget.js
CHANGED
|
@@ -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
|
|
20
|
-
* <div data-testimonials-inline
|
|
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 (
|
|
77
|
+
if (pageMode()) {
|
|
77
78
|
renderInline();
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
@@ -274,7 +275,13 @@
|
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
|
|
277
|
-
// --- inline (public
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|