UNPKG

gaf-mobile

Version:

GAF mobile Web site

996 lines (955 loc) 35.1 kB
'use strict'; /* global FastClick */ angular.module('gafMobileApp', [ 'ngSanitize', 'ngRoute', 'ngAnimate', 'ngMessages', 'snap', 'uiGmapgoogle-maps', 'google.places', 'angular-carousel', 'configs', 'threepio', 'flAnalytics', 'flApi', 'flApiTranslations', 'flAuth', 'flCaptcha', 'flCookies', 'flFacebook', 'flFeedback', 'flLocation', 'flUi', 'flUtils', 'flValidation', 'flStats', 'flCdn', 'flPayments', 'flPreload', 'ngRaven', 'angular-md5' ]) /* * URLs are "entry points" in the application, not state: * - An entry point is a specific state (i.e. beginning of the * project creation funnel) that users might want to refer * later (= bookmarkable), and/or that Google's bots might * want to crawl (/project/list/xxx-website). * - A state is a combination of a view and a context, like * "Project creation step 3 pick paid options", and SHOULD NOT * have an URL on there own (like /project-creation-step-3). */ .config(function($provide, TranslationsProvider, LocalizedRouteProvider, uiGmapGoogleMapApiProvider, $locationProvider, GOOGLE_MAPS_API_KEY, TRANSLATION_DOMAIN, LANGUAGES, PRIMARY_DOMAIN, LOCAL_DOMAINS) { uiGmapGoogleMapApiProvider.configure({ key: GOOGLE_MAPS_API_KEY, v: '3.17', libraries: 'places' }); TranslationsProvider.translate(TRANSLATION_DOMAIN, true); $locationProvider.html5Mode(true); $locationProvider.hashPrefix('!'); LocalizedRouteProvider.languages(LANGUAGES); LocalizedRouteProvider.localDomains(PRIMARY_DOMAIN, LOCAL_DOMAINS); LocalizedRouteProvider /* Homepage */ .when('/', { templateUrl: '/views/main.html', title: 'Hire Freelancer & Find Freelancer Jobs Online', controller: 'HomePageCtrl', reloadOnSearch: false, redirectAuth: '/dashboard' }) /* Dashboard */ .when('/dashboard', { templateUrl: '/views/dashboard.html', controller: 'DashboardCtrl', controllerAs: 'ctrl', title: 'Dashboard', reloadOnSearch: false, resolve: /* @ngInject */ { loggedInUser: function($q, Users) { return Users.getLoggedInUser({ display_info: true, status: true, jobs: true }); } }, requireAuth: true }) /* My Project */ .when('/my-projects', { templateUrl: '/views/my-projects.html', controller: 'MyProjectsCtrl', controllerAs: 'ctrl', title: 'My Projects', reloadOnSearch: false, requireAuth: true, desktopUrl: '/dashboard/projects.php' }) /* Standard Post Project */ .when('/post-project/custom', { templateUrl: '/views/post-project.html', controller: 'PostProjectCtrl', controllerAs: 'ctrl', title: 'Post a Project', desktopUrl: '/post-project', resolve: /* @ngInject */ { // Load google maps script for autocomplete directive gMaps: function(GMapsScriptLoader) { return GMapsScriptLoader.load(); } } }) /* Chessboard Post Project */ .when('/post-project', { templateUrl: '/views/post-project-chessboard.html', controller: 'ProjectTemplatesCtrl', controllerAs: 'ctrl', title: 'Post a Project', desktopUrl: '/post-project' }) /* Freelancer Skill Selector */ .when('/skill-select', { templateUrl: '/views/skill-select.html', title: 'Select Your Skills', desktopUrl: '/sellers/skill-select.php', requireAuth: true }) /* Simplified Post Project */ .when('/post-project/mobile', { templateUrl: '/views/post-project-template.html', controller: 'PostProjectCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { return 'Post a ' + $route.current.params.title + ' Project'; }, desktopUrl: '/post-project', resolve: /* @ngInject */ { gMaps: function(GMapsScriptLoader) { return GMapsScriptLoader.load(); } } }) /* Templated Post Project */ .when('/post-project/:templateSeoUrl', { templateUrl: '/views/post-project-template.html', controller: 'PostProjectCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { return 'Post a ' + $route.current.locals.template.get().description + ' Project'; }, desktopUrl: '/post-project', resolve: /* @ngInject */ { template: function(ProjectTemplates, $route, $q) { return ProjectTemplates.get() .then(function(templateList) { var template = templateList .getBySeoUrl($route.current.params.templateSeoUrl); return template ? template : $q.reject(); }); }, gMaps: function(GMapsScriptLoader) { return GMapsScriptLoader.load(); }, user: function(Users, Auth, $q) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser({ avatar: true, status: true, balance_details: true }); } else { return $q.when(); } } } }) /* Upgrade Project */ .when('/upgrade-project/:projectId', { templateUrl: '/views/upgrade-project.html', title: 'Upgrade Your Project', controller: 'UpgradeProjectCtrl', controllerAs: 'ctrl', resolve: /* @ngInject */ { projectBundle: function($q, $route, Projects, ProjectFees) { return Projects.getById($route.current.params.projectId, { upgrade_details: true }).then( function(project) { if(project.get().currency) { // Set the project for upgrade Projects.setCurrent(project); return ProjectFees.get(project.get().currency.id); } else { return $q.reject(); } }); } }, reloadOnSearch: false, requireAuth: true }) /* Mobile Landing Page */ .when('/mobile', { templateUrl: '/views/mobile.html', title: 'Freelancer Mobile', desktopUrl: '/mobile' }) .when('/membership', { templateUrl: '/views/membership.html', title: 'Membership Upsell', controller: 'MembershipsCtrl', controllerAs: 'ctrl', requireAuth: true, resolve: /* @ngInject */ { loggedInUser: function($q, Auth, Users) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser({ 'status': true }); } } } }) /* Hire Pages */ .when('/hire/:job?', { templateUrl: '/views/hire.html', controller: 'HirePageCtrl', title: /* @ngInject */ function($route) { if ($route.current.locals.job) { return 'Hire or Find ' + $route.current.locals.job.seo_info.context_phrase_worker; } else if ($route.current.locals.h1) { return 'Hire or Find ' + $route.current.locals.h1; } else { return 'Hire or Find Freelancers Online'; } }, desktopUrl: '/hire/:job', resolve: /* @ngInject */ { job: function($route, Jobs) { return Jobs.getJobBySeoUrl($route.current.params.job) .then(function(job) { // A valid job is optional if (job) { return job.get(); } }); }, h1: function($route, $location) { if ($route.current.params.job) { // Only used when the job is not a valid one return $route.current.params.job.replace(/-/g, ' '); } else if ($location.search().h1) { return $location.search().h1.replace(/-/g, ' '); } } } }) /* Find Page */ .when('/find/:job?', { templateUrl: '/views/find.html', controller: 'FindPageCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { if ($route.current.locals.job) { return 'Hire or Find ' + $route.current.locals.job.seo_info.context_phrase_worker; } else { return 'Hire or Find Freelancers Online'; } }, desktopUrl: '/hire/:job', resolve: /* @ngInject */ { job: function(Jobs, $route) { return Jobs.getJobBySeoUrl($route.current.params.job) .then(function(job) { if (job) { return job.get(); } }); }, loggedInUser: function(Auth, Users, $q) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser(); } return $q.when(); }, template: function(Jobs, ProjectTemplates, $route, $q, $location, PROJECT_TEMPLATE_CATEGORY_BUNDLE) { var job = $route.current.params.job; if (job) { // Use the category of the given job to get the project template // using the mapping defined in PROJECT_TEMPLATE_CATEGORY_BUNDLE return Jobs.getJobBySeoUrl(job) .then(function(job) { if (job) { return ProjectTemplates.get() .then(function(templateList) { var templates = PROJECT_TEMPLATE_CATEGORY_BUNDLE .filter(function(data) { return data.categoryId === job.get().category.id; }); var templateId = templates[0].templateId; var template = templateList.getById(templateId); return template ? template.get() : $q.reject(); }); } else { return $q.reject(); } }) .catch(function() { $location.path('/post-project'); $location.replace(); return $q.defer().promise; }); } else { $location.path('/post-project'); $location.replace(); return $q.defer().promise; } } } }) /* Job Pages */ .when('/projects/:route*', { templateUrl: '/views/view-project/view-project.html', controller: 'ViewProjectCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { if($route.current.locals.projectBundle) { return $route.current.locals.projectBundle.get().title; } }, desktopUrl: '/projects/:route/#/details', resolve: /* @ngInject */ { projectBundle: function($route, ViewProjectCtrlFactory) { var route = $route.current.params.route; // Projects with seo_url projects/<skill>/<seo_url>-<id> // Projects without seo_url projects/<skill>/project-<id> var projectId = route.match(/-([0-9]+)(?:\/)?$/); // Projects with deprecated urls projects/<skill>/<projectid>.html var projectIdHTML = route.match(/([0-9]+)(?:\/)?.html$/); if (projectId && projectId[1] && angular.isNumber(Number(projectId[1]))) { return ViewProjectCtrlFactory.getProject(Number(projectId[1])); } else if (projectIdHTML && projectIdHTML[1] && angular.isNumber(Number(projectIdHTML[1]))) { return ViewProjectCtrlFactory .getProject(Number(projectIdHTML[1])); } else { // Handle deprecated GAF routes with projects/<seo_url>.html route = route.replace('.html', ''); return ViewProjectCtrlFactory.getProject(route); } }, userBids: function(Auth, Bids, $q) { if (Auth.isAuthenticated()) { return Bids.get({ 'bidders[]': Auth.getUserId() }); } else { return $q.when(); } }, gMaps: function(GMapsScriptLoader) { return GMapsScriptLoader.load(); }, loggedInUser: function(Auth, Users, $q) { if (!Auth.isAuthenticated()) { return $q.when(undefined); } return Users.getLoggedInUser({ status: true, jobs: true, location_details: true }); } }, reloadOnSearch: false }) /* Freelancers Directory */ .when('/freelancers', { templateUrl: '/views/freelancers-directory.html', controller: 'FreelancersDirectoryCtrl', controllerAs: 'ctrl', title: 'Browse Freelancers', desktopUrl: '/freelancers', reloadOnSearch: false, resolve: /* @ngInject */ { loggedInUser: function($q, Auth, Users) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser(); } return $q.when(undefined); }, categories: function(Categories) { return Categories.getListWithJobs(); }, } }) /* Jobs Directory Root */ .when('/jobs', { templateUrl: '/views/jobs-directory.html', controller: 'JobsDirectoryCtrl', controllerAs: 'ctrl', title: 'Jobs', desktopUrl: '/job', resolve: /* @ngInject */ { projects: function(JobsDirectoryManager) { return JobsDirectoryManager.loadPage(); } }, reloadOnSearch: false }) /* Local Jobs Directory */ .when('/jobs/localjobs', { templateUrl: '/views/jobs-localjobs.html', title: 'Browse Local Jobs', desktopUrl: '/jobs/localjobs', resolve: /* @ngInject */ { // Load google maps script for autocomplete directive gMaps: function(GMapsScriptLoader) { return GMapsScriptLoader.load(); }, userLocation: function($q, Auth, Users) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser({ location_details: true, is_local: true }); } else { return $q.when(); } } } }) /* Jobs Directory */ .when('/jobs/:route*', { templateUrl: '/views/jobs-directory.html', controller: 'JobsDirectoryCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { if ($route.current.locals.job) { return 'Latest ' + $route.current.locals.job.get().name + ' Jobs'; } else { return 'Latest ' + $route.current.params.route.replace(/-/g, ' ') + ' Jobs'; } }, desktopUrl: '/jobs/:route', resolve: /* @ngInject */ { projects: function($q, $route, $location, Jobs, JobsDirectoryManager) { var route = $route.current.params.route; // TODO: remove duplication var jobsUrlRe = /^([^\/]+)(?:(?:\/)|(?:\/([0-9]+)\/?))?$/; var jobsDirUrl = route.match(jobsUrlRe); if (jobsDirUrl && jobsDirUrl.length > 1) { var params = {}; if (/^[0-9]+$/.test(jobsDirUrl[1])) { params.page = jobsDirUrl[1]; } else { params.job = jobsDirUrl[1]; params.page = jobsDirUrl[2]; } if (params.page === '1') { $location.path('/jobs/' + route.slice(0, -2)); $location.replace(); // Need to prevent the invalid route to be loaded // before the redirection return $q.defer().promise; } else { return Jobs.getJobBySeoUrl(jobsDirUrl[1]).then(function(job) { if (job) { return JobsDirectoryManager.loadPage(params.page, '', { 'jobs[]': job.get().id }, true); } else { return $q.reject(); } }); } } }, job: function($q, $route, $location, Jobs) { var route = $route.current.params.route; // TODO: remove duplication var jobsUrlRe = /^([^\/]+)(?:(?:\/)|(?:\/([0-9]+)\/?))?$/; var jobsDirUrl = route.match(jobsUrlRe); if (jobsDirUrl && jobsDirUrl[1]) { return Jobs.getJobBySeoUrl(jobsDirUrl[1]); } else { $location.path('/jobs'); $location.replace(); return $q.defer().promise; } } }, reloadOnSearch: false }) /* Work Directory is the same of the Jobs Directory expect that that * route doesn't contain the PVPs too */ .when('/work/:route*', { templateUrl: '/views/jobs-directory.html', controller: 'JobsDirectoryCtrl', controllerAs: 'ctrl', title: /* @ngInject */ function($route) { if ($route.current.locals.job) { return 'Latest ' + $route.current.locals.job.get().name + ' Jobs'; } else { return 'Latest ' + $route.current.params.route.replace(/-/g, ' ') + ' Jobs'; } }, desktopUrl: '/work/:route', resolve: /* @ngInject */ { projects: function($q, $route, $location, JobsDirectoryManager) { var route = $route.current.params.route; // TODO: remove duplication var jobsUrlRe = /^([^\/]+)(?:(?:\/)|(?:\/([0-9]+)\/?))?$/; var jobsDirUrl = route.match(jobsUrlRe); // If it's not a valid jobs directory url, it's a project page if (jobsDirUrl && jobsDirUrl.length > 1) { var params = {}; if (/^[0-9]+$/.test(jobsDirUrl[1])) { params.page = jobsDirUrl[1]; } else { params.job = jobsDirUrl[1]; params.page = jobsDirUrl[2]; } if (params.page === '1') { $location.path('/jobs/' + route.slice(0, -2)); $location.replace(); // Need to prevent the invalid route to be loaded // before the redirection return $q.defer().promise; } else { return JobsDirectoryManager.loadPage(params.page, params.job, { 'job_seo_urls[]': params.job }, true); } } else { return $q.reject(); } }, job: function($q, $route, Jobs) { var route = $route.current.params.route; // TODO: remove duplication var jobsUrlRe = /^([^\/]+)(?:(?:\/)|(?:\/([0-9]+)\/?))?$/; var jobsDirUrl = route.match(jobsUrlRe); if (jobsDirUrl && jobsDirUrl[1]) { return Jobs.getJobBySeoUrl(jobsDirUrl[1]); } else { return $q.when(); } } }, reloadOnSearch: false }) /* User Profiles */ .when('/u/:username', { templateUrl: '/views/user-profiles.html', controller: 'UserProfileCtrl', controllerAs: 'ctrl', //Include work phrase to title once Job APIs are available title: /* @ngInject */ function($route) { var user = $route.current.locals.user.get(); var capitalizedRole = user.role.charAt(0).toUpperCase() + user.role.slice(1); return $route.current.params.username + ' - ' + user.location.country.name + ' | ' + capitalizedRole; }, resolve: /* @ngInject */ { user: function($route, $location, $q, Users) { var params = { reputation: true, avatar: true, profile_description: true, jobs: true, cover_image: true }; return Users.getByUsername($route.current.params.username, params); }, loggedInUser: function($q, Auth, Users) { if (Auth.isAuthenticated()) { return Users.getLoggedInUser(); } return $q.when(undefined); } }, desktopUrl: '/u/:username' + '.html', reloadOnSearch: false }) /* NDA form */ .when('/nda/:projectId', { templateUrl: '/views/nda-form.html', title: 'Sign Non-Disclosure Agreement', desktopUrl: '/NDA/NDAcreator.php?project_id=:projectId', requireAuth: true }) /* Deposit Page */ .when('/deposit/:depositMethod?', { templateUrl: '/views/deposit.html', controller: 'DepositCtrl', controllerAs: 'ctrl', title: 'Deposit Funds', desktopUrl: '/payments/deposit.php', requireAuth: true, resolve: /* @ngInject */ { // Load threatmetrix threatmetrix: function(Threatmetrix) { return Threatmetrix.load(); } }, reloadOnSearch: false }) /* Deposit Page for Native Mobile Webview */ .when('/webview/deposit', { templateUrl: '/views/deposit-webview.html', controller: 'DepositCtrl', controllerAs: 'ctrl', title: 'Deposit Funds', requireAuth: true, resolve: /* @ngInject */ { // Load threatmetrix threatmetrix: function(Threatmetrix) { return Threatmetrix.load(); } }, reloadOnSearch: false }) /* Login */ .when('/login', { templateUrl: '/views/login.html', controller: 'LoginCtrl', controllerAs: 'ctrl', title: 'Login', desktopUrl: '/users/login.php', reloadOnSearch: false, redirectAuth: '/dashboard' }) /* Signup */ .when('/signup', { templateUrl: '/views/signup.html', controller: 'SignupCtrl', controllerAs: 'ctrl', title: 'Create an Account', desktopUrl: '/signup', reloadOnSearch: false, redirectAuth: '/dashboard' }) /* Log out */ .when('/logout', { templateUrl: '/views/logout.html', controller: 'LogoutCtrl', title: 'Logout' }) /* Reset Password */ .when('/reset-password', { templateUrl: '/views/reset-password.html', controller: 'ResetPasswordCtrl', controllerAs: 'ctrl', title: 'Reset your password', desktopUrl: '/login', redirectAuth: '/dashboard' }) /* Unsupported browsers */ .when('/unsupported-browser', { templateUrl: '/views/unsupported-browser.html', title: 'Unsupported browser alert - Opera' }) /* Verify Phone */ .when('/verify-phone', { templateUrl: '/views/verify-phone.html', controller: 'PhoneVerificationCtrl', controllerAs: 'ctrl', resolve: /* @ngInject */ { loggedInUser: function(Users) { return Users.getLoggedInUser({ status: true, location_details: true }); } }, requireAuth: true }) /* Verify Payment Method */ .when('/payments/verify', { templateUrl: '/views/verify-payment.html', controller: 'PaymentVerificationCtrl', controllerAs: 'ctrl', title: 'Add a Verified Payment Method', desktopUrl: '/payments/verify.php', requireAuth: true, resolve: /* @ngInject */ { // Load threatmetrix threatmetrix: function(Threatmetrix) { return Threatmetrix.load(); } }, reloadOnSearch: false }) /* Inbox */ .when('/messages', { templateUrl: '/views/inbox.html', title: 'Inbox', desktopUrl: '/messages', requireAuth: true, reloadOnSearch: false }) /* User Message By Param */ .when('/messages/thread', { templateUrl: '/views/thread.html', title: 'User Message', desktopUrl: '/messages#/thread/', requireAuth: true, }) /* User Message By ID */ .when('/messages/thread/:threadId', { templateUrl: '/views/thread.html', title: 'User Message', desktopUrl: '/messages#/thread/:threadId', requireAuth: true, }) /* Soft 404 Page */ .when('/not-found', { templateUrl: '/views/error.html', controller: 'ErrorCtrl', controllerAs: 'ctrl' }) /* Offline Page */ .when('/offline', { templateUrl: '/views/offline.html', title: 'Offline' }) /* Ghost Campaign Route that takes `l` as parameter for redirect which defaults to /post-project if route is not valid in mobile web */ .when('/campaign/:program', { title: 'Campaign Ads', resolve: /* @ngInject */ { redirect: function($q, $route, $location, Analytics) { var redirectUrl = $location.search().l; var program = $route.current.params.program; var programPrepend = program[0]; // This checks that the first character of the route is `0` as // specified in the campaign URL specifications if (programPrepend === '0' && redirectUrl) { // Remove the prepended `0` on the string and treat that var programRoot = program.replace(programPrepend, ''); var programName = programRoot.match(/[A-Z]+/i)[0]; var programId = programRoot.match(/[0-9]+/)[0]; var parsedRedirectUrl = redirectUrl.replace( /^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/, '' ); Analytics.trackEvent('CampaignClick', programName, redirectUrl, false, false, programId); $location.url(parsedRedirectUrl); $location.replace(); } else { $location.url('/post-project'); } } } }) .otherwise({ // Redirect to the error page redirectTo: function(routeParams, path, search) { // Build the search string (search is an object) var searchString = ''; for (var key in search) { if (searchString) { searchString += '&'; } searchString += key + '=' + search[key]; } // Record the path & the search string (if any) return '/not-found?url=' + encodeURIComponent(path + (searchString ? '?' + searchString : '')) + '&reason=' + encodeURIComponent('NO ROUTE'); } }); // Push JS exceptions to GA & New Relic $provide.decorator('$exceptionHandler', function($window, $delegate, Analytics) { return function(exception) { Analytics.trackError('angular', exception.message); if ($window.NREUM) { $window.NREUM.noticeError(exception); } return $delegate.apply($delegate, arguments); }; }); }) // Dynamically configure API_BASE_URL according to country domain to // ensure authentication is valid for that domain or // API_CUSTOM_URL constant if this exists .value('API_BASE_URL', '') .config(function($provide) { $provide.decorator('API_BASE_URL', function ($location, API_CUSTOM_URL) { var tld = $location.host().split('freelancer')[1]; var countryDomainUrl = 'https://www.freelancer' + tld + '/api'; return API_CUSTOM_URL ? API_CUSTOM_URL : countryDomainUrl; }); }) // Dynamically configure GAF_BASE_URL according to country domain to // ensure authentication is valid for that domain or // GAF_CUSTOM_URL constant if this exists .value('GAF_BASE_URL', '') .config(function($provide) { $provide.decorator('GAF_BASE_URL', function ($location, GAF_CUSTOM_URL) { var tld = $location.host().split('freelancer')[1]; var countryDomainUrl = 'https://www.freelancer' + tld; return GAF_CUSTOM_URL ? GAF_CUSTOM_URL : countryDomainUrl; }); }) .run(function($rootScope, $route, $location, $timeout, $injector, $window, LocalizedRoute, Localization, Translation, GAF_BASE_URL, $raven, Auth, Analytics, Experiments, BrowserLanguage, LANGUAGES) { var preferredLanguage = BrowserLanguage.getPreferredLanguage(); if (LANGUAGES[preferredLanguage]) { Localization.language(preferredLanguage, true); } // Track user in Sentry, if logged in if(Auth.isAuthenticated()) { $raven.setUserContext({ id: Auth.getUserId() }); } LocalizedRoute.languages(); // Change page title, based on route information $rootScope.$on('$routeChangeSuccess', function() { if (angular.isString($route.current.title)) { $rootScope.title = $route.current.title + ' | Freelancer'; } else if (angular.isFunction($route.current.title) || angular.isArray($route.current.title)) { $rootScope.title = $injector.invoke($route.current.title) + ' | Freelancer'; } else { $rootScope.title = 'Freelancer'; } }); // Set desktop (canonical) URL based on route information $rootScope.$on('$routeChangeSuccess', function() { var desktopUrl = $route.current.desktopUrl; if (desktopUrl) { desktopUrl = desktopUrl.replace(/:([a-zA-Z])+/g, function(match) { // replace() doesn't return capture groups when the global // flag is set so you need to strip of the ':' delimiter var param = $route.current.params[match.split(':')[1]]; return param ? param : ''; }); // Strip trailing slash if any if (desktopUrl.substr(-1) === '/') { desktopUrl = desktopUrl.slice(0, -1); } // Strip trailing .html if any for deprecated SEO urls desktopUrl = desktopUrl.replace('.html', ''); } // If there no desktopUrl it will default to Gaf base URL $rootScope.desktopUrl = GAF_BASE_URL + (desktopUrl ? desktopUrl : ''); }); // Set route experiment variations on current route $rootScope.$on('$routeChangeSuccess', function() { var experiments = $route.current.experiments; if(experiments) { angular.forEach(experiments, function(experiment) { $route.current['isIn' + experiment.id + 'Variation'] = Experiments.activateTest(experiment.id, experiment.needsAuth, experiment.variation); }); } }); // Handle the HTTP redirect logic when pre-rendering the app server side $rootScope.$on('$locationChangeStart', function(event, current, previous) { if ($window.FL_PRERENDER_ENABLED && (current !== previous)) { // Default the redirect to a 404 if (!$window.FL_PRERENDER_HTTP_STATUS_CODE) { $window.FL_PRERENDER_HTTP_STATUS_CODE = 404; } $window.FL_PRERENDER_HTTP_REDIRECT_LOCATION = current; // Prevent Angular to actually do the redirect event.preventDefault(); } }); // Catch the route errors: the route exist but the pre-conditions // are not resolved, e.g. for the project pages the project does not exist $rootScope.$on('$routeChangeError', function(event, current, previous, rejection{ // Consider these route changes as 404 $window.FL_PRERENDER_HTTP_STATUS_CODE = 404; $location.url('/error?url=' + encodeURIComponent($location.url()) + (rejection ? ('&reason=' + encodeURIComponent(rejection.statusText)) : '')); $location.replace(); }); // Prevent the 300ms touch delay on Apple devices FastClick.attach(document.body); var loadingTimeout; $rootScope.$on('$routeChangeStart', function() { $rootScope.isViewLoading = true; }); $rootScope.$on('$routeChangeSuccess', function() { Translation.loadTranslations(Localization.language()).finally(function() { if (loadingTimeout) { $timeout.cancel(loadingTimeout); } $rootScope.isViewLoading = false; }); }); // Check auth requirement and redirect to login page if unfulfilled or to // custom URL if fulfilled and show info banner $rootScope.$on('$routeChangeStart', function(event, next) { // Show offline page, if necessary, when launched from the homescreen if ($location.search().homescreen) { if (!$window.navigator.onLine) { Analytics.trackEvent('InstallableWebApp', '', 'launch', 'isOffline', true, 'offline'); $location.url('/offline'); return; } else { Analytics.trackEvent('InstallableWebApp', '', 'launch', 'isOffline', false, 'online'); } } if(next.requireAuth && !Auth.isAuthenticated()) { $location.url('/login?return=' + encodeURIComponent($location.url())); $location.hash('showBanner'); $route.reload(); } if(next.redirectAuth && Auth.isAuthenticated()) { $location.url(next.redirectAuth); $route.reload(); } }); // Redirect Opera Mini users on Android to the unsupported browser page if(navigator.userAgent.indexOf('Opera Mini') >= 0 && navigator.userAgent.indexOf('Android') >= 0 && window.location.href.indexOf('unsupported-browser') === -1) { window.location.href = '/unsupported-browser'; } // Mark when module has finished bootstrapping if ($window.performance && $window.performance.now) { // Track in QTS Analytics.trackMobileUserTiming('ng_app_loaded', $window.performance.now()); // Track using the User Timing API (not yet supported in Safari) if ($window.performance.mark) { $window.performance.mark('ng_app_loaded'); } } // Track user action on Add to Homescreen prompt (Chrome 43+) window.addEventListener('beforeinstallprompt', function(e) { Analytics.trackEvent('AddToHomescreen', '', 'AddToHomescreen', 'showPrompt', true, ''); e.userChoice.then(function(choiceResult) { Analytics.trackEvent('AddToHomescreen', '', 'AddToHomescreen', 'install', choiceResult.outcome, ''); }); }); });