Create a equal-featured vue variant of the public portion of the site

This commit is contained in:
Kevin MacMartin 2017-11-21 23:12:31 -05:00
parent f9f3cfc5b7
commit 795e8335c2
44 changed files with 720 additions and 168 deletions

View file

@ -67,6 +67,7 @@ class RegisterController extends Controller
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'],
'password' => bcrypt($data['password']), 'password' => bcrypt($data['password']),
'api_token' => str_random(60)
]); ]);
} }
} }

View file

@ -15,7 +15,7 @@ class User extends Authenticatable
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'email', 'password', 'name', 'email', 'password', 'api_token'
]; ];
/** /**
@ -24,6 +24,6 @@ class User extends Authenticatable
* @var array * @var array
*/ */
protected $hidden = [ protected $hidden = [
'password', 'remember_token', 'password', 'remember_token', 'api_token'
]; ];
} }

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddApiTokenToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function(Blueprint $table) {
$table->string('api_token', 60)->unique();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function(Blueprint $table) {
$table->dropColumn('api_token');
});
}
}

124
gulpfile.js vendored
View file

@ -1,25 +1,39 @@
// include packages // Core packages
const gulp = require("gulp"), const gulp = require("gulp"),
gutil = require("gulp-util"), gutil = require("gulp-util"),
plumber = require("gulp-plumber"), plumber = require("gulp-plumber"),
concat = require("gulp-concat"), concat = require("gulp-concat");
sass = require("gulp-sass"),
// Sass packages
const sass = require("gulp-sass"),
sassGlob = require("gulp-sass-glob"), sassGlob = require("gulp-sass-glob"),
postCSS = require("gulp-postcss"), postCSS = require("gulp-postcss"),
autoprefixer = require("autoprefixer"), autoprefixer = require("autoprefixer");
babel = require("gulp-babel"),
// Javascript packages
const babel = require("gulp-babel"),
stripDebug = require("gulp-strip-debug"), stripDebug = require("gulp-strip-debug"),
uglify = require("gulp-uglify"); uglify = require("gulp-uglify");
// determine if gulp has been run with --production // Vue packages
const prod = gutil.env.production; const browserify = require("browserify"),
vueify = require("vueify"),
source = require("vinyl-source-stream"),
buffer = require("vinyl-buffer");
// declare plugin settings // Determine if gulp has been run with --production
const sassOutputStyle = prod ? "compressed" : "nested", const isProduction = gutil.env.production;
sassIncludePaths = [ "bower_components" ],
autoprefixerSettings = { remove: false, cascade: false, browsers: [ "last 6 versions" ] };
// javascript files for the public site // Declare plugin settings
const sassOutputStyle = isProduction ? "compressed" : "nested",
sassPaths = [ "bower_components", "node_modules" ],
autoprefixerSettings = { remove: false, cascade: false, browsers: [ "last 6 versions" ] },
vuePaths = [ "./bower_components", "./node_modules", "./resources/components", "./resources/assets/js" ];
// Vue file for the public site
const vuePublic = "resources/assets/js/app-vue.js";
// Javascript files for the public site
const jsPublic = [ const jsPublic = [
"resources/assets/js/site-vars.js", "resources/assets/js/site-vars.js",
"resources/assets/js/contact.js", "resources/assets/js/contact.js",
@ -27,7 +41,7 @@ const jsPublic = [
"resources/assets/js/app.js" "resources/assets/js/app.js"
]; ];
// javascript libraries for the public site // Javascript libraries for the public site
const jsPublicLibs = [ const jsPublicLibs = [
"bower_components/jquery/dist/jquery.js", "bower_components/jquery/dist/jquery.js",
"bower_components/bootstrap-sass/assets/javascripts/bootstrap.js", "bower_components/bootstrap-sass/assets/javascripts/bootstrap.js",
@ -35,12 +49,12 @@ const jsPublicLibs = [
"node_modules/what-input/dist/what-input.js" "node_modules/what-input/dist/what-input.js"
]; ];
// javascript files for the dashboard // Javascript files for the dashboard
const jsDashboard = [ const jsDashboard = [
"resources/assets/js/dashboard.js" "resources/assets/js/dashboard.js"
]; ];
// javascript libraries for the dashboard // Javascript libraries for the dashboard
const jsDashboardLibs = [ const jsDashboardLibs = [
"bower_components/jquery/dist/jquery.js", "bower_components/jquery/dist/jquery.js",
"bower_components/bootstrap-sass/assets/javascripts/bootstrap.js", "bower_components/bootstrap-sass/assets/javascripts/bootstrap.js",
@ -50,102 +64,128 @@ const jsDashboardLibs = [
"bower_components/simplemde/dist/simplemde.min.js" "bower_components/simplemde/dist/simplemde.min.js"
]; ];
// paths to folders containing fonts that should be copied to public/fonts/ // Paths to folders containing fonts that should be copied to public/fonts/
const fontPaths = [ const fontPaths = [
"resources/assets/fonts/**", "resources/assets/fonts/**",
"bower_components/bootstrap-sass/assets/fonts/**/*", "bower_components/bootstrap-sass/assets/fonts/**/*",
"bower_components/fontawesome/fonts/**" "bower_components/fontawesome/fonts/**"
]; ];
// function to handle gulp-plumber errors // Handle errors
function plumberError(err) { function handleError(err) {
console.log(err); gutil.log(err);
this.emit("end"); this.emit("end");
} }
// function to handle the processing of sass files // Process sass
function processSass(filename) { function processSass(filename) {
return gulp.src("resources/assets/sass/" + filename + ".scss") return gulp.src("resources/assets/sass/" + filename + ".scss")
.pipe(plumber(plumberError)) .pipe(plumber(handleError))
.pipe(sassGlob()) .pipe(sassGlob())
.pipe(sass({ outputStyle: sassOutputStyle, includePaths: sassIncludePaths })) .pipe(sass({ outputStyle: sassOutputStyle, includePaths: sassPaths }))
.pipe(postCSS([ autoprefixer(autoprefixerSettings) ])) .pipe(postCSS([ autoprefixer(autoprefixerSettings) ]))
.pipe(concat(filename + ".css")) .pipe(concat(filename + ".css"))
.pipe(gulp.dest("public/css/")); .pipe(gulp.dest("public/css/"));
} }
// function to handle the processing of javascript files // Process vue
function processJavaScript(ouputFilename, inputFiles, es6) { function processVue(ouputFilename, inputFile) {
const javascript = gulp.src(inputFiles) const javascript = browserify({
.pipe(plumber(plumberError)) entries: [ inputFile ],
.pipe(concat(ouputFilename + ".js")); paths: vuePaths
}).transform("babelify")
.transform(vueify)
.bundle()
.on("error", handleError)
.pipe(source(ouputFilename + ".js"))
.pipe(buffer());
if (es6) { javascript.pipe(babel()); } if (isProduction) { javascript.pipe(stripDebug()).pipe(uglify().on("error", handleError)); }
if (prod) { javascript.pipe(stripDebug()).pipe(uglify()); }
return javascript.pipe(gulp.dest("public/js/")); return javascript.pipe(gulp.dest("public/js/"));
} }
// gulp task for public styles // Process javascript
function processJavaScript(ouputFilename, inputFiles, es6) {
const javascript = gulp.src(inputFiles)
.pipe(plumber(handleError))
.pipe(concat(ouputFilename + ".js"));
if (es6) { javascript.pipe(babel()); }
if (isProduction) { javascript.pipe(stripDebug()).pipe(uglify()); }
return javascript.pipe(gulp.dest("public/js/"));
}
// Task for public styles
gulp.task("sass-public", function() { gulp.task("sass-public", function() {
return processSass("app"); return processSass("app");
}); });
// gulp task for dashboard styles // Task for dashboard styles
gulp.task("sass-dashboard", function() { gulp.task("sass-dashboard", function() {
return processSass("dashboard"); return processSass("dashboard");
}); });
// gulp task for public javascript // Task for public vue
gulp.task("js-public-vue", function() {
return processVue("app-vue", vuePublic);
});
// Task for public javascript
gulp.task("js-public", function() { gulp.task("js-public", function() {
return processJavaScript("app", jsPublic, true); return processJavaScript("app", jsPublic, true);
}); });
// gulp task for public javascript libraries // Task for public javascript libraries
gulp.task("js-public-libs", function() { gulp.task("js-public-libs", function() {
return processJavaScript("lib", jsPublicLibs, false); return processJavaScript("lib", jsPublicLibs, false);
}); });
// gulp task for dashboard javascript // Task for dashboard javascript
gulp.task("js-dashboard", function() { gulp.task("js-dashboard", function() {
return processJavaScript("dashboard", jsDashboard, true); return processJavaScript("dashboard", jsDashboard, true);
}); });
// gulp task for dashboard javascript libraries // Task for dashboard javascript libraries
gulp.task("js-dashboard-libs", function() { gulp.task("js-dashboard-libs", function() {
return processJavaScript("lib-dashboard", jsDashboardLibs, false); return processJavaScript("lib-dashboard", jsDashboardLibs, false);
}); });
// gulp task to copy fonts // Task to copy fonts
gulp.task("fonts", function() { gulp.task("fonts", function() {
return gulp.src(fontPaths) return gulp.src(fontPaths)
.pipe(plumber(plumberError)) .pipe(plumber(handleError))
.pipe(gulp.dest("public/fonts/")); .pipe(gulp.dest("public/fonts/"));
}); });
// gulp watch task // Task to run tasks when their respective files are changed
gulp.task("watch", function() { gulp.task("watch", function() {
const gLiveReload = require("gulp-livereload"); const livereload = require("gulp-livereload");
const liveReloadUpdate = function(files, wait) { const liveReloadUpdate = function(files, wait) {
setTimeout(function() { setTimeout(function() {
gLiveReload.changed(files); livereload.changed(files);
}, wait || 1); }, wait || 1);
}; };
gLiveReload.listen(); livereload.listen();
gulp.watch(jsPublic, [ "js-public" ]).on("change", liveReloadUpdate); gulp.watch(jsPublic, [ "js-public" ]).on("change", liveReloadUpdate);
gulp.watch(jsDashboard, [ "js-dashboard" ]).on("change", liveReloadUpdate); gulp.watch(jsDashboard, [ "js-dashboard" ]).on("change", liveReloadUpdate);
gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ]).on("change", liveReloadUpdate); gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ]).on("change", liveReloadUpdate);
gulp.watch([ vuePublic, "resources/assets/js/mixins/**/*.js", "resources/components/**/*.vue" ], [ "js-public-vue" ]).on("change", function(files) {
liveReloadUpdate(files, 3000);
});
gulp.watch("resources/assets/sass/**/*.scss", [ "sass-public", "sass-dashboard" ]).on("change", function(files) { gulp.watch("resources/assets/sass/**/*.scss", [ "sass-public", "sass-dashboard" ]).on("change", function(files) {
liveReloadUpdate(files, 1000); liveReloadUpdate(files, 1000);
}); });
}); });
// gulp default task // Task to run non-development tasks
gulp.task("default", [ gulp.task("default", [
"sass-public", "sass-public",
"sass-dashboard", "sass-dashboard",
"js-public-vue",
"js-public", "js-public",
"js-public-libs", "js-public-libs",
"js-dashboard", "js-dashboard",

View file

@ -10,7 +10,11 @@
"dependencies": { "dependencies": {
"autoprefixer": "^7.1.6", "autoprefixer": "^7.1.6",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babelify": "^8.0.0",
"browserify": "^14.5.0",
"es6-promise": "^4.1.1",
"gsap": "^1.20.3", "gsap": "^1.20.3",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-babel": "^7.0.0", "gulp-babel": "^7.0.0",
@ -22,6 +26,15 @@
"gulp-strip-debug": "^1.1.0", "gulp-strip-debug": "^1.1.0",
"gulp-uglify": "^3.0.0", "gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.8", "gulp-util": "^3.0.8",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.5.8",
"vue-resource": "^1.3.4",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.8",
"vueify": "^9.4.1",
"vuex": "^3.0.1",
"vuex-router-sync": "^5.0.0",
"what-input": "^5.0.3" "what-input": "^5.0.3"
} }
} }

120
resources/assets/js/app-vue.js vendored Normal file
View file

@ -0,0 +1,120 @@
// Determine whether to use vue.js in debug or production mode
const Vue = env.debug ? require("vue/dist/vue.js") : require("vue/dist/vue.min.js");
// Import plugins
import VueRouter from "vue-router";
import VueResource from "vue-resource";
import Vuex from "vuex";
import { sync } from "vuex-router-sync";
// Load plugins
Vue.use(VueRouter);
Vue.use(VueResource);
Vue.use(Vuex);
// CSRF prevention header
Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken;
// Import page components
import HomePage from "pages/home.vue";
import ContactPage from "pages/contact.vue";
import Error404Page from "pages/error404.vue";
// Import section components
import NavSection from "sections/nav.vue";
import FooterSection from "sections/footer.vue";
// Name the nav and footer components so they can be used globally
Vue.component("nav-component", NavSection);
Vue.component("footer-component", FooterSection);
// Create a router instance
const router = new VueRouter({
mode: "history",
linkActiveClass: "active",
root: "/",
routes: [
{ path: "/", component: HomePage },
{ path: "/contact", component: ContactPage },
{ path: "/*", component: Error404Page }
]
});
// Create a vuex store instance
const store = new Vuex.Store({
state: {
appName: env.appName,
firstLoad: true,
lastPath: ""
},
getters: {
getAppName: state => {
return state.appName;
},
getFirstLoad: state => {
return state.firstLoad;
},
getLastPath: state => {
return state.lastPath;
}
},
mutations: {
setFirstLoad(state, value) {
state.firstLoad = value;
},
setLastPath(state, value) {
state.lastPath = value;
}
},
actions: {
}
});
// Sync vue-router-sync with vuex store
sync(store, router);
// Functionality to run before page load and change
router.beforeEach((to, from, next) => {
if (to.path !== store.getters.getLastPath) {
if (store.getters.getFirstLoad) {
next();
} else {
// Fade the page out and scroll when moving from one page to another
TweenMax.to("#router-view", 0.25, {
opacity: 0,
onComplete: () => {
$("html, body").scrollTop(0);
next();
}
});
}
}
});
// Functionality to run on page load and change
router.afterEach((to, from) => {
if (to.path !== store.getters.getLastPath) {
store.commit("setLastPath", to.path);
if (store.getters.getFirstLoad) {
// Set Page.firstLoad to false so we know the initial load has completed
store.commit("setFirstLoad", false);
} else {
Vue.nextTick(() => {
TweenMax.to("#router-view", 0.25, { opacity: 1 });
});
}
}
});
const App = new Vue({
router,
store
}).$mount("#page-content");

78
resources/assets/js/mixins/base-page.js vendored Normal file
View file

@ -0,0 +1,78 @@
export default {
data() {
return {
metaTitle: "",
metaDescription: "",
metaKeywords: "",
metaTags: {
"title": [ "name", "title" ],
"description": [ "name", "description" ],
"keywords": [ "name", "keywords" ],
"dc:title": [ "name", "title" ],
"dc:description": [ "name", "description" ],
"og:title": [ "property", "title" ],
"og:description": [ "property", "description" ],
"og:url": [ "property", "url" ],
"twitter:title": [ "name", "title" ],
"twitter:description": [ "name", "description" ]
}
};
},
computed: {
pageTitle() {
return this.metaTitle === "" ? env.appName : `${this.metaTitle} | ${env.appName}`;
},
pageDescription() {
return this.metaDescription === "" ? env.appDesc : this.metaDescription;
},
fullPath() {
return document.location.origin + this.$route.path;
}
},
methods: {
updateMetaTag(name, attribute, content) {
const $tag = $("meta[" + name + "=" + attribute.replace(/:/, "\\:") + "]");
if ($tag.length) {
$tag.attr("content", content);
}
},
updateMetaData() {
let metaContent;
document.title = this.pageTitle;
$("link[rel=canonical]").attr("href", this.fullPath);
Object.keys(this.metaTags).forEach((name) => {
switch (this.metaTags[name][1]) {
case "title":
metaContent = this.pageTitle;
break;
case "description":
metaContent = this.pageDescription;
break;
case "keywords":
metaContent = this.metaKeywords;
break;
case "url":
metaContent = this.fullPath;
break;
default:
metaContent = "";
}
this.updateMetaTag(this.metaTags[name][0], name, metaContent);
});
}
},
created() {
this.updateMetaData();
}
};

View file

@ -6,7 +6,7 @@
@import "bootstrap-sass/assets/stylesheets/_bootstrap.scss"; @import "bootstrap-sass/assets/stylesheets/_bootstrap.scss";
// Supplementary // Supplementary
@import "elements/**/*.scss"; @import "sections/**/*.scss";
@import "pages/**/*.scss"; @import "pages/**/*.scss";
// //
@ -38,13 +38,13 @@ body {
@media (max-width: $grid-float-breakpoint-max) { padding-top: $nav-height-mobile; } @media (max-width: $grid-float-breakpoint-max) { padding-top: $nav-height-mobile; }
} }
#page-container { .page-container {
display: flex; display: flex;
min-height: 100vh; min-height: 100vh;
padding-top: $nav-height; padding-top: $nav-height;
flex-direction: column; flex-direction: column;
#main-content { .main-content {
flex-grow: 1; flex-grow: 1;
} }
} }

View file

@ -1,5 +1,4 @@
.page-contact { .contact-page-component {
#contact-form {
$trans-speed: 100ms; $trans-speed: 100ms;
margin-top: 35px; margin-top: 35px;
margin-bottom: 20px; margin-bottom: 20px;
@ -50,4 +49,3 @@
} }
} }
} }
}

View file

@ -1,14 +1,8 @@
footer { .footer-section-component {
width: 100%; width: 100%;
height: 32px; height: 32px;
padding-left: 10px; padding-left: 10px;
background-color: $c-base; background-color: $c-base;
color: $c-text-light; color: $c-text-light;
line-height: 32px; line-height: 32px;
&.sticky-footer {
position: absolute;
bottom: 0px;
width: 100%;
}
} }

View file

@ -1,4 +1,4 @@
.navbar { .nav-section-component {
z-index: 1; z-index: 1;
margin-bottom: 0px; margin-bottom: 0px;
height: $nav-height; height: $nav-height;

View file

@ -1,4 +1,4 @@
#subscription-form { .subscription-form-section-component {
$trans-speed: 100ms; $trans-speed: 100ms;
position: absolute; position: absolute;
top: 50%; top: 50%;

View file

@ -0,0 +1,100 @@
<template>
<div class="contact-page-component">
<div class="container">
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<h1>Contact</h1>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
<div id="contact-form">
<form action="#" method="POST" accept-charset="UTF-8" @submit.prevent="submit">
<input type="text" v-model="form.name" name="name" placeholder="Name" />
<input type="text" v-model="form.email" name="email" placeholder="Email" />
<textarea name="message" v-model="form.message" placeholder="Message"></textarea>
<input
class="submit"
:class="{ disabled: submitSuccess }"
type="submit"
name="submit"
value="Submit"
/>
</form>
<div
class="notification"
:class="{ success: submitSuccess, visible: errorCount > 0 && submitSuccess }">
<template v-if="submitSuccess">
Thanks for your message!
</template>
<template v-else>
<strong>Error:</strong> There were problems with the <span>{{ errorCount }}</span> fields highlighted above
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BasePageMixin from "mixins/base-page.js";
export default {
mixins: [
BasePageMixin
],
data() {
return {
metaTitle: "Contact",
metaDescription: "Contact Us",
metaKeywords: "contact",
submitting: false,
errorCount: 0,
submitSuccess: false,
form: {
name: "",
email: "",
message: ""
}
};
},
methods: {
submit() {
if (!this.submitting) {
this.submitting = true;
$(this.$el).find(":input.error").removeClass("error");
this.$http.post("/api/contact-submit" + env.apiToken, JSON.stringify(this.form)).then((response) => {
// Success
$(this.$el).find(":input").attr("disabled", true);
this.submitSuccess = true;
this.submitting = false;
}, (response) => {
// Error
let errors = 0;
for (let errorName in response.body.errors) {
if ($(this.$el).find(`[name='${errorName}']`).length) {
$(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++;
}
}
this.errorCount = errors;
this.submitting = false;
});
}
}
}
};
</script>

View file

@ -0,0 +1,22 @@
<template>
<div class="error-page-component">
Page Not Found
</div>
</template>
<script>
import BasePageMixin from "mixins/base-page.js";
export default {
mixins: [
BasePageMixin
],
data() {
return {
metaTitle: "Page Not Found",
metaDescription: "The requested page cannot be found"
};
}
};
</script>

View file

@ -0,0 +1,26 @@
<template>
<div class="home-page-component">
<subscription-form />
</div>
</template>
<script>
import BasePageMixin from "mixins/base-page.js";
import SubscriptionFormSection from "sections/subscription-form.vue";
export default {
mixins: [
BasePageMixin
],
components: {
"subscription-form": SubscriptionFormSection
},
data() {
return {
metaKeywords: "home"
};
}
};
</script>

View file

@ -0,0 +1,5 @@
<template>
<footer class="footer-section-component">
&copy; {{ new Date().getFullYear() }} {{ $store.getters.getAppName }}
</footer>
</template>

View file

@ -0,0 +1,23 @@
<template>
<nav class="nav-section-component navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" id="navbar-toggle" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<router-link class="navbar-logo" to="/"></router-link>
</div>
<div id="navbar-collapse" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li class="navlink"><router-link to="/" title="Home">Home</router-link></li>
<li class="navlink"><router-link to="/contact" title="Contact">Contact</router-link></li>
</ul>
</div>
</div>
</nav>
</template>

View file

@ -0,0 +1,62 @@
<template>
<div class="subscription-form-section-component">
<form action="#" method="POST" accept-charset="UTF-8" @submit.prevent="submit">
<div class="notification" :class="[ notifyStatus, { visible: notifyStatus !== '' } ]">{{ notifyText }}</div>
<input type="text" v-model="form.email" name="email" placeholder="Email" />
<input type="text" v-model="form.name" name="name" placeholder="Name" />
<input type="submit" name="submit" value="Subscribe" />
</form>
</div>
</template>
<script>
export default {
data() {
return {
submitting: false,
notifyStatus: "",
notifyText: "",
form: {
email: "",
name: ""
}
};
},
methods: {
submit() {
if (!this.submitting) {
this.submitting = true;
this.notifyStatus = "";
$(this.$el).find(":input.error").removeClass("error");
this.$http.post("/api/subscription-submit" + env.apiToken, JSON.stringify(this.form)).then((response) => {
// Success
$(this.$el).find(":input").fadeOut(150);
this.notifyText = "Thanks for subscribing!";
this.notifyStatus = "success";
this.submitting = false;
}, (response) => {
// Error
let errors = 0;
for (let errorName in response.body.errors) {
if ($(this.$el).find(`[name='${errorName}']`).length) {
$(this.$el).find(`[name='${errorName}']`).addClass("error");
errors++;
}
}
if (errors === 0) {
this.notifyText = "An error occurred. Are you already subscribed?";
this.notifyStatus = "error";
}
this.submitting = false;
});
}
}
}
};
</script>

View file

@ -1,6 +1,6 @@
@extends('layouts.dashboard') @extends('templates.dashboard')
@section('content') @section('page-content')
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">

View file

@ -1,6 +1,6 @@
@extends('layouts.dashboard') @extends('templates.dashboard')
@section('content') @section('page-content')
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">

View file

@ -1,6 +1,6 @@
@extends('layouts.dashboard') @extends('templates.dashboard')
@section('content') @section('page-content')
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">

View file

@ -1,6 +1,6 @@
@extends('layouts.dashboard') @extends('templates.dashboard')
@section('content') @section('page-content')
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">

View file

@ -1,6 +1,6 @@
@extends('layouts.dashboard') @extends('templates.dashboard')
@section('content') @section('page-content')
<div class="container spark-screen"> <div class="container spark-screen">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">

View file

@ -2,5 +2,5 @@
@section('dashboard-body') @section('dashboard-body')
@set('menu_class', 'list-group-item') @set('menu_class', 'list-group-item')
<ul class="list-group linked-list">@include('dashboard.elements.menu')</ul> <ul class="list-group linked-list">@include('dashboard.sections.menu')</ul>
@endsection @endsection

View file

@ -23,7 +23,7 @@
@endif @endif
@else @else
@set('menu_class', 'nav-item') @set('menu_class', 'nav-item')
@include('dashboard.elements.menu') @include('dashboard.sections.menu')
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">

View file

@ -1 +0,0 @@
<footer>&copy; {{ date('Y') }} {{ env('APP_NAME') }}</footer>

View file

@ -1,7 +0,0 @@
<form id="subscription-form" action="#" method="POST" accept-charset="UTF-8">
<div class="notification"></div>
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
<input type="text" name="email" placeholder="Email" />
<input type="text" name="name" placeholder="Name" />
<input type="submit" name="submit" value="Subscribe" />
</form>

View file

@ -0,0 +1,9 @@
<script type="text/javascript">
var env = {
appName: "{{ env('APP_NAME') }}",
appDesc: "{{ env('APP_DESC') }}",
apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}",
csrfToken: "{{ csrf_token() }}",
debug: {{ Config::get('app.debug') ? 'true' : 'false' }}
};
</script>

View file

@ -1,4 +1,4 @@
@extends('layouts.error') @extends('templates.error')
@section('error-title') @section('error-title')
Page Not Found Page Not Found

View file

@ -1,4 +1,4 @@
@extends('layouts.error') @extends('templates.error')
@section('error-title') @section('error-title')
Be Right Back Be Right Back

View file

@ -1,4 +1,4 @@
@extends('layouts.error') @extends('templates.error')
@section('error-title') @section('error-title')
No Such Record No Such Record

View file

@ -1,6 +1,7 @@
@extends('layouts.public', [ 'title' => 'Contact' ]) @extends('templates.public', [ 'title' => 'Contact' ])
@section('content') @section('content')
<div class="contact-page-component">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2"> <div class="col-xs-12 col-md-8 col-md-offset-2">
@ -10,7 +11,6 @@
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2"> <div class="col-xs-12 col-md-8 col-md-offset-2">
<div id="contact-form">
<form action="#" method="POST" accept-charset="UTF-8"> <form action="#" method="POST" accept-charset="UTF-8">
<input type="hidden" name="_token" value="{{ csrf_token() }}" /> <input type="hidden" name="_token" value="{{ csrf_token() }}" />
<input type="text" name="name" placeholder="Name" /> <input type="text" name="name" placeholder="Name" />

View file

@ -0,0 +1,7 @@
@extends('templates.public')
@section('content')
<div class="home-page-component">
@include('sections.subscription-form')
</div>
@endsection

View file

@ -0,0 +1,3 @@
<footer class="footer-section-component">
&copy; {{ date('Y') }} {{ env('APP_NAME') }}
</footer>

View file

@ -1,4 +1,4 @@
<nav class="navbar navbar-default navbar-fixed-top"> <nav class="nav-section-component navbar navbar-default navbar-fixed-top">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" id="navbar-toggle" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false" aria-controls="navbar"> <button type="button" id="navbar-toggle" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false" aria-controls="navbar">

View file

@ -0,0 +1,9 @@
<div class="subscription-form-section-component">
<form id="subscription-form" action="#" method="POST" accept-charset="UTF-8">
<div class="notification"></div>
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
<input type="text" name="email" placeholder="Email" />
<input type="text" name="name" placeholder="Name" />
<input type="submit" name="submit" value="Subscribe" />
</form>
</div>

View file

@ -41,21 +41,13 @@
@endif @endif
</head> </head>
@if(preg_match('/^dashboard/', Request::path())) <body class="{{ $device_mobile ? 'mobile-browser' : 'desktop-browser' }}">
@set('body_class', preg_replace([ '/\/(new|[0-9][0-9]*)$/', '/\//' ], [ '', '-' ], Request::path()))
@else
@set('body_class', Request::path() == '/' ? 'index' : preg_replace('/\/.*/', '', Request::path()))
@endif
<body class="page-{{ $body_class }} {{ $device_mobile ? 'mobile-browser' : 'desktop-browser' }}">
@yield('page-top') @yield('page-top')
<div id="page-container"> <div id="page-content">
<div id="main-content"> @yield('page-content')
@yield('content')
</div> </div>
@yield('page-bottom') @yield('page-bottom')
</div>
</body> </body>
</html> </html>

View file

@ -1,4 +1,4 @@
@extends('layouts.base', [ 'title' => 'Dashboard' ]) @extends('templates.base', [ 'title' => 'Dashboard' ])
@section('page-includes') @section('page-includes')
<script src="/js/lib-dashboard.js?version={{ env('CACHE_BUST') }}"></script> <script src="/js/lib-dashboard.js?version={{ env('CACHE_BUST') }}"></script>
@ -7,5 +7,5 @@
@endsection @endsection
@section('page-top') @section('page-top')
@include('dashboard.elements.nav') @include('dashboard.sections.nav')
@endsection @endsection

View file

@ -0,0 +1,23 @@
@extends('templates.base')
@section('page-includes')
<script src="/js/lib.js?version={{ env('CACHE_BUST') }}"></script>
<link rel="stylesheet" href="/css/app.css?version={{ env('CACHE_BUST') }}" />
@include('elements.variables')
@endsection
@section('page-content')
<nav-component></nav-component>
<div class="page-container">
<div id="router-view" class="main-content">
<router-view></router-view>
</div>
<footer-component></footer-component>
</div>
@endsection
@section('page-bottom')
<script src="/js/app-vue.js?version={{ env('CACHE_BUST') }}"></script>
@endsection

View file

@ -1,4 +1,4 @@
@extends('layouts.base') @extends('templates.base')
@section('page-includes') @section('page-includes')
<script src="/js/lib.js?version={{ env('CACHE_BUST') }}"></script> <script src="/js/lib.js?version={{ env('CACHE_BUST') }}"></script>
@ -6,10 +6,14 @@
<link rel="stylesheet" href="/css/app.css?version={{ env('CACHE_BUST') }}" /> <link rel="stylesheet" href="/css/app.css?version={{ env('CACHE_BUST') }}" />
@endsection @endsection
@section('page-top') @section('page-content')
@include('elements.nav') @include('sections.nav')
@endsection
@section('page-bottom') <div class="page-container">
@include('elements.footer') <div class="main-content">
@yield('content')
</div>
@include('sections.footer')
</div>
@endsection @endsection

View file

@ -1,5 +0,0 @@
@extends('layouts.public', [ 'title' => 'Home' ])
@section('content')
@include('elements.subscription-form')
@endsection

View file

@ -19,19 +19,23 @@ use App\Utilities\Language;
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
Route::get('/', function() {
return view('website.index');
});
Route::get('/contact', function() {
return view('website.contact');
});
Route::get('/language/{lang}', function($lang) { Route::get('/language/{lang}', function($lang) {
Language::setSessionLanguage($lang); Language::setSessionLanguage($lang);
return redirect()->back(); return redirect()->back();
}); });
// Route::get('/', function() {
// return view('pages.index');
// });
//
// Route::get('/contact', function() {
// return view('pages.contact');
// });
Route::get('/{vue?}', function() {
return view('templates.public-vue');
})->where('vue', '[\/\w\.-]*');
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Routes | Authentication Routes