diff --git a/.gitignore b/.gitignore index ef76e47..8ec555c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ *.db media/ local_settings.py -static/node_modules/ \ No newline at end of file +assets/node_modules/ \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index de18521..df10c4a 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -77,4 +77,14 @@ def get(self, *args, **kwargs): projects = Project.objects.filter(account=self.request.user) return render(self.request, self.template_name, { 'projects': projects, - }) \ No newline at end of file + }) + + +class BaseView(TemplateView): + """ base view + """ + template_name = 'base.html' + + def get(self, *args, **kwargs): + return render(self.request, self.template_name, {}) + diff --git a/assets/app/app.es6 b/assets/app/app.es6 new file mode 100644 index 0000000..dc613fa --- /dev/null +++ b/assets/app/app.es6 @@ -0,0 +1,54 @@ +import { + DashboardController, + AccountSettingController, + SignupController, + LoginController, + AdminDashboardController, + UpdateLogController +} from './components/controllers.es6'; + +import { + AccountService, + AuthService, + AdminService +} from './components/services.es6'; + +angular + .module('tracker', [ + 'ui.router', + 'ui.bootstrap', + 'angularMoment', + 'angular-storage', + 'angular-jwt', + 'angular-duration-format', + 'timer', + 'ngFileUpload', + 'angular.filter', + 'ui.select', + 'ngSanitize', + 'sc.select', + 'ui-notification', + 'obDateRangePicker' + ]) + .constant('TEMPLATE_URL', 'static/app/templates/') + .constant('API_URL', 'http://127.0.0.1:8080/api/') + .service('AccountService', AccountService) + .service('AuthService', AuthService) + .service('AdminService', AdminService) + .controller('DashboardController', DashboardController) + .controller('AccountSettingController', AccountSettingController) + .controller('SignupController', SignupController) + .controller('LoginController', LoginController) + .controller('AdminDashboardController', AdminDashboardController) + .controller('UpdateLogController', UpdateLogController) + .filter('toHrs', ()=> { + return (input)=> { + let total = 0; + for(let i = 0; i { + AuthService.logout(); + } + + /// get current user data + $scope.$watch( ()=> { + return AccountService.loaded; + },(isReady) => { + if(!isReady) { + $scope.user = AccountService.user; + $scope.user.birthdate = moment(AccountService.user.birthdate).toDate(); + } + }); + + ///get all projects of authenticated user + AccountService.getProjects().then((resp) => { + let data = resp.data + + $scope.projects = data; + if (data.length > 0) { + $scope.selectedProject = data[0]; + $scope.currentProject = data[0]; + } + }); + + ///get all user's logs + ($scope.completeLogs = () => { + AccountService.getAllLogs().then((resp) => { + $scope.allLogs = resp.data; + let project = $scope.selectedProject; + + let allLogs = []; + $scope.allLogs.map((log) => { + if(log.project == project.id) { + return allLogs.push(log); + } + }); + $scope.logs = allLogs.slice(0); + }); + })(); + + ///get current running log (if available) + AccountService.getCurrentLog().then((resp) => { + let data = resp.data; + if (data.project != null) { + $scope.selectedLog = $scope.currentLog = data; + $scope.ongoing = true; + + let date = this._$moment(data.start).toDate(); + $scope.started = date.getTime(); + + $scope.projects.find((project) => { + if(project.id == data.project) { + return $scope.selectedProject = project; + } + }); + } + $scope.tracking = data.project != null; + }); + + ///EVENT FUNCTIONS + $scope.selectLog = (logs) => { + let log = logs.slice(-1)[0]; + $scope.selectedLog = log; + }; + + $scope.viewProjectLogs = (project) => { + $scope.selectedProject = project; + $scope.logs = []; + $scope.allLogs.map((log) => { + if(log.project == project.id) { + return $scope.logs.push(log) + } + }); + }; + + $scope.createNewLog = (newLog) => { + let data = { + "project" : $scope.selectedProject.id, + "memo" : newLog.memo, + "timein" : true + } + AccountService.playTracker(data).then((resp) => { + let data =resp.data; + $scope.currentLog = data; + $scope.logs.push(data); + $scope.selectedLog = data; + $scope.tracking = true; + $scope.reloaded = false; + $scope.newLog.memo = ''; + }).catch((err) => { + console.log(err); + }); + setTimeout(() => { + $scope.$broadcast('timer-start'); + },500) + } + + $scope.startTracker = (selectedLog) => { + $scope.started = (new Date()).getTime(); + let data = { + "project" : selectedLog.project, + "memo" : selectedLog.memo, + "timein" : true + } + + AccountService.playTracker(data).then((resp) => { + $scope.currentLog = resp.data; + $scope.tracking = true; + $scope.reloaded = false; + }).catch((err) => { + console.log(err); + }); + setTimeout(() => { + $scope.$broadcast('timer-start'); + },500) + }; + + $scope.stopTracker = (selectedLog) => { + let data = { + "project" : selectedLog.project, + "memo" : selectedLog.memo, + "timein" : false + } + AccountService.playTracker(data).then((resp) => { + $scope.tracking = false; + $scope.ongoing = false; + $scope.completeLogs(); + if ($scope.reloaded){ + $scope.stopped = true; + } + }).catch((err) => { + console.log(err); + }); + setTimeout(() => { + $scope.$broadcast('timer-stop'); + },500) + }; + + //LOGS FILTERING + $scope.getLogs = (project_id) => { + let key = $scope.logsKey; + let currDate = new Date(); + + if (project_id){ + $scope.logs = []; + $scope.allLogs.map((log) => { + if(log.project == project_id) { + return $scope.logs.push(log) + } + }); + }; + if(key == 'today') { + let today = currDate.getDate(); + let currentLogs = $scope.logs; + + $scope.logs = currentLogs.filter((log) => { + let date = this._$moment(log.start).toDate().getDate(); + if(today == date) { + return log; + } + }); + }else if(key == 'yesterday') { + let yesterday = currDate.getDate()-1; + let currentLogs = $scope.logs; + + $scope.logs = currentLogs.filter((log) => { + let date = this._$moment(log.start).toDate().getDate(); + if(yesterday == date) { + return log; + } + }); + }else if(key == 'week') { + let currentLogs = $scope.logs; + // First day of the week + let firstday = currDate.getDate() - currDate.getDay(); + // Last day of the week + let lastday = firstday + 6; + + $scope.logs = currentLogs.filter((log) => { + let date = this._$moment(log.start).toDate().getDate(); + if(date >= firstday && date <= lastday ) { + return log; + } + }); + }else if(key == 'month') { + let currentLogs = $scope.logs; + // First day of the month + let firstday = (new Date(currDate.getFullYear(), currDate.getMonth(), 1)).getDate(); + // Last day of the month + let lastday = (new Date(currDate.getFullYear(), currDate.getMonth() + 1, 0)).getDate(); + + $scope.logs = currentLogs.filter((log) => { + let date = this._$moment(log.start).toDate().getDate(); + if(date >= firstday && date <= lastday ) { + return log; + } + }); + }else if(key == 'approved') { + let currentLogs = $scope.logs; + + $scope.logs = currentLogs.filter((log) => { + if(log.is_approved == true) { + return log; + } + }); + }else { + return $scope.logs; + } + }; + + $scope.$watch(() => { + if ($scope.tracking) { + $window.onbeforeunload = (event) => { + return "Tracker still running!"; + }; + }; + }); + + //MODAL + $scope.openAccountSetting = () => { + let modalInstance = this._$uibModal.open({ + windowTemplateUrl : 'static/node_modules/angular-ui-bootstrap/template/modal/window.html', + animation : true, + backdrop : 'static', + keyboard : false, + templateUrl : 'account-setting.html', + controller : 'AccountSettingController', + controllerAs : 'ctrl', + scope : $scope + }); + }; + + $scope.openUpdateLog = (log) => { + $scope.selectedLog = log; + + let modalInstance = this._$uibModal.open({ + windowTemplateUrl : 'static/node_modules/angular-ui-bootstrap/template/modal/window.html', + animation : true, + backdrop : 'static', + keyboard : false, + templateUrl : 'update-log.html', + controller : 'UpdateLogController', + controllerAs : 'ctrl', + scope : $scope, + resolve : { + log : () => { + return $scope.selectedLog; + } + } + }); + }; + } + + activeDashboard () { + let bodyClass = document.getElementById("main-body").classList; + + bodyClass.add('left-open', 'right-open'); + return (event, toState, toParams) => { + bodyClass.remove('left-open','right-open'); + } + } +} + +//ACCOUNT SETTING CONTROLLER +class AccountSettingController { + constructor($scope, $uibModalInstance, moment, AccountService, AuthService) { + 'ngInject'; + this._$uibModalInstance = $uibModalInstance; + $scope.uploadSuccess = false; + $scope.errors; + + $scope.updateProfile = (form) => { + let data = angular.copy(form); + data.birthdate = moment(data.birthdate).format('YYYY-MM-DD'); + + AccountService.update(data).then((resp) => { + this._$uibModalInstance.close(); + }) + .catch((error) => { + $scope.errors = error.data; + }) + } + + $scope.uploadPhoto = (form) => { + AccountService.uploadPhoto(form).then((resp) => { + $scope.uploadSuccess = true; + $scope.user.profile_photo = resp.data.profile_photo; + $scope.change = false; + }) + .catch((error) => { + console.log('error'); + }) + } + + //EVENT FUNCTION + $scope.cancel = () => { + this._$uibModalInstance.close(); + AccountService.getCurrentUser().then(account => { + $scope.user = account; + }); + }; + + $scope.cancelUpload = () => { + AccountService.getCurrentUser().then(account => { + $scope.user.profile_photo = account.profile_photo; + $scope.change = false; + }); + } + } +} + +//UPDATE LOG CONTROLLER +class UpdateLogController { + constructor($scope, moment, $uibModalInstance, AccountService) { + 'ngInject'; + this._$uibModalInstance = $uibModalInstance; + this._moment = moment; + + $scope.updateLog = (log) => { + let data = angular.copy(log); + + data.start = this._moment(data.start).toDate(); + data.end = this._moment(data.end).toDate(); + AccountService.updateLog(data).then((resp) => { + $scope.selectedLog = resp.data; + }).catch((err) => { + console.log(err); + }); + }; + //EVENT FUNCTION + $scope.cancel = () => { + this._$uibModalInstance.close(); + }; + } +} + +//USER SIGNUP CONTROLLER +class SignupController { + constructor($scope, $state, moment, AccountService) { + this.moment = moment; + this.$state = $state; + $scope.form = { + 'gender' : 'm', + 'position' : 'designer' + }; + + $scope.signup = (form) => { + let data = angular.copy(form); + + data.birthdate = this.moment(data.birthdate).format('YYYY-MM-DD'); + + AccountService.signup(data).then((resp) => { + this.$state.go('login'); + }).catch((err) => { + console.log(err); + }); + }; + } +} + +//ADMIN CONTROLLER +class AdminDashboardController { + constructor ($scope, $state, AuthService, AccountService, AdminService, store, moment, filterFilter, Notification) { + 'ngInject'; + + this.$scope = $scope; + this.AuthService = AuthService; + this.AccountService = AccountService; + this.AdminService = AdminService; + + $scope.user = undefined; + $scope.projectMembers = []; + $scope.members = []; + $scope.projects = []; + $scope.allMembers = []; + + $scope.logout = () => { + AuthService.logout(); + } + + $scope.$watch( ()=> { + return this.AccountService.loaded; + },(isReady) => { + if(!isReady) { + $scope.user = this.AccountService.user; + } + }); + + ///get all projects of current admin + AccountService.getAllProjects().then((resp) => { + $scope.projects = resp.data; + }); + + ///get all members on different projects + ($scope.allMembers = () => { + AccountService.getProjectMembers().then((resp) => { + $scope.projectMembers = resp.data; + }); + })(); + + ///get all accounts + ($scope.allAccounts = () => { + AccountService.getAccounts().then((resp) => { + $scope.accounts = resp.data; + }); + })(); + + $scope.getMembers = (project) => { + $scope.project = project; + $scope.members = []; + $scope.projectMember = []; + $scope.projectMembers.map((account) => { + if(account.project == project.id) { + $scope.members.push(account); + $scope.selectedMember = account; + $scope.selectedProject = project.name; + $scope.currentProject = project; + $scope.projectMember.push(account); + } + }); + AccountService.getProjectNoneMembers($scope.members).then((resp) => { + $scope.filteredMembers = resp.data; + }); + }; + + $scope.inviteMember = (project) => { + if(!project.member) { + Notification.clearAll() + Notification.warning('No email inputted!'); + }else { + Notification.info('Sending...'); + AccountService.sendInvite(project).then((resp) => { + Notification.clearAll() + Notification.success('Invitation Sent!'); + $scope.project.member = ''; + $scope.members.push(resp.data); + }).catch((err) => { + if(err.status == 500) { + Notification.clearAll() + Notification.warning({message: 'Make sure that this email was not added yet to this project.', title: 'Failed:'}); + }; + console.log(err.data); + }); + }; + }; + + //get all projects + this.AdminService.getProjects().then(resp => { + let data = resp; + $scope.currentProject = undefined; + $scope.projects = data; + if (data.length > 0) { + return $scope.currentProject = data[0]; + } + }); + + this.AdminService.getMembers().then(resp => { + let data = resp; + $scope.allMembers = data; + + //set current project + $scope.projectMember = []; + data.map((member) => { + let project = $scope.currentProject; + if(member.project == project.id) { + $scope.selectedMember = member; + $scope.selectedProject = project.name; + $scope.currentProject = project; + return $scope.projectMember.push(member); + } + }); + }); + + // get project members + $scope.projectMembers = (project) => { + $scope.projectMember = []; + $scope.allMembers.map((member) => { + if(member.project == project.id) { + $scope.selectedMember = member; + $scope.selectedProject = project.name; + $scope.currentProject = project; + return $scope.projectMember.push(member) + } + }); + }; + + $scope.allMemberLogs = []; + this.AdminService.getMemberLogs().then(resp => { + $scope.allMemberLogs = resp; + }); + + //default date range + this.dateStart = moment().startOf('isoweek').format('YYYY-MM-DD'); + this.dateEnd = moment().endOf('isoweek').format('YYYY-MM-DD'); + + // get member all logs + $scope.memberLogs = (member) => { + $scope.memberLog = []; + $scope.userLog = []; + $scope.selectedUser = member; + $scope.allMemberLogs.map((log) => { + if(log.member.project == member.project) { + if (member.account == log.member.account) { + log.start = moment(log.start).format('YYYY-MM-DD'); + log.seconds = moment.duration(log.log_field).asSeconds(); + if (this.dateStart <= log.start && log.start <= this.dateEnd) { + $scope.userLog.push(log.log_field); + return $scope.memberLog.push(log); + } + } + } + }); + + // get total hours + let total = 0; + $scope.totalHours = 0; + for(let i = 0; i <$scope.userLog.length; i++){ + let log = $scope.userLog[i]; + let logSeconds = moment.duration(log).asSeconds() + total += (logSeconds * 1000); + } + $scope.totalHours = total; + }; + + //date range filter + $scope.dateRangeFilter = (fieldName, minValue, maxValue) => { + if (!minValue && !maxValue) return; + return (item) => { + return minValue <= item[fieldName] && item[fieldName] <= maxValue; + }; + }; + + this.dateRangeApi = {}; + this.dropsUp = false; + this.opens = 'center'; + this.disabled = false; + this.format = 'YYYY-MM-DD'; + this.autoApply = true; + this.weekStart = 'mo'; + this.linked = true; + this.calendarsAlwaysOn = true; + + this.range = { + start: moment().startOf('isoweek'), + end: moment().endOf('isoweek') + }; + + this.setRange = () => { + this.dateRangeApi.setDateRange({ + start: moment().startOf('isoweek'), + end: moment().endOf('isoweek') + }); + } + + this.ranges = [ + { + name: 'Today', + start: moment(), + end: moment() + }, + { + name: 'Yesterday', + start: moment().subtract(1, 'd'), + end: moment().subtract(1, 'd') + }, + { + name: 'Current Week', + start: moment().startOf('isoweek'), + end: moment().endOf('isoweek') + }, + { + name: 'Current Month', + start: moment().startOf('month'), + end: moment() + } + ]; + + this.rangeApplied = (start, end) => { + this.dateStart = moment(start).format('YYYY-MM-DD'); + this.dateEnd = moment(end).format('YYYY-MM-DD'); + }; + + } +} + +//LOGIN CONTROLLER +class LoginController { + constructor($scope, $state, $window, store, AuthService) { + + $scope.form = {}; + $scope.errors = {}; + + $scope.userLogin = (form) => { + AuthService.login(form).then(() =>{ + $window.location.reload(); + }) + .catch((error) => { + $scope.errors = error; + }) + } + + AuthService.getAuthUser().then(account => { + if (AuthService.isAuthenticated() && ($state.current.name === 'login')) { + if (account.is_admin === true) { + store.set('account_type', 'admin'); + $state.go('admin'); + } else { + store.set('account_type', 'user'); + $state.go('dashboard'); + } + } + }); + } +} + + +export { + DashboardController, + AccountSettingController, + SignupController, + LoginController, + AdminDashboardController, + UpdateLogController +}; + diff --git a/assets/app/components/routes.es6 b/assets/app/components/routes.es6 new file mode 100644 index 0000000..45314d1 --- /dev/null +++ b/assets/app/components/routes.es6 @@ -0,0 +1,85 @@ +angular + .module('tracker') + .config(($urlMatcherFactoryProvider, $stateProvider, $httpProvider, $urlRouterProvider, $locationProvider, TEMPLATE_URL ,API_URL) => { + 'ngInject'; + $locationProvider.hashPrefix(''); + $urlRouterProvider.otherwise('/'); + $urlMatcherFactoryProvider.strictMode(false); + $stateProvider + .state('legacy', { + abstract : true, + url : '', + template : '' + }) + .state('login', { + url : '/', + templateUrl : TEMPLATE_URL + 'accounts/login.html', + controller : 'LoginController', + controllerAs : 'ctrl' + }) + .state('signup', { + url : '/signup/', + templateUrl : TEMPLATE_URL + 'accounts/create.html', + controller : 'SignupController', + controllerAs : 'ctrl', + role : 'anon' + }) + .state('dashboard', { + url : '/dashboard/', + templateUrl : TEMPLATE_URL + 'accounts/dashboard.html', + controller : 'DashboardController', + controllerAs : 'ctrl', + role : 'user' + }) + .state('admin', { + url : '/admin/', + templateUrl : TEMPLATE_URL + 'admin/dashboard.html', + controller : 'AdminDashboardController', + controllerAs : 'ctrl', + role : 'admin' + }) + .state('unauthorized', { + url: '/unauthorized/', + templateUrl: TEMPLATE_URL + 'unauthorized.html', + role : 'unauthorized' + }) + ; + }) + + .run(($rootScope, $q, $state, $http, $location, store, AuthService) => { + let token = store.get('token'); + + if (token) { + $http.defaults.headers.common.Authorization = 'Bearer ' + token; + } + + $rootScope.$on('$stateChangeStart', (event, next, current) => { + if (!AuthService.isAuthenticated() && current.name === 'login'){ + event.preventDefault(); + $state.go('login'); + } + }); + + $rootScope.$on('$stateChangeStart', (event, next, current, toState) => { + if (!AuthService.isAuthenticated() && next.name === 'unauthorized') { + event.preventDefault(); + $state.go('login'); + } + }); + + $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams) => { + if(toState.role !== undefined && toState.role != 'anon') { + if(store.get('account_type') !== toState.role ) { + event.preventDefault(); + $location.path('/unauthorized/'); + } + } + }); + + $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams) => { + if(AuthService.isAuthenticated() && toState.role === 'anon') { + $location.path('/unauthorized/'); + } + }); + }) +; diff --git a/assets/app/components/services.es6 b/assets/app/components/services.es6 new file mode 100644 index 0000000..e9e68b1 --- /dev/null +++ b/assets/app/components/services.es6 @@ -0,0 +1,158 @@ +class AccountService { + constructor($http, API_URL, Upload) { + this._$http = $http; + this._API_URL = API_URL; + this._Upload = Upload; + this.user = undefined; + this.loaded = false; + this.getCurrentUser(); + } + signup(form) { + return this._$http.post(this._API_URL + 'account/', form); + } + update(form) { + return this._$http.put(this._API_URL + 'account/', form).then(resp => { + return resp.data; + }); + } + playTracker(data) { + return this._$http.post(this._API_URL + 'timelog/', data); + } + getCurrentLog () { + return this._$http.get(this._API_URL + 'timelog/'); + } + updateLog (data) { + return this._$http.put(this._API_URL + 'timelog/', data); + } + getProjects () { + return this._$http.get(this._API_URL + 'projects/'); + } + getAllLogs () { + return this._$http.get(this._API_URL + 'logs/'); + } + update(form) { + return this._$http.put(this._API_URL + 'account/', form).then(resp => { + return resp.data; + }); + } + uploadPhoto(form) { + return this._Upload.upload({ + url: this._API_URL + 'photo/', + data: form, + method: 'PUT' + }); + } + getCurrentUser() { + if(this.loaded) return; + this.loaded = true; + return this._$http.get(this._API_URL + 'account/').then((result) => { + this.loaded = false; + this.user = result.data; + return result.data; + }) + } + getAllProjects () { + return this._$http.get(this._API_URL + 'project-list/'); + } + getProjectMembers () { + return this._$http.get(this._API_URL + 'members/'); + } + getProjectNoneMembers (data) { + return this._$http.post(this._API_URL + 'members/', data); + } + getAccounts () { + return this._$http.get(this._API_URL + 'accounts/'); + } + sendInvite (data) { + return this._$http.post(this._API_URL + 'invite/', data); + } +} + +class AuthService { + constructor($state, $q, $http, $window, $location, API_URL, store) { + this.$http = $http; + this.$state = $state; + this.$location = $location; + this.$window = $window; + this.$q = $q; + this.API_URL = API_URL + this.store = store; + this.loaded = false; + } + + login (form) { + return this.$http.post(this.API_URL + 'token/', form).then(resp => { + this.store.set('token',resp.data.token); + }) + .catch(error => this.$q.reject(error.data)); + } + + logout() { + return this.$http.get(this.API_URL + 'logout/').then(() => { + this.cleanCredentials(); + }) + .catch(error => this.$q.reject(error.data)); + } + + getAuthUser() { + return this.$http.get(this.API_URL + 'account/').then((result) => { + return result.data; + }) + } + + isAuthenticated () { + let credentials; + + credentials = this.getCredentials(); + return !!credentials.token; + } + + cleanCredentials () { + this.store.remove('token'); + this.store.remove('account_type'); + } + + getCredentials () { + let token; + + token = this.store.get('token'); + return { + token: token + }; + } + +} + + +class AdminService { + constructor($state, $q, $http, $window, $location, API_URL) { + this._$http = $http; + this._API_URL = API_URL; + } + + getProjects () { + return this._$http.get(this._API_URL + 'project-list/').then((result) => { + return result.data; + }); + } + + getMembers () { + return this._$http.get(this._API_URL + 'members/').then((result) => { + return result.data + }) + } + + getMemberLogs () { + return this._$http.get(this._API_URL + 'members/logs/').then((result) => { + return result.data + }) + } + + +} + +export { + AccountService, + AuthService, + AdminService, +}; \ No newline at end of file diff --git a/assets/app/templates/accounts/create.html b/assets/app/templates/accounts/create.html new file mode 100644 index 0000000..eff0ad5 --- /dev/null +++ b/assets/app/templates/accounts/create.html @@ -0,0 +1,118 @@ +
+
+
+ +
+
+
\ No newline at end of file diff --git a/assets/app/templates/accounts/dashboard.html b/assets/app/templates/accounts/dashboard.html new file mode 100644 index 0000000..859303b --- /dev/null +++ b/assets/app/templates/accounts/dashboard.html @@ -0,0 +1,451 @@ + + + + +
+
+
+
+
+
+
+
+

Design for swift tracker

+

Time started: 10:00 AM

+
+
+
+
+ +
+
+
+
+

+ + 12 + H + + + : + + 06 + M + + : + + 23 + S + +

+
+
+ +
+ + + + +
+
+
+
+
+
+
+
+
Design for swift tracker
+

10:00 AM

+
+
+
+
+ 12:06 + + +
+ + +
+
+
+
+
+
+
+
+
+
Sustainable asymmetrical kale chips roof party, etsy ethical health goth.
+

--:--

+
+
+
+ 12:06 + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Fingerstache messenger bag knausgaard
+

--:--

+
+
+
+
+ 12:06 + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Swag cardigan ler
+

10:00 AM

+
+
+
+
+ 12:06 + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Fam listicle swag, kinfolk chartreuse lo-fi shabby chic la croix 3 wolf moon vegan
+

10:00 AM

+
+
+
+
+ 12:06 + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Design for swift tracker
+

10:00 AM

+
+
+
+
+ 12:06 + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Everyday carry jianbing farm-to-table
+

10:00 AM

+
+
+
+
+ 12:06 + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
Yr humblebrag artisan literally, pork belly ugh 90's tofu try-hard meggings locavore XOXO
+

--:--

+
+
+
+
+ 12:06 + + + + + +
+ + +
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/assets/app/templates/accounts/login.html b/assets/app/templates/accounts/login.html new file mode 100644 index 0000000..c743a60 --- /dev/null +++ b/assets/app/templates/accounts/login.html @@ -0,0 +1,25 @@ +
+
+
+ +
+
+
+ diff --git a/assets/app/templates/admin/dashboard.html b/assets/app/templates/admin/dashboard.html new file mode 100644 index 0000000..c23d7ae --- /dev/null +++ b/assets/app/templates/admin/dashboard.html @@ -0,0 +1,275 @@ +