Laravel vue won't return meta tags - laravel

I provided meta in my vue-router but it doesn't return in my html
Code
router sample
{
path: '/',
name: 'home',
component: Home,
meta: {
title: 'Home Page - Welcome',
metaTags: [
{
name: 'description',
content: 'The home page of our example app.'
},
{
property: 'og:description',
content: 'The home page of our example app.'
}
]
}
},
{
path: '/login',
name: 'login',
component: Login,
meta: { title: 'Login' }
},
beforeeach
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (localStorage.getItem('myApp.jwt') == null) {
next({
path: '/login',
params: { nextUrl: to.fullPath }
})
} else {
let user = JSON.parse(localStorage.getItem('myApp.user'))
if (to.matched.some(record => record.meta.is_admin)) {
if (user.is_admin == 1) {
next()
}
else {
next({ name: 'userboard' })
}
}
next()
}
} else {
next()
}
});
This is how my page head is look like:
and no sign of meta in browser as well:
Questions
How to fix this issue?
How can I get title for dynamic pages like single post components?
..........

You would need to add a way for the tags to be updated in the DOM as vue-router won't do this for you out-of-the-box.
You could try adding an after hook with something like:
router.afterEach((to, from) => {
document.title = to.meta && to.meta.title ? to.meta.title : ''; // You can add a default here
Array.from(document.querySelectorAll('[data-vue-meta]')).map(el => el.parentNode.removeChild(el));
if (to.meta && to.meta.metaTags) {
to.meta.metaTags.map(tagDef => {
let tag = document.createElement('meta');
Object.keys(tagDef).forEach(key => tag.setAttribute(key, tagDef[key]));
tag.setAttribute('data-vue-meta', '');
return tag;
}).forEach(tag => document.head.appendChild(tag));
}
});

Related

Laravel vue stripe: how to pass client_secret PaymentIntent from clientside to serverside?

I'm using stripe with laravel and vue js. Stripe support told me that I have to implent the paymentIntent function. All the code works fine, the problem is that on the server side I have to pass the client_secre and I dont know how to do it...
Here's the code...
SERVER SCRIPT
\Stripe\Stripe::setApiKey('MY_KEY');
try {
\Stripe\PaymentIntent::create([
'currency' => 'EUR',
'amount' => $request->amount * 100,
'description' => 'Donazione',
'metadata' => [
'customer' => $request->name,
'integration_check' => 'accept_a_payment'
]
]);
CLIENT SIDE SCRIPT
import { Card, createToken } from 'vue-stripe-elements-plus'
export default {
components: { Card },
data () {
return {
complete: false,
errorMessage: '',
stripeOptions: {
// see https://stripe.com/docs/stripe.js#element-options for details
style: {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Raleway", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
},
hidePostalCode: true
}
}
},
methods: {
pay () {
//createToken().then(data => console.log(data.token))
// Instead of creatToken I have to use confirmCardPayment() and pass the client_secret
},
change(event) {
// if (event.error) {
// this.errorMessage = event.error.message;
// } else {
// this.errorMessage = ''
// }
this.errorMessage = event.error ? event.error.message : ''
}
}
}
I recently had to set this up in my platform and here is how I did it. I created a controller called:
PaymentIntentController.php
Stripe::setApiKey(env('STRIPE_SECRET'));
$payment_intent = PaymentIntent::create([
'payment_method_types' => ['card'],
'amount' => $request->invoice['total'] * 100,
'currency' => $this->currency($request),
'receipt_email' => $request->invoice['clients_email']
],
[
'stripe_account' => $request->user['stripe_user_id']
]);
return $payment_intent;
On the client-side, you need to have an Axios request hit this controller so you can get the payment_intent.
Like this:
loadPaymentIntent () {
axios.post('/api/stripe/connect_payment_intent', {'invoice': this.invoice, 'user': this.user}).then((response) => {
this.paymentIntent = response.data
})
},
I have my payment intent setup to load when a checkout form is displayed. Then when the form is submitted we have access to the payment_intent which we can use in the confirmCardPayment method like such:
submit () {
let self = this
self.isLoading = true
self.stripe.confirmCardPayment(self.paymentIntent.client_secret, {
return_url: self.returnUrl + `/clients/${self.invoice.client_id}/invoices/${self.invoice.id}`,
receipt_email: self.invoice.clients_email,
payment_method: {
card: self.card,
billing_details: {
name: self.formData.name,
}
}
}).then(function(result) {
if (result.error) {
self.isLoading = false
self.cardError.status = true
self.cardError.message = result.error.message
setTimeout(() => {
self.cardError = {}
}, 3000)
} else {
if (result.paymentIntent.status === 'succeeded') {
self.handleInvoice(result.paymentIntent)
self.closeModal()
setTimeout(() => {
location.href = self.returnUrl + `/clients/${self.invoice.client_id}/invoices/${self.invoice.id}?success=true`
}, 1000)
}
}
});
},

GraphQL Error: Field "image" must not have a selection since type "String" has no subfields

Description
I am new and learning GatsbyJs/NetlifyCM/GraphQL so I am sorry if I come across as stupid. I, however, have been trying to fix this issue for 5 days now and am at my wits end. I am trying to implement NetlifyCMS into a GatsbyJS blog starter. I seem to have everything working except the images. Graphql is querying the image URL as "img/image.jpg" which is what is set in the MD file, and that works fine. However, when I make a new post with NetlifyCMS it sets the URL to "/img/image.jpg" and graphql can't seem to find it. The extra slash in front causes the error. If I remove the slash OR put a dot in front of the slash, "./img/image.jpg" it builds fine which leads me to believe relative paths may be working? I have read issue 4123, issue 13938, issue 5990, As well as multiple other similar issues and blogs and still, am unable to get it to work. I have tried implementing gatsby-remark-relative-images and gatsby-plugin-netlify-cms but neither seems to fix it.
Any help would be appreciated. I am newer at web development and very new to gatsby/graphql. Thank you.
Steps to reproduce
remove or add a / in front of the image URL in the MD file. Here is a link to the repo to clone/download https://github.com/AaronCuddeback/blog.aaroncuddeback.com
Environment
System:
OS: Windows 10
CPU: (16) x64 Intel(R) Core(TM) i9-9900KF CPU # 3.60GHz
Binaries:
Node: 12.13.0 - C:\Program Files\nodejs\node.EXE
npm: 6.13.0 - ~\AppData\Roaming\npm\npm.CMD
Languages:
Python: 2.7.17 - /usr/bin/python
Browsers:
Edge: 44.18362.387.0
npmPackages:
gatsby: 2.18.2 => 2.18.2
gatsby-image: 2.2.33 => 2.2.33
gatsby-plugin-canonical-urls: 2.1.15 => 2.1.15
gatsby-plugin-emotion: 4.1.15 => 4.1.15
gatsby-plugin-feed: 2.3.21 => 2.3.21
gatsby-plugin-google-analytics: 2.1.28 => 2.1.28
gatsby-plugin-netlify: ^2.1.25 => 2.1.25
gatsby-plugin-netlify-cms: ^4.1.28 => 4.1.28
gatsby-plugin-netlify-cms-paths: ^1.3.0 => 1.3.0
gatsby-plugin-postcss: 2.1.15 => 2.1.15
gatsby-plugin-react-helmet: 3.1.15 => 3.1.15
gatsby-plugin-sharp: 2.3.2 => 2.3.2
gatsby-plugin-sitemap: 2.2.21 => 2.2.21
gatsby-plugin-typescript: 2.1.19 => 2.1.19
gatsby-remark-abbr: 2.0.0 => 2.0.0
gatsby-remark-copy-linked-files: 2.1.30 => 2.1.30
gatsby-remark-images: 3.1.33 => 3.1.33
gatsby-remark-prismjs: 3.3.24 => 3.3.24
gatsby-remark-relative-images: ^0.2.3 => 0.2.3
gatsby-remark-responsive-iframe: 2.2.27 => 2.2.27
gatsby-remark-smartypants: 2.1.16 => 2.1.16
gatsby-source-filesystem: 2.1.38 => 2.1.38
gatsby-transformer-json: 2.2.19 => 2.2.19
gatsby-transformer-remark: 2.6.37 => 2.6.37
gatsby-transformer-sharp: 2.3.5 => 2.3.5
gatsby-transformer-yaml: 2.2.17 => 2.2.17
File Contents:
gatsby-config.js:
const path = require('path');
module.exports = {
siteMetadata: {
title: 'Ghost',
description: 'The professional publishing platform',
siteUrl: 'https://gatsby-casper.netlify.com', // full path to blog - no ending slash
},
mapping: {
'MarkdownRemark.frontmatter.author': 'AuthorYaml',
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/static/img`,
name: 'uploads',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/content`,
name: 'content',
},
},
{
resolve: `gatsby-plugin-netlify-cms-paths`,
options: {
cmsConfig: `/static/admin/config.yml`,
},
},
'gatsby-plugin-sitemap',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
`gatsby-plugin-netlify-cms-paths`,
{
resolve: `gatsby-remark-relative-images`,
options: {
name: 'uploads', // Must match the source name ^
},
},
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 1170,
quality: 90,
},
},
{
resolve: 'gatsby-remark-responsive-iframe',
options: {
wrapperStyle: 'margin-bottom: 1rem',
},
},
'gatsby-remark-prismjs',
'gatsby-remark-copy-linked-files',
'gatsby-remark-smartypants',
'gatsby-remark-abbr',
],
},
},
'gatsby-transformer-json',
{
resolve: 'gatsby-plugin-canonical-urls',
options: {
siteUrl: 'https://gatsby-casper.netlify.com',
},
},
'gatsby-plugin-emotion',
'gatsby-plugin-typescript',
'gatsby-transformer-sharp',
'gatsby-plugin-react-helmet',
'gatsby-transformer-yaml',
'gatsby-plugin-feed',
{
resolve: 'gatsby-plugin-postcss',
options: {
postCssPlugins: [require('postcss-color-function'), require('cssnano')()],
},
},
{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: 'UA-XXXX-Y',
// Puts tracking script in the head instead of the body
head: true,
// IP anonymization for GDPR compliance
anonymize: true,
// Disable analytics for users with `Do Not Track` enabled
respectDNT: true,
// Avoids sending pageview hits from custom paths
exclude: ['/preview/**'],
// Specifies what percentage of users should be tracked
sampleRate: 100,
// Determines how often site speed tracking beacons will be sent
siteSpeedSampleRate: 10,
},
},
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-netlify`,
],
};
gatsby-node.js
const path = require('path');
const _ = require('lodash');
const { fmImagesToRelative } = require('gatsby-remark-relative-images');
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node);
// Sometimes, optional fields tend to get not picked up by the GraphQL
// interpreter if not a single content uses it. Therefore, we're putting them
// through `createNodeField` so that the fields still exist and GraphQL won't
// trip up. An empty string is still required in replacement to `null`.
switch (node.internal.type) {
case 'MarkdownRemark': {
const { permalink, layout, primaryTag } = node.frontmatter;
const { relativePath } = getNode(node.parent);
let slug = permalink;
if (!slug) {
slug = `/${relativePath.replace('.md', '')}/`;
}
// Used to generate URL to view this content.
createNodeField({
node,
name: 'slug',
value: slug || '',
});
// Used to determine a page layout.
createNodeField({
node,
name: 'layout',
value: layout || '',
});
createNodeField({
node,
name: 'primaryTag',
value: primaryTag || '',
});
}
}
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
allMarkdownRemark(
limit: 2000
sort: { fields: [frontmatter___date], order: ASC }
filter: { frontmatter: { draft: { ne: true } } }
) {
edges {
node {
excerpt
timeToRead
frontmatter {
title
tags
date
draft
image {
childImageSharp {
fluid(maxWidth: 3720) {
aspectRatio
base64
sizes
src
srcSet
}
}
publicURL
}
author {
id
bio
avatar {
children {
... on ImageSharp {
fixed(quality: 90) {
src
}
}
}
}
}
}
fields {
layout
slug
}
}
}
}
allAuthorYaml {
edges {
node {
id
}
}
}
}
`);
if (result.errors) {
console.error(result.errors);
throw new Error(result.errors);
}
// Create post pages
const posts = result.data.allMarkdownRemark.edges;
// Create paginated index
const postsPerPage = 6;
const numPages = Math.ceil(posts.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/' : `/${i + 1}`,
component: path.resolve('./src/templates/index.tsx'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
});
});
posts.forEach(({ node }, index) => {
const { slug, layout } = node.fields;
const prev = index === 0 ? null : posts[index - 1].node;
const next = index === posts.length - 1 ? null : posts[index + 1].node;
createPage({
path: slug,
// This will automatically resolve the template to a corresponding
// `layout` frontmatter in the Markdown.
//
// Feel free to set any `layout` as you'd like in the frontmatter, as
// long as the corresponding template file exists in src/templates.
// If no template is set, it will fall back to the default `post`
// template.
//
// Note that the template has to exist first, or else the build will fail.
component: path.resolve(`./src/templates/${layout || 'post'}.tsx`),
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug,
prev,
next,
primaryTag: node.frontmatter.tags ? node.frontmatter.tags[0] : '',
},
});
});
// Create tag pages
const tagTemplate = path.resolve('./src/templates/tags.tsx');
const tags = _.uniq(
_.flatten(
result.data.allMarkdownRemark.edges.map(edge => {
return _.castArray(_.get(edge, 'node.frontmatter.tags', []));
}),
),
);
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag,
},
});
});
// Create author pages
const authorTemplate = path.resolve('./src/templates/author.tsx');
result.data.allAuthorYaml.edges.forEach(edge => {
createPage({
path: `/author/${_.kebabCase(edge.node.id)}/`,
component: authorTemplate,
context: {
author: edge.node.id,
},
});
});
};
exports.onCreateWebpackConfig = ({ stage, actions }) => {
// adds sourcemaps for tsx in dev mode
if (stage === `develop` || stage === `develop-html`) {
actions.setWebpackConfig({
devtool: 'eval-source-map',
});
}
};

Unable to fetch data from API (Resource blocked by client) in Vuex

I'm trying to fetch some data from my API using vuex + axios, but the action give me a "Network Error" (ERR_BLOCKED_BY_CLIENT).
when i was using json-server it works fine, but it doesn't work with my API even with 'Allow-Access-Control-Origin': '*'
actions
const actions = {
async fetchSearch({ commit, state }) {
let res
try {
res = await axios(`http://localhost:8000/api/advertisements/search?column=title&per_page=${state.params.per_page}&search_input=${state.params.query.toLowerCase()}&page=${state.params.page}`, {
method: 'GET',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
}
})
} catch(err) {
console.log(err)
}
commit('clearProducts')
commit('setProducts', res.data)
},
setGlobalParams({ commit }, obj) {
commit('clearParams')
commit('setParams', obj)
}
}
component
<script>
/* Vuex import */
import { mapActions } from 'vuex'
export default {
name: 'base-search-component',
data() {
return {
query_obj: {
page: 1,
per_page: 8,
query: ''
}
}
},
methods: {
...mapActions([
'fetchSearch',
'setGlobalParams'
]),
fetchData() {
if (this.query_obj.query === '') {
return
} else {
this.setGlobalParams(this.query_obj)
this.fetchSearch()
this.$router.push({ name: 'search', params: { query_obj: this.query_obj } })
}
}
}
}
</script>
Assuming your cors issue was properly resolved the reason you cannot access the data is that it is being set before the axios promise is being resolved.
Change:
async fetchSearch({ commit, state }) {
let res
try {
res = await axios(`http://localhost:8000/api/advertisements/search?column=title&per_page=${state.params.per_page}&search_input=${state.params.query.toLowerCase()}&page=${state.params.page}`, {
method: 'GET',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
}
})
} catch(err) {
console.log(err)
}
commit('clearProducts')
commit('setProducts', res.data)
}
to:
async fetchSearch({ commit, state }) {
await axios(`http://localhost:8000/api/advertisements/search?column=title&per_page=${state.params.per_page}&search_input=${state.params.query.toLowerCase()}&page=${state.params.page}`, {
method: 'GET',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
commit('clearProducts')
commit('setProducts', response.data)
}).catch(err) {
console.log(err)
}
}
Further you should use mapState. Assuming setProducts is setting a state object like products this would look like:
<script>
/* Vuex import */
import { mapState, mapActions } from 'vuex'
export default {
name: 'base-search-component',
data() {
return {
query_obj: {
page: 1,
per_page: 8,
query: ''
}
}
},
computed: {
mapState([
'products'
])
},
methods: {
...mapActions([
'fetchSearch',
'setGlobalParams'
]),
fetchData() {
if (this.query_obj.query === '') {
return
} else {
this.setGlobalParams(this.query_obj)
this.fetchSearch()
this.$router.push({ name: 'search', params: { query_obj: this.query_obj } })
}
}
}
}
</script>
Now you can refrence this.products in JS or products in your template.

laravel vue getting info by hidden field

I need to pass logged user id to back-end and I have vuex store so I can get my user info like {{currentUser.id}} the problem is i cannot pass it to back-end it gives me validation error that user_id is required while i have this hidden input in my form
<input type="hidden" name="user_id" :value="currentUser.id">
for normal inputs i have v-model like v-model="project.title" which is not possible to use on hidden fields.
The question here is how can I pass my user_id to back-end?
Code
<script>
import validate from 'validate.js';
export default {
data: function () {
return {
project: {
title: '',
body: '',
attachment: '',
projectclass: '',
deadline: '',
user_id: '',
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
errors: null
}
},
computed: {
currentUser() {
return this.$store.getters.currentUser;
}
},
methods: {
add() {
this.errors = null;
const errors = validate(this.$data.project);
if(errors) {
this.errors = errors;
return;
}
axios.post('/api/projects/new', this.$data.project)
.then((response) => {
this.$router.push('/projects');
});
}
}
}
</script>
This happens because user_id in this.$data.project dosn't get updated.
Instead of having hidden input you can just do
add() {
this.errors = null;
const errors = validate(Object.assign(this.$data.project, {user_id: this.currentUser.id}));
if(errors) {
this.errors = errors;
return;
}
axios.post('/api/projects/new', Object.assign(this.$data.project, {user_id: this.currentUser.id}))
.then((response) => {
this.$router.push('/projects');
});
}

How to structure my states and routing in ui-router

I´m building a e-commerce site using angular1 and ui-router (1.0.0.beta3).
But I´m not sure how to setup this up.
This is what I have in mind.
const home = {
name: 'home',
url: '/',
views: {
header: 'header',
navbar: 'navbar',
sidenav: 'sideNav',
content: 'home'
}
};
const category = {
name: 'home.category',
url: '/{url}',
views: {
content: 'categoryPage'
}
};
const product = {
name: 'home.category.product',
url: '/{url}',
views: {
content: 'productPage'
}
};
we can´t control links that come from the cms it self, "/about-us" and "/category-x" can be a category or a cms-page so we added a route state where we can resolve the entity_type (product, category or cms-page)
.state('home.router', {
url: '/{url}?:{page:int}&:{limit:int}&:id',
params: {
limit: null,
category: null,
page: {
dynamic: true
},
id: {
dynamic: true
}
},
templateProvider: ['urlRewrite', function(urlRewrite) {
switch (urlRewrite.entity_type) {
case 'category':
return '<category-page limit="$stateParams.limit" page="$stateParams.page" category="{name: $resolve.urlRewrite.request_path, id: $resolve.urlRewrite.entity_id}"/>';
case 'product':
return '<product-page id="$resolve.urlRewrite.entity_id"/>';
case 'cms-page':
default:
return '<page url="$resolve.urlRewrite.target_path" />';
}
}],
resolve: {
urlRewrite: ['UrlRewrite', '$stateParams', function(UrlRewrite, $stateParams) {
return UrlRewrite.getUrlRewrite($stateParams.url);
}]
}
});
the problem is that category and route url patterns collide.
and we can´t really use parent/child inherits etc etc..
How should we go about resolving "unknown" urls?
Plunkr: http://plnkr.co/edit/gXzDO5j3arP8QCrpwL9k?p=preview
Let me provide you with the sample snippet. It will give you the idea.
appName.config(['$stateProvider','$urlRouterProvider', '$httpProvider' ,function ($stateProvider, $urlRouterProvider, $httpProvider) {
// $urlRouterProvider.otherwise('/login');
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
var $localStorage = $injector.get('$localStorage');
if($localStorage.user){
return '/dashboard'
}else {
return '/login'
}
});
$stateProvider
.state('login', {
url: '/login',
views: {
'': {
templateProvider: function ($templateFactory, $localStorage) {
return $templateFactory.fromUrl(asset_path('angular/templates/base/login.html'));
},
controller: 'LoginCtrl'
}
}
})
$httpProvider.interceptors.push('Interceptor');
}]);

Resources