gaf-mobile
Version:
GAF mobile Web site
996 lines (955 loc) • 35.1 kB
JavaScript
'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, '');
});
});
});