mirror of
https://github.com/prurigro/hypothetical.git
synced 2024-11-09 11:16:39 -05:00
Create a equal-featured vue variant of the public portion of the site
This commit is contained in:
parent
f9f3cfc5b7
commit
795e8335c2
44 changed files with 720 additions and 168 deletions
|
@ -67,6 +67,7 @@ class RegisterController extends Controller
|
|||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => bcrypt($data['password']),
|
||||
'api_token' => str_random(60)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class User extends Authenticatable
|
|||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'email', 'password',
|
||||
'name', 'email', 'password', 'api_token'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,6 @@ class User extends Authenticatable
|
|||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token',
|
||||
'password', 'remember_token', 'api_token'
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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
124
gulpfile.js
vendored
|
@ -1,25 +1,39 @@
|
|||
// include packages
|
||||
// Core packages
|
||||
const gulp = require("gulp"),
|
||||
gutil = require("gulp-util"),
|
||||
plumber = require("gulp-plumber"),
|
||||
concat = require("gulp-concat"),
|
||||
sass = require("gulp-sass"),
|
||||
concat = require("gulp-concat");
|
||||
|
||||
// Sass packages
|
||||
const sass = require("gulp-sass"),
|
||||
sassGlob = require("gulp-sass-glob"),
|
||||
postCSS = require("gulp-postcss"),
|
||||
autoprefixer = require("autoprefixer"),
|
||||
babel = require("gulp-babel"),
|
||||
autoprefixer = require("autoprefixer");
|
||||
|
||||
// Javascript packages
|
||||
const babel = require("gulp-babel"),
|
||||
stripDebug = require("gulp-strip-debug"),
|
||||
uglify = require("gulp-uglify");
|
||||
|
||||
// determine if gulp has been run with --production
|
||||
const prod = gutil.env.production;
|
||||
// Vue packages
|
||||
const browserify = require("browserify"),
|
||||
vueify = require("vueify"),
|
||||
source = require("vinyl-source-stream"),
|
||||
buffer = require("vinyl-buffer");
|
||||
|
||||
// declare plugin settings
|
||||
const sassOutputStyle = prod ? "compressed" : "nested",
|
||||
sassIncludePaths = [ "bower_components" ],
|
||||
autoprefixerSettings = { remove: false, cascade: false, browsers: [ "last 6 versions" ] };
|
||||
// Determine if gulp has been run with --production
|
||||
const isProduction = gutil.env.production;
|
||||
|
||||
// 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 = [
|
||||
"resources/assets/js/site-vars.js",
|
||||
"resources/assets/js/contact.js",
|
||||
|
@ -27,7 +41,7 @@ const jsPublic = [
|
|||
"resources/assets/js/app.js"
|
||||
];
|
||||
|
||||
// javascript libraries for the public site
|
||||
// Javascript libraries for the public site
|
||||
const jsPublicLibs = [
|
||||
"bower_components/jquery/dist/jquery.js",
|
||||
"bower_components/bootstrap-sass/assets/javascripts/bootstrap.js",
|
||||
|
@ -35,12 +49,12 @@ const jsPublicLibs = [
|
|||
"node_modules/what-input/dist/what-input.js"
|
||||
];
|
||||
|
||||
// javascript files for the dashboard
|
||||
// Javascript files for the dashboard
|
||||
const jsDashboard = [
|
||||
"resources/assets/js/dashboard.js"
|
||||
];
|
||||
|
||||
// javascript libraries for the dashboard
|
||||
// Javascript libraries for the dashboard
|
||||
const jsDashboardLibs = [
|
||||
"bower_components/jquery/dist/jquery.js",
|
||||
"bower_components/bootstrap-sass/assets/javascripts/bootstrap.js",
|
||||
|
@ -50,102 +64,128 @@ const jsDashboardLibs = [
|
|||
"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 = [
|
||||
"resources/assets/fonts/**",
|
||||
"bower_components/bootstrap-sass/assets/fonts/**/*",
|
||||
"bower_components/fontawesome/fonts/**"
|
||||
];
|
||||
|
||||
// function to handle gulp-plumber errors
|
||||
function plumberError(err) {
|
||||
console.log(err);
|
||||
// Handle errors
|
||||
function handleError(err) {
|
||||
gutil.log(err);
|
||||
this.emit("end");
|
||||
}
|
||||
|
||||
// function to handle the processing of sass files
|
||||
// Process sass
|
||||
function processSass(filename) {
|
||||
return gulp.src("resources/assets/sass/" + filename + ".scss")
|
||||
.pipe(plumber(plumberError))
|
||||
.pipe(plumber(handleError))
|
||||
.pipe(sassGlob())
|
||||
.pipe(sass({ outputStyle: sassOutputStyle, includePaths: sassIncludePaths }))
|
||||
.pipe(sass({ outputStyle: sassOutputStyle, includePaths: sassPaths }))
|
||||
.pipe(postCSS([ autoprefixer(autoprefixerSettings) ]))
|
||||
.pipe(concat(filename + ".css"))
|
||||
.pipe(gulp.dest("public/css/"));
|
||||
}
|
||||
|
||||
// function to handle the processing of javascript files
|
||||
function processJavaScript(ouputFilename, inputFiles, es6) {
|
||||
const javascript = gulp.src(inputFiles)
|
||||
.pipe(plumber(plumberError))
|
||||
.pipe(concat(ouputFilename + ".js"));
|
||||
// Process vue
|
||||
function processVue(ouputFilename, inputFile) {
|
||||
const javascript = browserify({
|
||||
entries: [ inputFile ],
|
||||
paths: vuePaths
|
||||
}).transform("babelify")
|
||||
.transform(vueify)
|
||||
.bundle()
|
||||
.on("error", handleError)
|
||||
.pipe(source(ouputFilename + ".js"))
|
||||
.pipe(buffer());
|
||||
|
||||
if (es6) { javascript.pipe(babel()); }
|
||||
if (prod) { javascript.pipe(stripDebug()).pipe(uglify()); }
|
||||
if (isProduction) { javascript.pipe(stripDebug()).pipe(uglify().on("error", handleError)); }
|
||||
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() {
|
||||
return processSass("app");
|
||||
});
|
||||
|
||||
// gulp task for dashboard styles
|
||||
// Task for dashboard styles
|
||||
gulp.task("sass-dashboard", function() {
|
||||
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() {
|
||||
return processJavaScript("app", jsPublic, true);
|
||||
});
|
||||
|
||||
// gulp task for public javascript libraries
|
||||
// Task for public javascript libraries
|
||||
gulp.task("js-public-libs", function() {
|
||||
return processJavaScript("lib", jsPublicLibs, false);
|
||||
});
|
||||
|
||||
// gulp task for dashboard javascript
|
||||
// Task for dashboard javascript
|
||||
gulp.task("js-dashboard", function() {
|
||||
return processJavaScript("dashboard", jsDashboard, true);
|
||||
});
|
||||
|
||||
// gulp task for dashboard javascript libraries
|
||||
// Task for dashboard javascript libraries
|
||||
gulp.task("js-dashboard-libs", function() {
|
||||
return processJavaScript("lib-dashboard", jsDashboardLibs, false);
|
||||
});
|
||||
|
||||
// gulp task to copy fonts
|
||||
// Task to copy fonts
|
||||
gulp.task("fonts", function() {
|
||||
return gulp.src(fontPaths)
|
||||
.pipe(plumber(plumberError))
|
||||
.pipe(plumber(handleError))
|
||||
.pipe(gulp.dest("public/fonts/"));
|
||||
});
|
||||
|
||||
// gulp watch task
|
||||
// Task to run tasks when their respective files are changed
|
||||
gulp.task("watch", function() {
|
||||
const gLiveReload = require("gulp-livereload");
|
||||
const livereload = require("gulp-livereload");
|
||||
|
||||
const liveReloadUpdate = function(files, wait) {
|
||||
setTimeout(function() {
|
||||
gLiveReload.changed(files);
|
||||
livereload.changed(files);
|
||||
}, wait || 1);
|
||||
};
|
||||
|
||||
gLiveReload.listen();
|
||||
livereload.listen();
|
||||
gulp.watch(jsPublic, [ "js-public" ]).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([ 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) {
|
||||
liveReloadUpdate(files, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
// gulp default task
|
||||
// Task to run non-development tasks
|
||||
gulp.task("default", [
|
||||
"sass-public",
|
||||
"sass-dashboard",
|
||||
"js-public-vue",
|
||||
"js-public",
|
||||
"js-public-libs",
|
||||
"js-dashboard",
|
||||
|
|
13
package.json
13
package.json
|
@ -10,7 +10,11 @@
|
|||
"dependencies": {
|
||||
"autoprefixer": "^7.1.6",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babelify": "^8.0.0",
|
||||
"browserify": "^14.5.0",
|
||||
"es6-promise": "^4.1.1",
|
||||
"gsap": "^1.20.3",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^7.0.0",
|
||||
|
@ -22,6 +26,15 @@
|
|||
"gulp-strip-debug": "^1.1.0",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
120
resources/assets/js/app-vue.js
vendored
Normal file
120
resources/assets/js/app-vue.js
vendored
Normal 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
78
resources/assets/js/mixins/base-page.js
vendored
Normal 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();
|
||||
}
|
||||
};
|
6
resources/assets/sass/app.scss
vendored
6
resources/assets/sass/app.scss
vendored
|
@ -6,7 +6,7 @@
|
|||
@import "bootstrap-sass/assets/stylesheets/_bootstrap.scss";
|
||||
|
||||
// Supplementary
|
||||
@import "elements/**/*.scss";
|
||||
@import "sections/**/*.scss";
|
||||
@import "pages/**/*.scss";
|
||||
|
||||
//
|
||||
|
@ -38,13 +38,13 @@ body {
|
|||
@media (max-width: $grid-float-breakpoint-max) { padding-top: $nav-height-mobile; }
|
||||
}
|
||||
|
||||
#page-container {
|
||||
.page-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
padding-top: $nav-height;
|
||||
flex-direction: column;
|
||||
|
||||
#main-content {
|
||||
.main-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
|
88
resources/assets/sass/pages/_contact.scss
vendored
88
resources/assets/sass/pages/_contact.scss
vendored
|
@ -1,53 +1,51 @@
|
|||
.page-contact {
|
||||
#contact-form {
|
||||
$trans-speed: 100ms;
|
||||
margin-top: 35px;
|
||||
.contact-page-component {
|
||||
$trans-speed: 100ms;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
input, textarea {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
border: 2px solid fade-out($c-text, 0.75);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
transition: border $trans-speed;
|
||||
&:focus { border: 2px solid fade-out($c-base, 0.4); }
|
||||
&.error { border: 2px solid $c-error; }
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
border: 2px solid fade-out($c-text, 0.75);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
transition: border $trans-speed;
|
||||
&:focus { border: 2px solid fade-out($c-base, 0.4); }
|
||||
&.error { border: 2px solid $c-error; }
|
||||
}
|
||||
textarea {
|
||||
resize: none;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
height: 150px;
|
||||
}
|
||||
.submit {
|
||||
background-color: lighten($c-base, 5%);
|
||||
color: $c-text-light;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
transition: background-color $trans-speed;
|
||||
&:hover { background-color: $c-base; }
|
||||
&.disabled { background-color: $c-base; }
|
||||
}
|
||||
|
||||
.submit {
|
||||
background-color: lighten($c-base, 5%);
|
||||
color: $c-text-light;
|
||||
.notification {
|
||||
margin: 0px auto 15px auto;
|
||||
padding: 5px 10px;
|
||||
background-color: lighten($c-error, 15%);
|
||||
color: $c-text-light;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity $trans-speed;
|
||||
span { font-weight: bold; }
|
||||
&.visible { opacity: 1; }
|
||||
|
||||
&.success {
|
||||
background-color: transparent;
|
||||
color: $c-text;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
transition: background-color $trans-speed;
|
||||
&:hover { background-color: $c-base; }
|
||||
&.disabled { background-color: $c-base; }
|
||||
}
|
||||
|
||||
.notification {
|
||||
margin: 0px auto 15px auto;
|
||||
padding: 5px 10px;
|
||||
background-color: lighten($c-error, 15%);
|
||||
color: $c-text-light;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity $trans-speed;
|
||||
span { font-weight: bold; }
|
||||
&.visible { opacity: 1; }
|
||||
|
||||
&.success {
|
||||
background-color: transparent;
|
||||
color: $c-text;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
footer {
|
||||
.footer-section-component {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding-left: 10px;
|
||||
background-color: $c-base;
|
||||
color: $c-text-light;
|
||||
line-height: 32px;
|
||||
|
||||
&.sticky-footer {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.navbar {
|
||||
.nav-section-component {
|
||||
z-index: 1;
|
||||
margin-bottom: 0px;
|
||||
height: $nav-height;
|
|
@ -1,4 +1,4 @@
|
|||
#subscription-form {
|
||||
.subscription-form-section-component {
|
||||
$trans-speed: 100ms;
|
||||
position: absolute;
|
||||
top: 50%;
|
100
resources/components/pages/contact.vue
Normal file
100
resources/components/pages/contact.vue
Normal 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>
|
22
resources/components/pages/error404.vue
Normal file
22
resources/components/pages/error404.vue
Normal 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>
|
26
resources/components/pages/home.vue
Normal file
26
resources/components/pages/home.vue
Normal 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>
|
5
resources/components/sections/footer.vue
Normal file
5
resources/components/sections/footer.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<footer class="footer-section-component">
|
||||
© {{ new Date().getFullYear() }} {{ $store.getters.getAppName }}
|
||||
</footer>
|
||||
</template>
|
23
resources/components/sections/nav.vue
Normal file
23
resources/components/sections/nav.vue
Normal 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>
|
62
resources/components/sections/subscription-form.vue
Normal file
62
resources/components/sections/subscription-form.vue
Normal 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>
|
|
@ -1,6 +1,6 @@
|
|||
@extends('layouts.dashboard')
|
||||
@extends('templates.dashboard')
|
||||
|
||||
@section('content')
|
||||
@section('page-content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@extends('layouts.dashboard')
|
||||
@extends('templates.dashboard')
|
||||
|
||||
@section('content')
|
||||
@section('page-content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@extends('layouts.dashboard')
|
||||
@extends('templates.dashboard')
|
||||
|
||||
@section('content')
|
||||
@section('page-content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@extends('layouts.dashboard')
|
||||
@extends('templates.dashboard')
|
||||
|
||||
@section('content')
|
||||
@section('page-content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@extends('layouts.dashboard')
|
||||
@extends('templates.dashboard')
|
||||
|
||||
@section('content')
|
||||
@section('page-content')
|
||||
<div class="container spark-screen">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
@section('dashboard-body')
|
||||
@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
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
@endif
|
||||
@else
|
||||
@set('menu_class', 'nav-item')
|
||||
@include('dashboard.elements.menu')
|
||||
@include('dashboard.sections.menu')
|
||||
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
|
@ -1 +0,0 @@
|
|||
<footer>© {{ date('Y') }} {{ env('APP_NAME') }}</footer>
|
|
@ -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>
|
9
resources/views/elements/variables.blade.php
Normal file
9
resources/views/elements/variables.blade.php
Normal 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>
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.error')
|
||||
@extends('templates.error')
|
||||
|
||||
@section('error-title')
|
||||
Page Not Found
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.error')
|
||||
@extends('templates.error')
|
||||
|
||||
@section('error-title')
|
||||
Be Right Back
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.error')
|
||||
@extends('templates.error')
|
||||
|
||||
@section('error-title')
|
||||
No Such Record
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
@extends('layouts.public', [ 'title' => 'Contact' ])
|
||||
@extends('templates.public', [ 'title' => 'Contact' ])
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8 col-md-offset-2">
|
||||
<h1>Contact</h1>
|
||||
<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>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8 col-md-offset-2">
|
||||
<div id="contact-form">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-8 col-md-offset-2">
|
||||
<form action="#" method="POST" accept-charset="UTF-8">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
|
||||
<input type="text" name="name" placeholder="Name" />
|
7
resources/views/pages/index.blade.php
Normal file
7
resources/views/pages/index.blade.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
@extends('templates.public')
|
||||
|
||||
@section('content')
|
||||
<div class="home-page-component">
|
||||
@include('sections.subscription-form')
|
||||
</div>
|
||||
@endsection
|
3
resources/views/sections/footer.blade.php
Normal file
3
resources/views/sections/footer.blade.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<footer class="footer-section-component">
|
||||
© {{ date('Y') }} {{ env('APP_NAME') }}
|
||||
</footer>
|
|
@ -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="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">
|
9
resources/views/sections/subscription-form.blade.php
Normal file
9
resources/views/sections/subscription-form.blade.php
Normal 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>
|
|
@ -41,21 +41,13 @@
|
|||
@endif
|
||||
</head>
|
||||
|
||||
@if(preg_match('/^dashboard/', Request::path()))
|
||||
@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' }}">
|
||||
<body class="{{ $device_mobile ? 'mobile-browser' : 'desktop-browser' }}">
|
||||
@yield('page-top')
|
||||
|
||||
<div id="page-container">
|
||||
<div id="main-content">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
@yield('page-bottom')
|
||||
<div id="page-content">
|
||||
@yield('page-content')
|
||||
</div>
|
||||
|
||||
@yield('page-bottom')
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.base', [ 'title' => 'Dashboard' ])
|
||||
@extends('templates.base', [ 'title' => 'Dashboard' ])
|
||||
|
||||
@section('page-includes')
|
||||
<script src="/js/lib-dashboard.js?version={{ env('CACHE_BUST') }}"></script>
|
||||
|
@ -7,5 +7,5 @@
|
|||
@endsection
|
||||
|
||||
@section('page-top')
|
||||
@include('dashboard.elements.nav')
|
||||
@include('dashboard.sections.nav')
|
||||
@endsection
|
23
resources/views/templates/public-vue.blade.php
Normal file
23
resources/views/templates/public-vue.blade.php
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.base')
|
||||
@extends('templates.base')
|
||||
|
||||
@section('page-includes')
|
||||
<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') }}" />
|
||||
@endsection
|
||||
|
||||
@section('page-top')
|
||||
@include('elements.nav')
|
||||
@endsection
|
||||
@section('page-content')
|
||||
@include('sections.nav')
|
||||
|
||||
@section('page-bottom')
|
||||
@include('elements.footer')
|
||||
<div class="page-container">
|
||||
<div class="main-content">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
@include('sections.footer')
|
||||
</div>
|
||||
@endsection
|
|
@ -1,5 +0,0 @@
|
|||
@extends('layouts.public', [ 'title' => 'Home' ])
|
||||
|
||||
@section('content')
|
||||
@include('elements.subscription-form')
|
||||
@endsection
|
|
@ -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) {
|
||||
Language::setSessionLanguage($lang);
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue