Add basic functionality
This commit is contained in:
126
server/router/account/index.js
Normal file
126
server/router/account/index.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* This router handles things related to the web browser experience...
|
||||
*/
|
||||
|
||||
// This is the mock data we working with...
|
||||
module.exports = function (options) {
|
||||
var express = options.express;
|
||||
var db = options.db;
|
||||
var passport = options.passport;
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
if (request.user) {
|
||||
response.display("account", {
|
||||
user: request.user,
|
||||
pageTitle: "Home - Mantra"
|
||||
})
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
router.route('/authenticate') // TODO: Might have a unique identifier for this endpoint...
|
||||
.get(function (request, response, next) {
|
||||
if (request.user == null) {
|
||||
response.display("login-username", {
|
||||
user: request.user,
|
||||
pageTitle: "Login - Mantra",
|
||||
});
|
||||
} else {
|
||||
response.redirect('/');
|
||||
}
|
||||
})
|
||||
.post(function (request, response, next) {
|
||||
if (request.user == null) {
|
||||
var username = "" + request.body.username;
|
||||
username = username.trim().toLowerCase();
|
||||
if(request.body.password == null) {
|
||||
const email = db.Email.build({
|
||||
address: username
|
||||
})
|
||||
email.validate()
|
||||
.then((email)=> {
|
||||
response.display("login-password", {
|
||||
user: request.user,
|
||||
username: email.address,
|
||||
pageTitle: "Login - Mantra",
|
||||
});
|
||||
}).catch(error => {
|
||||
response.display("login-username", {
|
||||
user: request.user,
|
||||
username: username,
|
||||
pageTitle: "Login - Mantra",
|
||||
data: {
|
||||
message: "Enter a valid email"
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
passport.authenticate('local', (error, user, info) => {
|
||||
console.error("Error: ", error)
|
||||
if(info) {
|
||||
console.log("Info: ", info)
|
||||
response.display("login-username", {
|
||||
user: request.user,
|
||||
username: username,
|
||||
pageTitle: "Login - Mantra",
|
||||
data: info
|
||||
})
|
||||
} else if(user) {
|
||||
|
||||
if(request.body.rememberMe) {
|
||||
var now = new Date();
|
||||
var expiresAt = new Date();
|
||||
expiresAt.setFullYear(expiresAt.getFullYear()+5);
|
||||
// request.session.cookie.expires = expiresAt;
|
||||
request.session.cookie.maxAge = expiresAt;
|
||||
|
||||
// TODO: Add the remember me cookie...
|
||||
} else {
|
||||
// TODO: Set that the remember me cookie wasn't selected...
|
||||
}
|
||||
|
||||
request.logIn(user, function(err) {
|
||||
if (err) { return next(err); }
|
||||
return response.redirect('/');
|
||||
});
|
||||
} else {
|
||||
response.redirect('/account/authenticate')
|
||||
}
|
||||
})(request, response, next);
|
||||
}
|
||||
} else {
|
||||
response.redirect("/");
|
||||
}
|
||||
})
|
||||
|
||||
router.route('/logout')
|
||||
.post(function(request, response, next) {
|
||||
if (request.user) {
|
||||
request.logout();
|
||||
return db.Session.destroy({
|
||||
where: {
|
||||
sid: request.sessionID
|
||||
}
|
||||
}).then(result => {
|
||||
response.redirect('/');
|
||||
}).catch(error => {
|
||||
response.redirect('/');
|
||||
console.error("Log out error: ", error);
|
||||
})
|
||||
} else {
|
||||
response.redirect('/');
|
||||
}
|
||||
});
|
||||
|
||||
// var passwordResetRouter = require('./password-reset/')(options);
|
||||
var libraryRouter = require('../library')(options);
|
||||
|
||||
// router.use('/password-reset', passwordResetRouter);
|
||||
router.use('/library', libraryRouter);
|
||||
|
||||
return router;
|
||||
};
|
||||
182
server/router/account/password-reset/index.js
Normal file
182
server/router/account/password-reset/index.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const express = require('express')
|
||||
const emailAPI = require('../../../email/email-api')
|
||||
|
||||
module.exports = function (options) {
|
||||
const db = options.db;
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.display("password-reset-initiation", {
|
||||
user: request.user,
|
||||
pageTitle: "Password Reset - Mantra"
|
||||
})
|
||||
}).post(function(request, response, next) {
|
||||
// TODO: Create UserPasswordReset object.
|
||||
// TODO: Send email with link to /account/password-reset/:id
|
||||
db.User.findOne({
|
||||
include: [
|
||||
{
|
||||
association: db.User.UserEmails,
|
||||
required: true,
|
||||
where: {
|
||||
emailAddress: request.body.email
|
||||
}
|
||||
},
|
||||
{
|
||||
association: db.User.UserPasswordReset
|
||||
}
|
||||
// TODO: Include user.UserPasswordReset
|
||||
]
|
||||
}).then(user => {
|
||||
if (user) {
|
||||
// TODO: Check if user already created a password reset entry to save on a db transaction
|
||||
return Promise.all([
|
||||
user.userEmails[0],
|
||||
user.userPasswordReset ? user.userPasswordReset : db.UserPasswordReset.create({
|
||||
userId: user.id
|
||||
})
|
||||
])
|
||||
} else {
|
||||
return [null, null]
|
||||
}
|
||||
}).then(async (results) => {
|
||||
userEmail = results[0]
|
||||
userPasswordReset = results[1]
|
||||
if (userPasswordReset) {
|
||||
await userPasswordReset.increment('sent')
|
||||
// Send password reset email...
|
||||
return emailAPI.sendPasswordResetEmail(userPasswordReset.token, userEmail.emailAddress)
|
||||
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}).then(emailResult => {
|
||||
if (emailResult) {
|
||||
response.display("password-reset-initiated", {
|
||||
user: request.user,
|
||||
pageTitle: "Password Reset Initiated - Mantra"
|
||||
})
|
||||
} else {
|
||||
// TODO: Check if email is an immigrant
|
||||
db.Immigrant.findOne({
|
||||
where: {
|
||||
emailAddress: request.body.email
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.Immigrant.User,
|
||||
},
|
||||
{
|
||||
association: db.Immigrant.Email,
|
||||
required: true,
|
||||
}
|
||||
]
|
||||
}).then(async (immigrant) => {
|
||||
if (immigrant && immigrant.user == null) {
|
||||
// Resend invitation
|
||||
await emailAPI.sendImmigrationInvitationEmail(
|
||||
immigrant.id, immigrant.emailAddress, immigrant.displayName
|
||||
)
|
||||
|
||||
response.display("password-reset-initiated", {
|
||||
user: request.user,
|
||||
pageTitle: "Password Reset Initiated - Mantra"
|
||||
})
|
||||
} else {
|
||||
next(`Failed to create a user password reset entry (user ${request.body.email} doesn't exist)`)
|
||||
}
|
||||
}).catch (error => {
|
||||
next(error)
|
||||
})
|
||||
// TODO: Create password reset error page with possible reasons...
|
||||
|
||||
}
|
||||
}).catch(reason => {
|
||||
console.error("Something went wrong with reset: ", reason)
|
||||
// TODO: Tell user
|
||||
next(reason)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
router.route('/:token')
|
||||
.get(function(request, response, next) {
|
||||
if (request.user) {
|
||||
// TODO:
|
||||
response.display("logout-to-continue", {
|
||||
user: request.user,
|
||||
pageTitle: "Logout Before Continue - Mantra"
|
||||
})
|
||||
} else {
|
||||
db.UserPasswordReset.findByPk(request.params.token, {
|
||||
include: [
|
||||
{
|
||||
association: db.UserPasswordReset.User
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(userPasswordReset => {
|
||||
if (userPasswordReset) {
|
||||
response.display("password-reset", {
|
||||
user: request.user,
|
||||
pageTitle: "Password Reset - Mantra",
|
||||
userPasswordReset: userPasswordReset
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}).catch(error => {
|
||||
next(error)
|
||||
})
|
||||
}
|
||||
|
||||
}).post(function(request, response, next) {
|
||||
if (Boolean(request.body.password) && request.body.password == request.body.confirmPassword) {
|
||||
db.UserPasswordReset.findByPk(request.params.token, {
|
||||
include: [
|
||||
{
|
||||
association: db.UserPasswordReset.User,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
association: db.User.Passwords,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(userPasswordReset => {
|
||||
if (userPasswordReset) {
|
||||
var password = userPasswordReset.user.passwords[0]
|
||||
password.password = request.body.password // Password will be hashed...
|
||||
return Promise.all([userPasswordReset, password.save()])
|
||||
} else {
|
||||
return [null, null]
|
||||
}
|
||||
}).then(results => {
|
||||
userPasswordReset = results[0]
|
||||
password = results[1]
|
||||
if (password) {
|
||||
// Password reset success invite user to reset...
|
||||
response.redirect("/account")
|
||||
// Cleanup...
|
||||
userPasswordReset.destroy({
|
||||
force: true
|
||||
}).then(() => {})
|
||||
.catch(reason => console.error("Failed to destroy userPasswordReset", reason))
|
||||
} else {
|
||||
// TODO: Give user failed password page...
|
||||
next("Failed to reset password")
|
||||
}
|
||||
}).catch(error => {
|
||||
next(error)
|
||||
})
|
||||
} else {
|
||||
// TODO: Tell user password reset failed..
|
||||
next("Passwords do not match")
|
||||
}
|
||||
|
||||
})
|
||||
return router;
|
||||
};
|
||||
15
server/router/campaigns/index.js
Normal file
15
server/router/campaigns/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const express = require('express')
|
||||
|
||||
module.exports = function (options) {
|
||||
const db = options.db;
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.display("campaigns", {
|
||||
user: request.user,
|
||||
pageTitle: "Library - Mantra"
|
||||
})
|
||||
})
|
||||
return router;
|
||||
};
|
||||
@@ -17,5 +17,15 @@ module.exports = function (options) {
|
||||
})
|
||||
});
|
||||
|
||||
var accountRouter = require('./account/')(options);
|
||||
var libraryRouter = require('./library')(options);
|
||||
var pledgesRouter = require('./pledges/')(options);
|
||||
var campaignsRouter = require('./campaigns/')(options);
|
||||
|
||||
router.use('/library', libraryRouter);
|
||||
router.use('/pledges', pledgesRouter);
|
||||
router.use('/campaigns', campaignsRouter);
|
||||
router.use('/account', accountRouter);
|
||||
|
||||
return router;
|
||||
};
|
||||
88
server/router/library/index.js
Normal file
88
server/router/library/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const express = require('express');
|
||||
const req = require('express/lib/request');
|
||||
|
||||
module.exports = function (options) {
|
||||
const db = options.db;
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
db.Entry.findAll({
|
||||
|
||||
}).then(entries => {
|
||||
response.display("library", {
|
||||
user: request.user,
|
||||
pageTitle: "Library - Mantra",
|
||||
entries: entries
|
||||
})
|
||||
}).catch(error => {
|
||||
next(error)
|
||||
})
|
||||
})
|
||||
|
||||
router.route('/add')
|
||||
.get(function(request, response, next) {
|
||||
if (request.user) {
|
||||
response.display("library-form", {
|
||||
user: request.user,
|
||||
pageTitle: "Add Library - Mantra",
|
||||
entry: { }
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
.post(function(request, response, next) {
|
||||
if (request.user) {
|
||||
db.Dialect.findOne({
|
||||
where: {
|
||||
languageId: "en",
|
||||
countryId: "int"
|
||||
}
|
||||
}).then(dialect => {
|
||||
if (dialect) {
|
||||
return db.Entry.create({
|
||||
name: request.body.name,
|
||||
url: request.body.url,
|
||||
dialectId: dialect.id,
|
||||
licenseId: "copyright"
|
||||
})
|
||||
} else {
|
||||
response.redirect("/library/add") // TODO: Show error message on missing dialect...
|
||||
}
|
||||
|
||||
}).then(entry => {
|
||||
if (entry) {
|
||||
response.redirect(`/library/${entry.id}`)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}).catch(error => {
|
||||
next(error)
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
router.route('/:id')
|
||||
.get(function(request, response, next) {
|
||||
db.Entry.findByPk(request.params.id, {
|
||||
include: [
|
||||
{
|
||||
association: db.Entry.EntryApproval
|
||||
}
|
||||
]
|
||||
}).then(entry => {
|
||||
response.display("entry", {
|
||||
user: request.user,
|
||||
pageTitle: "Library - Mantra",
|
||||
entry: entry
|
||||
})
|
||||
}).catch(error => {
|
||||
next(error)
|
||||
})
|
||||
})
|
||||
return router;
|
||||
};
|
||||
15
server/router/pledges/index.js
Normal file
15
server/router/pledges/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const express = require('express')
|
||||
|
||||
module.exports = function (options) {
|
||||
const db = options.db;
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.display("pledges", {
|
||||
user: request.user,
|
||||
pageTitle: "Library - Mantra"
|
||||
})
|
||||
})
|
||||
return router;
|
||||
};
|
||||
@@ -115,12 +115,12 @@ module.exports = function () {
|
||||
include: [
|
||||
{
|
||||
association: db.UserRole.Role,
|
||||
required: true,
|
||||
where: {
|
||||
type: {
|
||||
[db.Sequelize.Op.or]: roles
|
||||
}
|
||||
}
|
||||
// required: true,
|
||||
// where: {
|
||||
// type: {
|
||||
// [db.Sequelize.Op.or]: roles
|
||||
// }
|
||||
// }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -172,12 +172,12 @@ module.exports = function () {
|
||||
include: [
|
||||
{
|
||||
association: db.UserRole.Role,
|
||||
required: true,
|
||||
where: {
|
||||
type: {
|
||||
[db.Sequelize.Op.or]: roles
|
||||
}
|
||||
}
|
||||
// required: true,
|
||||
// where: {
|
||||
// type: {
|
||||
// [db.Sequelize.Op.or]: roles
|
||||
// }
|
||||
// }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
6
server/views/account.pug
Normal file
6
server/views/account.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Account
|
||||
6
server/views/campaigns.pug
Normal file
6
server/views/campaigns.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Campaigns
|
||||
@@ -1,14 +1,18 @@
|
||||
html {
|
||||
font-family: courier;
|
||||
}
|
||||
|
||||
/* Color scheme from https://material.io/resources/color/#!/?view.left=0&view.right=1&primary.color=212121&secondary.color=263238 */
|
||||
.primary-color {
|
||||
background-color: #683400 !important;
|
||||
background-color: #f9fbff !important;
|
||||
}
|
||||
|
||||
.primary-color-light {
|
||||
background-color: #9a5e2c !important;
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
.primary-color-dark {
|
||||
background-color: #3d0b00 !important;
|
||||
background-color: #c6c8cc !important;
|
||||
}
|
||||
|
||||
.secondary-color {
|
||||
|
||||
6
server/views/entry.pug
Normal file
6
server/views/entry.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1= entry.name
|
||||
@@ -7,6 +7,6 @@ block content
|
||||
|
||||
p.flow-text A home for translation projects.
|
||||
p.flow-text Give translators the abilitiy to crowd fund for their work.
|
||||
p.flow-text Gives authors/publishers the option to crowd fund for rights to their literature.
|
||||
p.flow-text Give the community the ability to bid for translations of their choice.
|
||||
p.flow-text Open source by default.
|
||||
p.flow-text Gives authors/publishers the option to crowd fund digital rights to their literature.
|
||||
p.flow-text Give the community the ability to pledge for library of their choice.
|
||||
p.flow-text Open source open literature.
|
||||
18
server/views/library-form.pug
Normal file
18
server/views/library-form.pug
Normal file
@@ -0,0 +1,18 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Library
|
||||
|
||||
.row
|
||||
.col.s12
|
||||
form.row(action="/library/add", method="post")
|
||||
.col.s12.input-field
|
||||
input#name(type="text", name="name", value=entry.name)
|
||||
label(for="name") Entry Name
|
||||
.col.s12.input-field
|
||||
input#url(type="text", name="url", value=entry.url)
|
||||
label(for="url") Entry Url
|
||||
.col.s12
|
||||
button.btn.black(type="submit") add entry
|
||||
17
server/views/library.pug
Normal file
17
server/views/library.pug
Normal file
@@ -0,0 +1,17 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Library
|
||||
|
||||
a.btn.black(href="/library/add") add entry
|
||||
if entries.length == 0
|
||||
p.flow-text No Literature currently on the system
|
||||
else
|
||||
.row
|
||||
each entry in entries
|
||||
.col.s12
|
||||
a(href=`/library/${entry.id}`)
|
||||
.card-panel
|
||||
p.flow-text= entry.name
|
||||
39
server/views/login-password.pug
Normal file
39
server/views/login-password.pug
Normal file
@@ -0,0 +1,39 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
.container
|
||||
.row
|
||||
.col.s12.l4.m3
|
||||
p
|
||||
.col.s12.l4.m6
|
||||
.center
|
||||
h2.flow-text Password
|
||||
if message
|
||||
p.flow-text.red-text= message
|
||||
form(action="/account/authenticate" method="POST")
|
||||
<input type="hidden" name="username" value="#{username}"/>
|
||||
.row
|
||||
.col.s12
|
||||
<label for="password">Password:</label>
|
||||
<input id="password" type="password" name="password" autofocus/>
|
||||
.col.s12
|
||||
p
|
||||
label
|
||||
input(type="checkbox", name="rememberMe", checked="checked" )
|
||||
span Remember Me
|
||||
.row
|
||||
|
||||
<div class="col s12 center">
|
||||
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
|
||||
<button class="btn blue waves-effect waves-light" type="submit" name="action">Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col s12 center">
|
||||
<a class="btn-flat waves-effect waves-light" href="/account/password-recovery">forgot password</a>
|
||||
</div>
|
||||
|
||||
44
server/views/login-username.pug
Normal file
44
server/views/login-username.pug
Normal file
@@ -0,0 +1,44 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
-
|
||||
if (data)
|
||||
var message = data.message
|
||||
data = data == null ? {} : data;
|
||||
|
||||
.container
|
||||
.row
|
||||
.col.s12
|
||||
.row
|
||||
.col.s12.l4.m3
|
||||
p
|
||||
.col.s12.l4.m6
|
||||
.center
|
||||
h2.flow-text Login/Register
|
||||
if message
|
||||
p.flow-text.red-text= message
|
||||
form(action="/account/authenticate" method="POST")
|
||||
.row
|
||||
<label for="email">Email:</label>
|
||||
<input id="email" type="text" name="username" value="#{data.username}" autofocus/>
|
||||
.row
|
||||
|
||||
<div class="col s12 center">
|
||||
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
|
||||
<button class="btn blue waves-effect waves-light" type="submit" name="action">Login/Register
|
||||
</button>
|
||||
</div>
|
||||
.col.s12
|
||||
p.center
|
||||
a.center(href="/account/password-reset") forgot password
|
||||
p.center
|
||||
a.center(href="/account/creation") create a new account
|
||||
.col.s12.l4.m3
|
||||
.row
|
||||
|
||||
//- .col.s12
|
||||
.center
|
||||
p
|
||||
strong OR
|
||||
a.btn.blue(href="/account/oauth/google") LOGIN VIA GOOGLE
|
||||
|
||||
6
server/views/pledges.pug
Normal file
6
server/views/pledges.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Pledges
|
||||
@@ -25,16 +25,29 @@ html(lang="en" dir="ltr")
|
||||
body
|
||||
ul.sidenav.sidenav-fixed#slide-out
|
||||
li
|
||||
.user-view.primary-color-light
|
||||
.user-view.primary-color-light.z-depth-2
|
||||
.background
|
||||
a#name
|
||||
p.flow-text.white-text.name Mantra
|
||||
a#bio
|
||||
span.white-text Mantra Mantra Mantra
|
||||
a#email
|
||||
span.white-text.email mantra@exonumia.africa
|
||||
p.flow-text.black-text.name Mantra.
|
||||
//- a(href="#bio")
|
||||
span.black-text Open source open literature
|
||||
a(href="#email")
|
||||
span.email mantra@exonumia.africa
|
||||
|
||||
if user
|
||||
li
|
||||
a(href="/library")
|
||||
i.material-icons.black-text person
|
||||
span library
|
||||
li
|
||||
a(href="/campaigns")
|
||||
i.material-icons.black-text person
|
||||
span campaigns
|
||||
|
||||
li
|
||||
a(href="/pledges")
|
||||
i.material-icons.black-text person
|
||||
span pledges
|
||||
li
|
||||
.divider
|
||||
li
|
||||
@@ -60,7 +73,7 @@ html(lang="en" dir="ltr")
|
||||
block navigation
|
||||
nav.primary-color
|
||||
.nav-wrapper
|
||||
a.brand-logo.center.hide-on-small-only(href="/") Mantra
|
||||
a.brand-logo.black-text.center.hide-on-small-only(href="/") Mantra
|
||||
ul.left
|
||||
li
|
||||
a.sidenav-trigger(href="#", data-target="slide-out")
|
||||
@@ -79,34 +92,34 @@ html(lang="en" dir="ltr")
|
||||
//- I just need some space at the bottom...
|
||||
|
||||
block footer
|
||||
footer.primary-color.page-footer(role="footer")
|
||||
footer.primary-color.page-footer.z-depth-3(role="footer")
|
||||
.container
|
||||
.row
|
||||
.col.s12.m6
|
||||
p.flow-text Follow
|
||||
p.flow-text.black-text Follow
|
||||
ul
|
||||
li
|
||||
a.white-text(href="https://www.twitter.com/exonumia280") Twitter
|
||||
a.black-text(href="https://www.twitter.com/exonumia280") Twitter
|
||||
li
|
||||
a.white-text(href="https://www.facebook.com/exonumia.africa") Facebook
|
||||
a.black-text(href="https://www.facebook.com/exonumia.africa") Facebook
|
||||
li
|
||||
a.white-text(href="https://www.instagram.com/exonumia.africa") Instagram
|
||||
a.black-text(href="https://www.instagram.com/exonumia.africa") Instagram
|
||||
li
|
||||
a.white-text(href="https://www.linkedin.com/company/exonumia-africa") LinkedIn
|
||||
a.black-text(href="https://www.linkedin.com/company/exonumia-africa") LinkedIn
|
||||
|
||||
.col.s12.m6
|
||||
//- p.flow-text Links
|
||||
//- ul
|
||||
li
|
||||
a.white-text(href="/faq") FAQ
|
||||
a.black-text(href="/faq") FAQ
|
||||
li
|
||||
a.white-text(href="/privacy") Privacy Policy
|
||||
a.black-text(href="/privacy") Privacy Policy
|
||||
li
|
||||
a.white-text(href="/compliance") Compliance
|
||||
a.black-text(href="/compliance") Compliance
|
||||
li
|
||||
a.white-text(href="/terms") Terms
|
||||
a.black-text(href="/terms") Terms
|
||||
li
|
||||
a.white-text(href="/contact") Contact
|
||||
a.black-text(href="/contact") Contact
|
||||
|
||||
.footer-copyright.primary-color-dark
|
||||
.container
|
||||
|
||||
Reference in New Issue
Block a user