initial commit
This commit is contained in:
2
.browserslistrc
Normal file
2
.browserslistrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
17
.eslintrc.js
Normal file
17
.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
explorations
|
||||||
|
TODOs.md
|
||||||
|
dist/*.gz
|
||||||
|
dist/*.map
|
||||||
|
dist/vue.common.min.js
|
||||||
|
test/e2e/reports
|
||||||
|
test/e2e/screenshots
|
||||||
|
coverage
|
||||||
|
RELEASE_NOTE*.md
|
||||||
|
dist/*.js
|
||||||
|
packages/vue-server-renderer/basic.js
|
||||||
|
packages/vue-server-renderer/build.js
|
||||||
|
packages/vue-server-renderer/server-plugin.js
|
||||||
|
packages/vue-server-renderer/client-plugin.js
|
||||||
|
packages/vue-template-compiler/build.js
|
||||||
|
.vscode
|
||||||
4
auth_config.json
Normal file
4
auth_config.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"domain": "dev-xu-97g3w.eu.auth0.com",
|
||||||
|
"clientId": "W0Iim8av0OOJXQMfuPiOAjA3fx3ESQoi"
|
||||||
|
}
|
||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
141
flow.md
Normal file
141
flow.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Flow + Structure of Vue project
|
||||||
|
|
||||||
|
|
||||||
|
- If components are to be imported elsewhere they should `export default` and set their name to be the name of the html element you want.
|
||||||
|
- If components import an element they should `export default` and set a `components` with an array of the components being used in the html.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
import Nav from './components/partials/Nav.vue';
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
Nav
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Main Page
|
||||||
|
|
||||||
|
|
||||||
|
### `App.vue`
|
||||||
|
|
||||||
|
|
||||||
|
This is the main entrypoint into the website.
|
||||||
|
|
||||||
|
|
||||||
|
- `template` should inject a `<div id="app">`
|
||||||
|
- insert the `<Nav />` component
|
||||||
|
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
|
||||||
|
- Create a `VueRouter` in `./router/index.js`
|
||||||
|
- Create a `routes` array which takes a dict of each route
|
||||||
|
- Route should set three things:
|
||||||
|
- A `path` - this sets the URL path.
|
||||||
|
- A `name` - this is the name to appear in the navbar.
|
||||||
|
- A `component` - this should import the vue component
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
path: '/event/:id',
|
||||||
|
name: 'eventSingle',
|
||||||
|
component: () => import ('../views/EventSingle.vue')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### `./router/index.js`
|
||||||
|
|
||||||
|
|
||||||
|
- Import all components
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/event/:id',
|
||||||
|
name: 'eventSingle',
|
||||||
|
component: () => import ('../views/EventSingle.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
base: process.env.BASE_URL,
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export default router
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Rendering Objects
|
||||||
|
|
||||||
|
|
||||||
|
### Cards + Card List
|
||||||
|
|
||||||
|
|
||||||
|
We have:
|
||||||
|
|
||||||
|
|
||||||
|
- A EventCard of which many of them form a EventsList
|
||||||
|
- A EventSingle which is a page for each card
|
||||||
|
|
||||||
|
|
||||||
|
We use an EventsList template to render the cards - this imported and used in the `Home.vue`
|
||||||
|
|
||||||
|
|
||||||
|
A router exists for each Event - this is used to link each EventCard to an EventSingle. An EventSingle is a template for a EventCard.
|
||||||
|
|
||||||
|
|
||||||
|
The EventsList hits an api to generate the cards dynamically. The EventSingle hits an api to get details of a single card.
|
||||||
|
|
||||||
|
|
||||||
|
#### EventCard
|
||||||
|
|
||||||
|
|
||||||
|
- Takes a prop of the data you need
|
||||||
|
- Creates a card for a single item
|
||||||
|
- Links to a EventSingle using a route parameter
|
||||||
|
|
||||||
|
|
||||||
|
#### EventSingle
|
||||||
|
|
||||||
|
|
||||||
|
- Hits an API
|
||||||
|
- Uses a route parameter to determine which event to show
|
||||||
|
- Displays a whole page for a single event
|
||||||
|
|
||||||
|
|
||||||
|
#### EventsList
|
||||||
|
|
||||||
|
|
||||||
|
- Hits an API for its data
|
||||||
|
- Uses a `v-for` to render a EventCard
|
||||||
|
- Uses a router-link to link to a EventSingle
|
||||||
82
notes.md
Normal file
82
notes.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
setting a router param
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/event/:id',
|
||||||
|
name: 'eventSingle',
|
||||||
|
component: () => import ('../views/EventSingle.vue')
|
||||||
|
}
|
||||||
|
|
||||||
|
reading a router param
|
||||||
|
|
||||||
|
created() {
|
||||||
|
const ID = Number(this.$route.params.id);
|
||||||
|
const event = this.events.find(event => event.id === ID);
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
create something from this router param (Using it)
|
||||||
|
|
||||||
|
see above
|
||||||
|
|
||||||
|
find in a dict and the syntax of => ===
|
||||||
|
|
||||||
|
Use find as a method on an array.
|
||||||
|
|
||||||
|
find(something => something.id === ID)
|
||||||
|
|
||||||
|
use arrow notation here in order to use the param you define to be used.
|
||||||
|
the bit before the arrow notation is a param you want to find (name is arbitrary it will resolve to an object)
|
||||||
|
the bit after the arrow allows you to use the name you want to be iterated/used. this must resolve to a boolean (i.e === as a coniditional)
|
||||||
|
think of the arrow notation as a "where" clause (find something where something===True/False)
|
||||||
|
|
||||||
|
setting a param based off this
|
||||||
|
|
||||||
|
in a hook you can set the value of your find and bind it to something
|
||||||
|
this.event = event;
|
||||||
|
|
||||||
|
make sure to create it in data too
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
using class bindings (setting a class of an element dynamically)
|
||||||
|
link to https://vuejs.org/v2/guide/class-and-style.html
|
||||||
|
|
||||||
|
|
||||||
|
v-binds (v-bind: and just :)
|
||||||
|
|
||||||
|
using v-for, what it does (renders the thing being v-for'd for each thing in it)
|
||||||
|
how to set an html attribute from this (using v-bind:attribute e.g v-bind:src)
|
||||||
|
|
||||||
|
<div class="event-images columns is-multiline has-text-centered">
|
||||||
|
<div v-for="image in event.images" :key="image.id" class="column is-one-third">
|
||||||
|
<img :src="image" :alt="event.name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
Vue lifecycle (useful for seeing all hooks available)
|
||||||
|
https://cdn.auth0.com/blog/vue-meetup/vue-lifecycle.png
|
||||||
|
|
||||||
|
|
||||||
|
doing something on a click
|
||||||
|
|
||||||
|
in a `<a>` you can use `@click="login"` to do a method
|
||||||
|
You should define this method in the script directive
|
||||||
|
|
||||||
|
|
||||||
|
Conditional (ternary) operator
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
|
||||||
|
|
||||||
|
The "?" and ":" syntax is often used as a shortcut to a if block
|
||||||
|
When using this you should specify 3 arguments:
|
||||||
|
- the condition to check
|
||||||
|
- what should happen if it's true (?)
|
||||||
|
- what should happen if it's false (:)
|
||||||
|
|
||||||
|
onRedirectCallback: appState => {
|
||||||
|
router.push(
|
||||||
|
appState && appState.targetUrl
|
||||||
|
? appState.targetUrl
|
||||||
|
: window.location.pathname
|
||||||
|
);
|
||||||
|
}
|
||||||
11806
package-lock.json
generated
Normal file
11806
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
package.json
Normal file
34
package.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "events-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@auth0/auth0-spa-js": "^1.6.4",
|
||||||
|
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"bulma": "^0.8.0",
|
||||||
|
"core-js": "^3.6.4",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"v-blur": "^1.0.4",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-axios": "^2.1.5",
|
||||||
|
"vue-router": "^3.1.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "~4.2.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.2.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.2.0",
|
||||||
|
"@vue/cli-service": "~4.2.0",
|
||||||
|
"babel-eslint": "^10.0.3",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-vue": "^6.1.2",
|
||||||
|
"sass": "^1.25.0",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
17
public/index.html
Normal file
17
public/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
src/App.vue
Normal file
24
src/App.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<Nav />
|
||||||
|
<router-view/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Nav from './components/partials/Nav.vue';
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
Nav
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#app {
|
||||||
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
src/auth/authGuard.js
Normal file
27
src/auth/authGuard.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { getInstance } from "./index";
|
||||||
|
|
||||||
|
export const authGuard = (to, from, next) => {
|
||||||
|
const authService = getInstance();
|
||||||
|
|
||||||
|
const fn = () => {
|
||||||
|
// If the user is authenticated, continue with the route
|
||||||
|
if (authService.isAuthenticated) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, log in
|
||||||
|
authService.loginWithRedirect({ appState: { targetUrl: to.fullPath } });
|
||||||
|
};
|
||||||
|
|
||||||
|
// If loading has already finished, check our auth state using `fn()`
|
||||||
|
if (!authService.loading) {
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for the loading property to change before we check isAuthenticated
|
||||||
|
authService.$watch("loading", loading => {
|
||||||
|
if (loading === false) {
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
127
src/auth/index.js
Normal file
127
src/auth/index.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import Vue from "vue";
|
||||||
|
import createAuth0Client from "@auth0/auth0-spa-js";
|
||||||
|
|
||||||
|
/** Define a default action to perform after authentication */
|
||||||
|
const DEFAULT_REDIRECT_CALLBACK = () =>
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
|
|
||||||
|
let instance;
|
||||||
|
|
||||||
|
/** Returns the current instance of the SDK */
|
||||||
|
export const getInstance = () => instance;
|
||||||
|
|
||||||
|
/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
|
||||||
|
export const useAuth0 = ({
|
||||||
|
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
|
||||||
|
redirectUri = window.location.origin,
|
||||||
|
...options
|
||||||
|
}) => {
|
||||||
|
if (instance) return instance;
|
||||||
|
|
||||||
|
// The 'instance' is simply a Vue object
|
||||||
|
instance = new Vue({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
isAuthenticated: false,
|
||||||
|
user: {},
|
||||||
|
auth0Client: null,
|
||||||
|
popupOpen: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** Authenticates the user using a popup window */
|
||||||
|
async loginWithPopup(o) {
|
||||||
|
this.popupOpen = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.auth0Client.loginWithPopup(o);
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
this.popupOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.user = await this.auth0Client.getUser();
|
||||||
|
this.isAuthenticated = true;
|
||||||
|
},
|
||||||
|
/** Handles the callback when logging in using a redirect */
|
||||||
|
async handleRedirectCallback() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
await this.auth0Client.handleRedirectCallback();
|
||||||
|
this.user = await this.auth0Client.getUser();
|
||||||
|
this.isAuthenticated = true;
|
||||||
|
} catch (e) {
|
||||||
|
this.error = e;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** Authenticates the user using the redirect method */
|
||||||
|
loginWithRedirect(o) {
|
||||||
|
return this.auth0Client.loginWithRedirect(o);
|
||||||
|
},
|
||||||
|
/** Returns all the claims present in the ID token */
|
||||||
|
getIdTokenClaims(o) {
|
||||||
|
return this.auth0Client.getIdTokenClaims(o);
|
||||||
|
},
|
||||||
|
/** Returns the access token. If the token is invalid or missing, a new one is retrieved */
|
||||||
|
getTokenSilently(o) {
|
||||||
|
return this.auth0Client.getTokenSilently(o);
|
||||||
|
},
|
||||||
|
/** Gets the access token using a popup window */
|
||||||
|
|
||||||
|
getTokenWithPopup(o) {
|
||||||
|
return this.auth0Client.getTokenWithPopup(o);
|
||||||
|
},
|
||||||
|
/** Logs the user out and removes their session on the authorization server */
|
||||||
|
logout(o) {
|
||||||
|
return this.auth0Client.logout(o);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** Use this lifecycle method to instantiate the SDK client */
|
||||||
|
async created() {
|
||||||
|
// Create a new instance of the SDK client using members of the given options object
|
||||||
|
this.auth0Client = await createAuth0Client({
|
||||||
|
domain: options.domain,
|
||||||
|
client_id: options.clientId,
|
||||||
|
audience: options.audience,
|
||||||
|
redirect_uri: redirectUri
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If the user is returning to the app after authentication...
|
||||||
|
if (
|
||||||
|
window.location.search.includes("code=") &&
|
||||||
|
window.location.search.includes("state=")
|
||||||
|
) {
|
||||||
|
// handle the redirect and retrieve tokens
|
||||||
|
const { appState } = await this.auth0Client.handleRedirectCallback();
|
||||||
|
|
||||||
|
// Notify subscribers that the redirect callback has happened, passing the appState
|
||||||
|
// (useful for retrieving any pre-authentication state)
|
||||||
|
onRedirectCallback(appState);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.error = e;
|
||||||
|
} finally {
|
||||||
|
// Initialize our internal authentication state
|
||||||
|
this.isAuthenticated = await this.auth0Client.isAuthenticated();
|
||||||
|
this.user = await this.auth0Client.getUser();
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a simple Vue plugin to expose the wrapper object throughout the application
|
||||||
|
export const Auth0Plugin = {
|
||||||
|
install(Vue, options) {
|
||||||
|
Vue.prototype.$auth = useAuth0(options);
|
||||||
|
}
|
||||||
|
};
|
||||||
63
src/components/EventCard.vue
Normal file
63
src/components/EventCard.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="event-card">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<h2 class="is-size-4 has-text-weight-bold">{{ event.name }}</h2>
|
||||||
|
<small class="event-date">{{ event.date }}</small>
|
||||||
|
<span>{{ event.location }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'EventCard',
|
||||||
|
props: ['event'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
background-image: url('https://placekitten.com/400/400');
|
||||||
|
height: 200px;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding-top: 50px;
|
||||||
|
position: absolute;
|
||||||
|
color: #FFF;
|
||||||
|
background-color: rgba(0, 0, 0, 0.35);
|
||||||
|
top: 0;
|
||||||
|
padding: 10px;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
span {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.event-date {
|
||||||
|
background-color: #151515;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: .75em;
|
||||||
|
padding: 2px 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
76
src/components/EventsList.vue
Normal file
76
src/components/EventsList.vue
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div class="events container">
|
||||||
|
<h2 class="subtitle is-3">
|
||||||
|
Check out our incoming events
|
||||||
|
</h2>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
<div v-for="event in events" :key="event.id" class="column is-one-quarter">
|
||||||
|
<router-link :to="'/event/' + event.id">
|
||||||
|
<EventCard :event="event"></EventCard>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import EventCard from '@/components/EventCard';
|
||||||
|
export default {
|
||||||
|
name: 'EventsList',
|
||||||
|
components: {
|
||||||
|
EventCard
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
event: {},
|
||||||
|
events: [{
|
||||||
|
id: 1,
|
||||||
|
name: 'Charity Ball',
|
||||||
|
category: 'Fundraising',
|
||||||
|
description: 'Spend an elegant night of dinner and dancing with us as we raise money for our new rescue farm.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
],
|
||||||
|
location: '1234 Fancy Ave',
|
||||||
|
date: '12-25-2019',
|
||||||
|
time: '11:30'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Rescue Center Goods Drive',
|
||||||
|
category: 'Adoptions',
|
||||||
|
description: 'Come to our donation drive to help us replenish our stock of pet food, toys, bedding, etc. We will have live bands, games, food trucks, and much more.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500'
|
||||||
|
],
|
||||||
|
location: '1234 Dog Alley',
|
||||||
|
date: '11-21-2019',
|
||||||
|
time: '12:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Rescue Center Goods Drive',
|
||||||
|
category: 'Adoptions',
|
||||||
|
description: 'Come to our donation drive to help us replenish our stock of pet food, toys, bedding, etc. We will have live bands, games, food trucks, and much more.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500'
|
||||||
|
],
|
||||||
|
location: '1234 Dog Alley',
|
||||||
|
date: '11-21-2019',
|
||||||
|
time: '12:00'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.events {
|
||||||
|
margin-top: 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
63
src/components/HelloWorld.vue
Normal file
63
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hello">
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
<p>
|
||||||
|
For a guide and recipes on how to configure / customize this project,<br>
|
||||||
|
check out the
|
||||||
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||||
|
</p>
|
||||||
|
<h3>Installed CLI Plugins</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
||||||
|
target="_blank" rel="noopener">babel</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
||||||
|
target="_blank" rel="noopener">router</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
||||||
|
target="_blank" rel="noopener">eslint</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Essential Links</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||||
|
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||||
|
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||||
|
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||||
|
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Ecosystem</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||||
|
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||||
|
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'HelloWorld',
|
||||||
|
props: {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped lang="scss">
|
||||||
|
h3 {
|
||||||
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
src/components/partials/Nav.vue
Normal file
88
src/components/partials/Nav.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<nav class="navbar container" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
<strong class="is-size-4">Animal Rescue League</strong>
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
data-target="navbarBasicExample">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="navbar" class="navbar-menu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<router-link to="/" class="navbar-item">Home</router-link>
|
||||||
|
<router-link to="/about" class="navbar-item">About</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<!-- Check that the SDK client is not currently loading before accessing is methods -->
|
||||||
|
<div v-if="!$auth.loading">
|
||||||
|
<!-- show login when not authenticated -->
|
||||||
|
<a v-if="!$auth.isAuthenticated" @click="login" class="button is-dark"><strong>Sign
|
||||||
|
in</strong></a>
|
||||||
|
<!-- show logout when authenticated -->
|
||||||
|
<a v-if="$auth.isAuthenticated" @click="logout" class="button is-dark"><strong>Log
|
||||||
|
out</strong></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-item" id="live-time" v-if="timeReady">
|
||||||
|
{{ time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
export default {
|
||||||
|
name: 'Nav',
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
time: '',
|
||||||
|
timeReady: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTime() {
|
||||||
|
// console.log('Getting current time');
|
||||||
|
return moment().format('HH:mm:ss')
|
||||||
|
},
|
||||||
|
// Log the user in
|
||||||
|
login() {
|
||||||
|
this.$auth.loginWithRedirect();
|
||||||
|
},
|
||||||
|
// Log the user out
|
||||||
|
logout() {
|
||||||
|
this.$auth.logout({
|
||||||
|
returnTo: window.location.origin
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
setInterval(() => {
|
||||||
|
this.time = this.getTime(), 1000
|
||||||
|
});
|
||||||
|
this.timeReady = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
nav {
|
||||||
|
margin-top: 25px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
|
||||||
|
&.router-link-exact-active {
|
||||||
|
color: #d88d00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
38
src/main.js
Normal file
38
src/main.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import axios from 'axios'
|
||||||
|
import VueAxios from 'vue-axios'
|
||||||
|
import './../node_modules/bulma/css/bulma.css'
|
||||||
|
// import moment from './../node_modules/moment/moment.js'
|
||||||
|
|
||||||
|
// Import the Auth0 configuration
|
||||||
|
import { domain, clientId } from "../auth_config.json";
|
||||||
|
|
||||||
|
// Import the plugin here
|
||||||
|
import { Auth0Plugin } from "./auth";
|
||||||
|
|
||||||
|
// Install the authentication plugin here
|
||||||
|
Vue.use(Auth0Plugin, {
|
||||||
|
domain,
|
||||||
|
clientId,
|
||||||
|
onRedirectCallback: appState => {
|
||||||
|
router.push(
|
||||||
|
appState && appState.targetUrl
|
||||||
|
? appState.targetUrl
|
||||||
|
: window.location.pathname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
// Object.defineProperty(Vue.prototype, '$moment', { value: moment });
|
||||||
|
|
||||||
|
Vue.use(VueAxios, axios)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount('#app')
|
||||||
|
|
||||||
|
|
||||||
36
src/router/index.js
Normal file
36
src/router/index.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
import Home from '../views/Home.vue'
|
||||||
|
import { authGuard } from "../auth/authGuard";
|
||||||
|
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/event/:id',
|
||||||
|
name: 'eventSingle',
|
||||||
|
component: () => import ('../views/EventSingle.vue'),
|
||||||
|
beforeEnter: authGuard
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
base: process.env.BASE_URL,
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
18
src/views/About.vue
Normal file
18
src/views/About.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<div class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title is-size-1">
|
||||||
|
About Animal Rescue League
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.org-description {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
105
src/views/EventSingle.vue
Normal file
105
src/views/EventSingle.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div class="event-single">
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
{{ event.name }}
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
<strong>Date:</strong> {{ event.date }}
|
||||||
|
<br>
|
||||||
|
<strong>Time:</strong> {{ event.time }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="event-content">
|
||||||
|
<div class="container">
|
||||||
|
<p class="is-size-4 description">{{ event.description }}</p>
|
||||||
|
<p class="is-size-4">Location: {{ event.location }}</p>
|
||||||
|
<p class="is-size-4">Category: {{ event.category }}</p>
|
||||||
|
<div class="event-images columns is-multiline has-text-centered"></div>
|
||||||
|
<div class="event-images columns is-multiline has-text-centered">
|
||||||
|
<div v-for="image in event.images" :key="image.id" class="column is-one-third">
|
||||||
|
<img :src="image" :alt="event.name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'EventSingle',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
events: [{
|
||||||
|
id: 1,
|
||||||
|
name: 'Charity Ball',
|
||||||
|
category: 'Fundraising',
|
||||||
|
description: 'Spend an elegant night of dinner and dancing with us as we raise money for our new rescue farm.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
'https://placekitten.com/500/500',
|
||||||
|
],
|
||||||
|
location: '1234 Fancy Ave',
|
||||||
|
date: '12-25-2019',
|
||||||
|
time: '11:30'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Rescue Center Goods Drive',
|
||||||
|
category: 'Adoptions',
|
||||||
|
description: 'Come to our donation drive to help us replenish our stock of pet food, toys, bedding, etc. We will have live bands, games, food trucks, and much more.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500'
|
||||||
|
],
|
||||||
|
location: '1234 Dog Alley',
|
||||||
|
date: '11-21-2019',
|
||||||
|
time: '12:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Rescue Center Goods Drive',
|
||||||
|
category: 'Adoptions',
|
||||||
|
description: 'Come to our donation drive to help us replenish our stock of pet food, toys, bedding, etc. We will have live bands, games, food trucks, and much more.',
|
||||||
|
featuredImage: 'https://placekitten.com/500/500',
|
||||||
|
images: [
|
||||||
|
'https://placekitten.com/500/500'
|
||||||
|
],
|
||||||
|
location: '1234 Dog Alley',
|
||||||
|
date: '11-21-2019',
|
||||||
|
time: '12:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
event: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
const ID = Number(this.$route.params.id);
|
||||||
|
let event = this.events.find(my_event => my_event.id === ID);
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.event-single {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
margin-bottom: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-images {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: 30px
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
src/views/Home.vue
Normal file
88
src/views/Home.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
Welcome to the Animal Rescue League
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
Make sure you check out the upcoming events below:
|
||||||
|
</h2>
|
||||||
|
<div class="button-block">
|
||||||
|
<button v-if="!$auth.isAuthenticated" @click="login" class="button is-xl is-dark">Sign Up to Browse Events</button>
|
||||||
|
<h3 v-if="$auth.isAuthenticated" class="is-size-5 has-background-dark welcome">Welcome, {{ $auth.user.nickname }}!</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<EventsList />
|
||||||
|
<!-- <img class="blurify" src="https://cdn.auth0.com/blog/vue-meetup/event-banner.png"> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import EventsList from '@/components/EventsList'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'home',
|
||||||
|
components: {
|
||||||
|
EventsList
|
||||||
|
},
|
||||||
|
methods : {
|
||||||
|
// Log the user in
|
||||||
|
login() {
|
||||||
|
this.$auth.loginWithRedirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.hero {
|
||||||
|
text-align: center;
|
||||||
|
background-image: url('https://face4pets.files.wordpress.com/2018/01/shelter-data-1.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-body .title {
|
||||||
|
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 40px 0 20px 0;
|
||||||
|
font-size: 60px;
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.7);
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-block {
|
||||||
|
text-align: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -150px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-right: 50px;
|
||||||
|
padding-left: 50px;
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome {
|
||||||
|
width: 400px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-xl {
|
||||||
|
font-size: 1.7rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
0
vue.config.js
Normal file
0
vue.config.js
Normal file
5
vue_tags.toml
Normal file
5
vue_tags.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[tags]
|
||||||
|
login = ["auth0"]
|
||||||
|
api = ["none"]
|
||||||
|
css = ["bulma"]
|
||||||
|
auth0_password = "zoBj4YM#"
|
||||||
Reference in New Issue
Block a user