mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
welcome tour + save completed tours in database
This commit is contained in:
parent
11a2dde776
commit
5b46edd748
@ -329,10 +329,10 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreive once the notifications from the server and display a message popup for each new one.
|
||||
* Retrieve once the notifications from the server and display a message popup for each new one.
|
||||
* Then, periodically check for new notifications.
|
||||
*/
|
||||
var getNotifications = function () {
|
||||
const getNotifications = function () {
|
||||
$rootScope.toCheckNotifications = true;
|
||||
if (!$rootScope.checkNotificationsIsInit && !!$rootScope.currentUser) {
|
||||
setTimeout(function () {
|
||||
@ -373,7 +373,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
/**
|
||||
* Open the modal window allowing the user to log in.
|
||||
*/
|
||||
var openLoginModal = function (toState, toParams, callback) {
|
||||
const openLoginModal = function (toState, toParams, callback) {
|
||||
<% active_provider = AuthProvider.active %>
|
||||
<% if active_provider.providable_type != DatabaseProvider.name %>
|
||||
$window.location.href = '/sso-redirect';
|
||||
@ -480,7 +480,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
* When the status changes, the callback is triggered with the new status as parameter
|
||||
* Inspired by http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active#answer-1060034
|
||||
*/
|
||||
var onPageVisible = function (callback) {
|
||||
const onPageVisible = function (callback) {
|
||||
let hidden = 'hidden';
|
||||
|
||||
const onchange = function (evt) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', 'homeContentPromise', 'uiTourService', '_t',
|
||||
function ($scope, $stateParams, homeContentPromise, uiTourService, _t) {
|
||||
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', 'homeContentPromise', 'Member', 'uiTourService', '_t',
|
||||
function ($scope, $stateParams, homeContentPromise, Member, uiTourService, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// Home page HTML content
|
||||
@ -22,8 +22,48 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
|
||||
// We set the home page content, with the directives replacing the placeholders
|
||||
$scope.homeContent = insertDirectives(homeContentPromise.setting.value);
|
||||
|
||||
// setup the tour
|
||||
// setup the tour for admins
|
||||
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
|
||||
setupWelcomeTour();
|
||||
}
|
||||
};
|
||||
|
||||
const insertDirectives = function (html) {
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = html.trim();
|
||||
|
||||
node.querySelectorAll('div#news').forEach((newsNode) => {
|
||||
const news = document.createElement('news');
|
||||
newsNode.parentNode.replaceChild(news, newsNode);
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#projects').forEach((projectsNode) => {
|
||||
const projects = document.createElement('projects');
|
||||
projectsNode.parentNode.replaceChild(projects, projectsNode);
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#twitter').forEach((twitterNode) => {
|
||||
const twitter = document.createElement('twitter');
|
||||
twitterNode.parentNode.replaceChild(twitter, twitterNode);
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#members').forEach((membersNode) => {
|
||||
const members = document.createElement('members');
|
||||
membersNode.parentNode.replaceChild(members, membersNode);
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#events').forEach((eventsNode) => {
|
||||
const events = document.createElement('events');
|
||||
eventsNode.parentNode.replaceChild(events, eventsNode);
|
||||
});
|
||||
|
||||
return node.outerHTML;
|
||||
};
|
||||
|
||||
const setupWelcomeTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTour();
|
||||
// add the steps
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
@ -97,10 +137,18 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
|
||||
content: _t('app.public.tour.plans.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary .admin-section',
|
||||
stepId: 'admin',
|
||||
order: 9,
|
||||
title: _t('app.public.tour.admin.title'),
|
||||
content: _t('app.public.tour.admin.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.about-page-link',
|
||||
stepId: 'about',
|
||||
order: 9,
|
||||
order: 10,
|
||||
title: _t('app.public.tour.about.title'),
|
||||
content: _t('app.public.tour.about.content'),
|
||||
placement: 'bottom'
|
||||
@ -108,7 +156,7 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.notification-center-link',
|
||||
stepId: 'notifications',
|
||||
order: 10,
|
||||
order: 11,
|
||||
title: _t('app.public.tour.notifications.title'),
|
||||
content: _t('app.public.tour.notifications.content'),
|
||||
placement: 'bottom'
|
||||
@ -116,44 +164,47 @@ Application.Controllers.controller('HomeController', ['$scope', '$stateParams',
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.user-menu-dropdown',
|
||||
stepId: 'profile',
|
||||
order: 11,
|
||||
order: 12,
|
||||
title: _t('app.public.tour.profile.title'),
|
||||
content: _t('app.public.tour.profile.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.start();
|
||||
};
|
||||
|
||||
const insertDirectives = function (html) {
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = html.trim();
|
||||
|
||||
node.querySelectorAll('div#news').forEach((newsNode) => {
|
||||
const news = document.createElement('news');
|
||||
newsNode.parentNode.replaceChild(news, newsNode);
|
||||
uitour.createStep({
|
||||
selector: '.app-generator .app-version',
|
||||
stepId: 'version',
|
||||
order: 13,
|
||||
title: _t('app.public.tour.version.title'),
|
||||
content: _t('app.public.tour.version.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#projects').forEach((projectsNode) => {
|
||||
const projects = document.createElement('projects');
|
||||
projectsNode.parentNode.replaceChild(projects, projectsNode);
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 14,
|
||||
title: _t('app.public.tour.conclusion.title'),
|
||||
content: _t('app.public.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#twitter').forEach((twitterNode) => {
|
||||
const twitter = document.createElement('twitter');
|
||||
twitterNode.parentNode.replaceChild(twitter, twitterNode);
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('welcome') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'welcome' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#members').forEach((membersNode) => {
|
||||
const members = document.createElement('members');
|
||||
membersNode.parentNode.replaceChild(members, membersNode);
|
||||
// if the user has never seen the tour, show him now
|
||||
if ($scope.currentUser.profile.tours.indexOf('welcome') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
uitour.start();
|
||||
}
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#events').forEach((eventsNode) => {
|
||||
const events = document.createElement('events');
|
||||
eventsNode.parentNode.replaceChild(events, eventsNode);
|
||||
});
|
||||
|
||||
return node.outerHTML;
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
|
@ -30,6 +30,11 @@ Application.Services.factory('Member', ['$resource', function ($resource) {
|
||||
mapping: {
|
||||
method: 'GET',
|
||||
url: '/api/members/mapping'
|
||||
},
|
||||
completeTour: {
|
||||
method: 'PATCH',
|
||||
url: '/api/members/:id/complete_tour',
|
||||
params: { id: '@id' }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -72,7 +72,7 @@
|
||||
|
||||
<!-- Admin entries -->
|
||||
<div class="line-s bg-red-dark dk " ng-if="isAuthorized('admin')"></div>
|
||||
<div class="text-xs font-bold text-bordeau hidden-nav-xs padder m-t-lg m-b-sm" ng-if="isAuthorized('admin')" translate>{{ 'app.public.common.admin' }}</div>
|
||||
<div class="text-xs font-bold text-bordeau hidden-nav-xs padder m-t-lg m-b-sm admin-section" ng-if="isAuthorized('admin')" translate>{{ 'app.public.common.admin' }}</div>
|
||||
<ul class="nav" ng-if="isAuthorized('admin')">
|
||||
<li class="" ng-repeat="navLink in adminNavLinks">
|
||||
<a ng-click="toggleNavSize($event)" ga ui-sref="{{navLink.state}}" ui-sref-active="active" class="auto" data-toggle="class:nav-off-screen" data-target="#nav">
|
||||
|
@ -4,7 +4,6 @@
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-if="tourStep.isPrev()" ng-click="tour.prev()" translate>{{ 'app.public.tour.previous' }}</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-if="tourStep.isNext()" ng-click="tour.next()" translate>{{ 'app.public.tour.next' }}</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="tour.pause()" translate>{{ 'app.public.tour.pause' }}</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="tour.end()" translate>{{ 'app.public.tour.end' }}</button>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
# API Controller for resources of type User with role 'member'
|
||||
class API::MembersController < API::ApiController
|
||||
before_action :authenticate_user!, except: [:last_subscribed]
|
||||
before_action :set_member, only: %i[update destroy merge]
|
||||
before_action :set_member, only: %i[update destroy merge complete_tour]
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@ -189,6 +189,15 @@ class API::MembersController < API::ApiController
|
||||
@members = User.includes(:profile)
|
||||
end
|
||||
|
||||
def complete_tour
|
||||
authorize @member
|
||||
|
||||
tours = "#{@member.profile.tours} #{params[:tour]}"
|
||||
@member.profile.update_attributes(tours: tours.strip)
|
||||
|
||||
render json: { tours: @member.profile.tours }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_member
|
||||
|
@ -31,6 +31,10 @@ class UserPolicy < ApplicationPolicy
|
||||
user.id == record.id
|
||||
end
|
||||
|
||||
def complete_tour?
|
||||
user.id == record.id
|
||||
end
|
||||
|
||||
%w[list create mapping].each do |action|
|
||||
define_method "#{action}?" do
|
||||
user.admin?
|
||||
|
@ -21,6 +21,7 @@ json.profile do
|
||||
json.website member.profile.website
|
||||
json.job member.profile.job
|
||||
json.extract! member.profile, :facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr
|
||||
json.tours member.profile.tours&.split || []
|
||||
end
|
||||
|
||||
json.invoicing_profile do
|
||||
|
@ -89,7 +89,14 @@
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body ng-controller="ApplicationController" ng-init="setCurrentUser(<%= current_user ? current_user.to_json : 'null' %>)" ng-cloak ui-tour ui-tour-backdrop="true" ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'" ui-tour-scroll-offset="0">
|
||||
<body ng-controller="ApplicationController"
|
||||
ng-init="setCurrentUser(<%= current_user ? current_user.to_json : 'null' %>)"
|
||||
ng-cloak
|
||||
ui-tour
|
||||
ui-tour-backdrop="true"
|
||||
ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'"
|
||||
ui-tour-use-hotkeys="true"
|
||||
>
|
||||
<div growl></div>
|
||||
|
||||
<%= flash_messages %>
|
||||
|
@ -381,7 +381,6 @@ fr:
|
||||
tour:
|
||||
previous: "Précédent"
|
||||
next: "Suivant"
|
||||
pause: "Pause"
|
||||
end: "Terminer la visite"
|
||||
welcome:
|
||||
title: "Bienvenue dans Fab-Manager"
|
||||
@ -403,20 +402,29 @@ fr:
|
||||
content: "Une soirée porte ouverte ou un stage pour fabriquer sa lampe de bureau ? C'est par ici !"
|
||||
calendar:
|
||||
title: "Le calendrier publique"
|
||||
content: "Visualisez en un clin d'oeil tout ce qui est prévu au programme des prochaines semaines"
|
||||
content: "Visualisez en un clin d'oeil tout ce qui est prévu au programme des prochaines semaines."
|
||||
projects:
|
||||
title: "Les projets"
|
||||
content: "Documentez et partagez avec la communauté toutes vos réalisations"
|
||||
content: "Documentez et partagez avec la communauté toutes vos réalisations."
|
||||
plans:
|
||||
title: "Les abonnements"
|
||||
content: "Les abonnements offrent un moyen de segmenter vos tarifs et d'accorder des avantages aux utilisateurs réguliers"
|
||||
content: "Les abonnements offrent un moyen de segmenter vos tarifs et d'accorder des avantages aux utilisateurs réguliers."
|
||||
admin:
|
||||
title: "Section administrateur"
|
||||
content: "L'ensemble des éléments ci-dessous ne sont accessibles qu'aux administrateurs. Ils permettent de gérer et de configurer Fab-Manager. À la fin de cette visite, cliquez sur l'un d'entre eux pour en savoir plus."
|
||||
about:
|
||||
title: "À propos"
|
||||
content: "Une page que vous pouvez entièrement personnaliser, pour y présenter votre activité"
|
||||
content: "Une page que vous pouvez entièrement personnaliser, pour y présenter votre activité."
|
||||
notifications:
|
||||
title: "Centre de notifications"
|
||||
content: "Chaque fois qu'il se passe quelque chose d'important, vous serez notifié ici"
|
||||
content: "Chaque fois qu'il se passe quelque chose d'important, vous serez notifié ici."
|
||||
profile:
|
||||
title: "Menu utilisateur"
|
||||
content: "Retrouvez ici vos informations personnelles ainsi que toute votre activité sur Fab-Manager"
|
||||
content: "Retrouvez ici vos informations personnelles ainsi que toute votre activité sur Fab-Manager."
|
||||
version:
|
||||
title: "Version de l'application"
|
||||
content: "Passez votre curseur sur cette icône pour connaître la version de Fab-Manager. Si vous n'êtes pas à jour, cela vous sera signalé ici et vous pourrez alors obtenir des détails en cliquant dessus."
|
||||
conclusion:
|
||||
title: "Merci de votre attention"
|
||||
content: "Affichez de l'aide en appuyant sur <strong>F1</strong> à n'importe quel moment.<br> Bonne continuation avec Fab-Manager."
|
||||
|
||||
|
@ -58,6 +58,7 @@ Rails.application.routes.draw do
|
||||
post 'list', action: 'list', on: :collection
|
||||
get 'search/:query', action: 'search', on: :collection
|
||||
get 'mapping', action: 'mapping', on: :collection
|
||||
patch ':id/complete_tour', action: 'complete_tour', on: :collection
|
||||
end
|
||||
resources :reservations, only: %i[show create index update]
|
||||
resources :notifications, only: %i[index show update] do
|
||||
|
@ -1,3 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# From this migration, we distinct multiple stylesheets by their name (previously there was one only one for the main theme override)
|
||||
class AddNameToStylesheet < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :stylesheets, :name, :string
|
||||
|
8
db/migrate/20200218092221_add_tours_to_profile.rb
Normal file
8
db/migrate/20200218092221_add_tours_to_profile.rb
Normal file
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# From this migration, we save in database the "feature tours" viewed by each users to prevent displaying them many times
|
||||
class AddToursToProfile < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :profiles, :tours, :string
|
||||
end
|
||||
end
|
11
db/schema.rb
11
db/schema.rb
@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20200127111404) do
|
||||
ActiveRecord::Schema.define(version: 20200218092221) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@ -465,6 +465,14 @@ ActiveRecord::Schema.define(version: 20200127111404) do
|
||||
|
||||
add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree
|
||||
|
||||
create_table "plans_availabilities", force: :cascade do |t|
|
||||
t.integer "plan_id"
|
||||
t.integer "availability_id"
|
||||
end
|
||||
|
||||
add_index "plans_availabilities", ["availability_id"], name: "index_plans_availabilities_on_availability_id", using: :btree
|
||||
add_index "plans_availabilities", ["plan_id"], name: "index_plans_availabilities_on_plan_id", using: :btree
|
||||
|
||||
create_table "price_categories", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.text "conditions"
|
||||
@ -511,6 +519,7 @@ ActiveRecord::Schema.define(version: 20200127111404) do
|
||||
t.string "lastfm"
|
||||
t.string "flickr"
|
||||
t.string "job"
|
||||
t.string "tours"
|
||||
end
|
||||
|
||||
add_index "profiles", ["user_id"], name: "index_profiles_on_user_id", using: :btree
|
||||
|
Loading…
x
Reference in New Issue
Block a user