Using Nuxt 3 cookies in tailwind.confg file - cookies

What I'm trying to accomplish is to have a variable that a user can update to change the primary color of the app. I can't figure out how to get the tailwind.confg.js file to communicate with my Nuxt app so it know what to set the primary color to. I've been trying to do this with cookies using different cookie libraries, but every time the tailwind.config.js file shows that the cookie is undefined and the browser shows the correct value.
I have tried settings cookies using the build in Nuxt's useCookie, using VueUse's useCookies and using universal-cookies and haven't been able to figure it out. I cannot get the cookie to show up correctly in the tailwind.config.js file. Here are my configs and a copy to my stackblitz
https://stackblitz.com/edit/nuxt-starter-qinx8e?file=app.vue
app.vue
<template>
<div>
<div class="bg-red-600 h-16 mb-4">HELLO WORLD</div>
<div class="bg-primary-600 h-16 mb-4">HELLO WORLD</div>
<button class="bg-blue-200 rounded-lg p-4 mx-2" #click="myColor = 'blue'">
Blue
</button>
<button class="bg-green-200 rounded-lg p-4" #click="myColor = 'green'">
Green
</button>
<body class="mx-2">
My color is: {{ myColor }}
</body>
</div>
</template>
<script setup>
const myColor = useCookie('myColor', {
default: () => 'orange',
watch: true,
});
</script>
tailwind.config.js
/** #type {import('tailwindcss').Config} */
const colors = require('tailwindcss/colors');
// need to figure out how to make this dynamic
const color = 'blue';
exports.darkMode = 'class';
exports.content = [
'./components/**/*.{js,vue,ts}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
'./plugins/**/*.{js,ts}',
'./nuxt.config.{js,ts}',
'./app.vue',
];
if (color === 'blue') {
exports.theme = {
extend: {
colors: {
primary: colors.blue,
},
},
};
}
if (color === 'green') {
exports.theme = {
extend: {
colors: {
primary: colors.green,
},
},
};
}
if (color === 'orange') {
exports.theme = {
extend: {
colors: {
primary: colors.orange,
},
},
};
}

Ihar answered the question, I was thinking about things all wrong and not realizing this would change for the server side, not just the client. I will do my logic in the Vue components to change color just for the client.

Related

vue3 filtered data on computed with chartjs

im learning about vuejs 3 and for example i want to show a chartjs with a combo to filter data. (Without the combo, everything works correctly)
i have a parent component chartjs.vue where import the chartjs and the combo. when i select a year, i change the variable and show it on the template
<script setup>
import ChartJsBarChart from '#/views/charts/chartjs/ChartJsBarChart.vue'
import SelectYear from '#/componentes/selectYear.vue'
const api = ref('exp')
const year = ref([])
function selectYear(x) {
year.value = x
}
</script>
<template>
<!-- 👉 Latest Statistics -->
<VCol cols="24" md="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Choose a year</VCardTitle>
</VCardItem>
<VCardText>
<SelectAno #input="selectAYear" />
</VCardText>
</VCard>
</VCol>
<VCol cols="24" md="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Total</VCardTitle>
</VCardItem>
<VCardText>
<ChartJsBarChart :yearSelected="year" :url="api" />
</VCardText>
</VCard>
</VCol>
</template>
one child component with a select (to try props and emit) selectYear.vue
<script setup>
import { ref, defineEmits, watch } from 'vue'
const emit = defineEmits(['input'])
let selected = ref([])
const years = [
'2005',
'2008',
'2009',
'2010',
'2011',
'2012',
'2014',
'2015',
'2016',
'2017',
'2018',
'2019',
'2020',
'2021',
'2022',
'2023',
]
watch(
() => selected.value,
(newValue, oldValue) => {
change(newValue)
},
)
const change = val => emit('input', val)
</script>
<template>
<VSelect
v-model="selected"
:items="years"
label="choose a year"
/>
</template>
and last child component with the chartjs ChartJsBarChart.vue
<script setup>
import BarChart from '#/libs/chartjs/components/BarChart'
import axios from '#axios'
import { ref, onMounted, computed } from "vue"
import { useStatesStore } from '#/store/states'
const props = defineProps({
url: {
type: null,
required: true,
},
yearSelected: {
type: null,
required: true,
},
})
const apiData = ref([]) //data from API
const data = ref({}) // data parsed to chartjs
const store = useStatesStore()
const states = ref(store.states) // legend
const filterByYear = computed(() => {
return apiData.value.filter(item => item.year === '2005')
})
onMounted(async () => {
await axios
.get('/api/dashboard/expProvActiv')
.then(res => {
apiData.value = res.data['hydra:member']
})
})
</script>
<template>
{{ props.yearSelected }}
<hr>
{{ apiData }}
<BarChart
:height="400"
:chart-data="data"
/>
</template>
the result (shortened) of {{ apiData }} with hundred of items is:
[ { "state": "state 1", "activity": "string", "subactivity": "string", "year": 2015, "surface": "3.5500", "total": 3, "rcs": 3 }, { "state": "state 2", "activity": "string", "subactivity": "string", "year": 2016, "surface": "10.9400", "total": 13, "rcs": 13 }]
if i try to show filterByYear, the first time i have not data and how can i do to call filterByYear when i change the year?
[answer myself] the year was string instead number
how can i update the child component BarChart to update the information?
thank you!

Getting credit card brand and show error message when using hosted fields in Braintree

I am trying to create payment page using braintree's hosted fields.
I have created sandbox account.
But i am not getting additional details like Card brand, error message like Drop in UI.
How to get those functionalities using Hosted fields.
import React from 'react';
var braintree = require('braintree-web');
class BillingComponent extends React.Component {
constructor(props) {
super(props);
this.clientDidCreate = this.clientDidCreate.bind(this);
this.hostedFieldsDidCreate = this.hostedFieldsDidCreate.bind(this);
this.submitHandler = this.submitHandler.bind(this);
this.showPaymentPage = this.showPaymentPage.bind(this);
this.state = {
hostedFields: null,
errorOccurred: false,
};
}
componentDidCatch(error, info) {
this.setState({errorOccurred: true});
}
componentDidMount() {
this.showPaymentPage();
}
showPaymentPage() {
braintree.client.create({
authorization: 'sandbox_xxxxx_xxxxxxx'
}, this.clientDidCreate);
}
clientDidCreate(err, client) {
braintree.hostedFields.create({
onFieldEvent: function (event) {console.log(JSON.stringify(event))},
client: client,
styles: {
'input': {
'font-size': '16pt',
'color': '#020202'
},
'.number': {
'font-family': 'monospace'
},
'.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
'card-brand-id': true,
supportedCardBrands: 'visa'
},
cvv: {
selector: '#cvv',
type: 'password'
},
expirationDate: {
selector: '#expiration-date',
prefill: "12/21"
}
}
}, this.hostedFieldsDidCreate);
}
hostedFieldsDidCreate(err, hostedFields) {
let submitBtn = document.getElementById('my-submit');
this.setState({hostedFields: hostedFields});
submitBtn.addEventListener('click', this.submitHandler);
submitBtn.removeAttribute('disabled');
}
submitHandler(event) {
let submitBtn = document.getElementById('my-submit');
event.preventDefault();
submitBtn.setAttribute('disabled', 'disabled');
this.state.hostedFields.tokenize(
function (err, payload) {
if (err) {
submitBtn.removeAttribute('disabled');
console.error(err);
}
else {
let form = document.getElementById('my-sample-form');
form['payment_method_nonce'].value = payload.nonce;
alert(payload.nonce);
// form.submit();
}
});
}
render() {
return (
<div className="user-prelogin">
<div className="row gutter-reset">
<div className="col">
<div className="prelogin-container">
<form action="/" id="my-sample-form">
<input type="hidden" name="payment_method_nonce"/>
<label htmlFor="card-number">Card Number</label>
<div className="form-control" id="card-number"></div>
<label htmlFor="cvv">CVV</label>
<div className="form-control" id="cvv"></div>
<label htmlFor="expiration-date">Expiration Date</label>
<div className="form-control" id="expiration-date"></div>
<input id="my-submit" type="submit" value="Pay" disabled/>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default BillingComponent;
I am able to get basic functionalities like getting nonce from card details. But i am unable to display card brand image/error message in the page as we show in Drop in UI.
How to show card brand image and error message using hosted fields?
Page created using Hosted fields:
Page created Drop in UI - Which shows error message
Page created Drop in UI - Which shows card brand
Though we do not get exact UI like Drop in UI, we can get the card type and display it ourselves by using listeners on cardTypeChange.
hostedFieldsDidCreate(err, hostedFields) {
this.setState({hostedFields: hostedFields});
if (hostedFields !== undefined) {
hostedFields.on('cardTypeChange', this.cardTypeProcessor);
hostedFields.on('validityChange', this.cardValidator);
}
this.setState({load: false});
}
cardTypeProcessor(event) {
if (event.cards.length === 1) {
const cardType = event.cards[0].type;
this.setState({cardType: cardType});
} else {
this.setState({cardType: null});
}
}
cardValidator(event) {
const fieldName = event.emittedBy;
let field = event.fields[fieldName];
let validCard = this.state.validCard;
// Remove any previously applied error or warning classes
$(field.container).removeClass('is-valid');
$(field.container).removeClass('is-invalid');
if (field.isValid) {
validCard[fieldName] = true;
$(field.container).addClass('is-valid');
} else if (field.isPotentiallyValid) {
// skip adding classes if the field is
// not valid, but is potentially valid
} else {
$(field.container).addClass('is-invalid');
validCard[fieldName] = false;
}
this.setState({validCard: validCard});
}
Got the following response from braintree support team.
Hosted fields styling can be found in our developer documentation. Regarding the logos, you can download them from the card types official websites -
Mastercard
Visa
AMEX
Discover
JCB
Or online from other vendors.
Note: Drop-In UI will automatically fetch the brand logos and provide validation errors unlike hosted fields as it is less customizable.

NuxtJS , Unit Test language picker with Jest and nuxt-i18n

I have a component that switch Language of a nuxtjs application using nuxt-i18n as follows
<template>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link langpicker">{{ $t("language_picker") }} </a>
<div class="navbar-dropdown is-hidden-mobile">
<div>
<nuxt-link
v-if="currentLanguage != 'en'"
class="navbar-item"
:to="switchLocalePath('en')"
>
<img src="~/static/flags/us.svg" class="flagIcon" /> English
</nuxt-link>
<nuxt-link
v-if="currentLanguage != 'el'"
class="navbar-item"
:to="switchLocalePath('el')"
>
<img src="~/static/flags/el.svg" class="flagIcon" /> Ελληνικά
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LangPicker",
computed: {
currentLanguage() {
return this.$i18n.locale || "en";
}
}
};
</script>
I want to write a Unit Test that test the correct language switch on 'nuxt-link' click.
So far I have the following
import { mount, RouterLinkStub } from "#vue/test-utils";
import LangPicker from "#/components/layout/LangPicker";
describe("LangPicker with locale en", () => {
let cmp;
beforeEach(() => {
cmp = mount(LangPicker, {
mocks: {
$t: msg => msg,
$i18n: { locale: "en" },
switchLocalePath: msg => msg
},
stubs: {
NuxtLink: RouterLinkStub
}
});
});
it("Trigger language", () => {
const el = cmp.findAll(".navbar-item")
});
});
cmp.find(".navbar-item") return an empty object.
I don't know how I must set up to "trigger" the click event.
const el = cmp.findAll(".navbar-item")[1].trigger("click");
make sure your find selector is correct.
const comp = cmp.find(".navbar-item");
comp.trigger('click');
you can use chrome dev tools selector utility.
Refer this link for detailed information.

ionic 2 - Using Highchart in the Segment

I use HighChart library in the Segment, my segment have 2 tab DASHBOARD and NEW, my Chart in the DASHBOARD tab. First run: My Chart run, but i click to New tab and come back DASHBOARD tab => My chart not run ?
[sorry, i'm not good english]
-- My code html:
<div class="segment-chart">
<ion-segment [(ngModel)]="pet">
<ion-segment-button value="dashboard" (ionSelect)="selectedFriends()">
DASHBOARD
</ion-segment-button>
<ion-segment-button value="new">
NEW
</ion-segment-button>
</ion-segment>
</div>
<div [ngSwitch]="pet">
<div class="chart" *ngSwitchCase="'dashboard'">
<!--View Chart-->
<div #chart>
<chart type="StockChart" [options]="options"></chart>
</div>
</div>
<ul *ngSwitchCase="'new'" style="list-style-type:none" class="div-new-body">
<li class="div-new-li" *ngFor="let new of lsNews">
<div class="div-new-detail">
<div class="div-new-title">
{{new.date}}
</div>
<div class="div-new-content">
{{new.title}}
</div>
</div>
<div class="div-new-nav">></div>
</li>
</ul>
</div>
My code file ts:
export class ChartPage implements AfterViewInit, OnDestroy {
private _chart: any;
lastData: any
lstData: any = []
pet : any
lsNews : any = []
opts : any;
#ViewChild('chart') public chartEl: ElementRef;
//chartOption: any
// Destroy Chart
ngOnDestroy(): void {
// throw new Error("Method not implemented.");
console.log("OnDestroy run")
var chart = this._chart
chart.destroy();
}
// option Chart
ngAfterViewInit() {
if (this.chartEl && this.chartEl.nativeElement) {
this.opts.chart = {
// type: 'area',
renderTo: this.chartEl.nativeElement,
backgroundColor: {
linearGradient: [0, 0, 500, 500],
stops: [
[0, '#3d4d64'],
[1, '#3d4d64']
]
},
height: '90%',
spacingBottom: 15,
spacingTop: 10,
spacingLeft: 10,
spacingRight: 10,
};
console.log('chart create ss')
this._chart = new Highcharts.StockChart(this.opts);
}
}
constructor(public navCtrl: NavController, public navParams: NavParams, public service: Service) {
const me = this;
this.pet= 'dashboard';
setInterval(function () {
if (me._chart) {
me._chart['series'][0].addPoint([
(new Date()).getTime(), // gia tri truc x
//5// gia tri truc y
me.getData()
],
true,
true);
}
}, 3000);
this.opts = {
credits: {
enabled: false
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150,
labels: {
style: {
color: '#705f43' // color time
}
},
lineColor: '#705f43' // 2 line cuoi mau trang
},
yAxis: {
gridLineColor: '#705f43', //line gach ngang
labels: {
style: {
color: '#fff'
}
},
lineColor: '#ff0000',
minorGridLineColor: '#ff0000',
tickColor: '#fff',
tickWidth: 1,
title: {
style: {
color: '#ff0000'
}
}
},
navigator: {
enabled: false
},
rangeSelector: {
buttons: [{
count: 1,
type: 'minute',
text: '1M'
}, {
count: 5,
type: 'minute',
text: '5M'
}, {
type: 'all',
text: 'All'
}],
inputEnabled: false,
selected: 0,
},
series: [{
name: 'Random data',
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -50; i <= 0; i += 1) {
data.push([
time + i * 1000,
Math.round(Math.random() * 100)
]);
}
return data;
}()),
zones: [{
color: '#f8ad40'
}]
}]
};
//end contructor
}
In case you have not been able to resolve this issue, I had the same issue using ion-segment and I was able to resolve it when I replaced ngSwitch with the [hidden] property.
The problem is that the canvas only get rendered once. The canvas is also lost once you switch between your segments, the only solution is to hide you segment when switching between segments
Edit your HTML code to the one below and you should be okay
<div class="segment-chart">
<ion-segment [(ngModel)]="pet">
<ion-segment-button value="dashboard" (ionSelect)="selectedFriends()">
DASHBOARD
</ion-segment-button>
<ion-segment-button value="new">
NEW
</ion-segment-button>
</ion-segment>
</div>
<div >
<div class="chart" [hidden] = "pet != 'dashboard'">
<!--View Chart-->
<div #chart>
<chart type="StockChart" [options]="options"></chart>
</div>
</div>
<ul [hidden] = "pet != 'new'" style="list-style-type:none" class="div-new-body">
<li class="div-new-li" *ngFor="let new of lsNews">
<div class="div-new-detail">
<div class="div-new-title">
{{new.date}}
</div>
<div class="div-new-content">
{{new.title}}
</div>
</div>
<div class="div-new-nav">></div>
</li>
</ul>
</div>
That should solve the issue.

Vue component testing using Karma: 'undefined is not an object'

I am working on an app which was created with the Vue loader's webpack template.
I included testing with Karma as an option when creating the project, so it was all set up and I haven't changed any of the config.
The app is a Github user lookup which currently consists of three components; App.vue, Stats.vue and UserForm.vue. The stats and form components are children of the containing app component.
Here is App.vue:
<template>
<div id="app">
<user-form
v-model="inputValue"
#go="submit"
:input-value="inputValue"
></user-form>
<stats
:username="username"
:avatar="avatar"
:fave-lang="faveLang"
:followers="followers"
></stats>
</div>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import _ from 'lodash'
import UserForm from './components/UserForm'
import Stats from './components/Stats'
Vue.use(VueAxios, axios)
export default {
name: 'app',
components: {
UserForm,
Stats
},
data () {
return {
inputValue: '',
username: '',
avatar: '',
followers: [],
faveLang: '',
urlBase: 'https://api.github.com/users'
}
},
methods: {
submit () {
if (this.inputValue) {
const api = `${this.urlBase}/${this.inputValue}`
this.fetchUser(api)
}
},
fetchUser (api) {
Vue.axios.get(api).then((response) => {
const { data } = response
this.inputValue = ''
this.username = data.login
this.avatar = data.avatar_url
this.fetchFollowers()
this.fetchFaveLang()
}).catch(error => {
console.warn('ERROR:', error)
})
},
fetchFollowers () {
Vue.axios.get(`${this.urlBase}/${this.username}/followers`).then(followersResponse => {
this.followers = followersResponse.data.map(follower => {
return follower.login
})
})
},
fetchFaveLang () {
Vue.axios.get(`${this.urlBase}/${this.username}/repos`).then(reposResponse => {
const langs = reposResponse.data.map(repo => {
return repo.language
})
// Get most commonly occurring string from array
const faveLang = _.chain(langs).countBy().toPairs().maxBy(_.last).head().value()
if (faveLang !== 'null') {
this.faveLang = faveLang
} else {
this.faveLang = ''
}
})
}
}
}
</script>
<style lang="stylus">
body
background-color goldenrod
</style>
Here is Stats.vue:
<template>
<div class="container">
<h1 class="username" v-if="username">{{username}}</h1>
<img v-if="avatar" :src="avatar" class="avatar">
<h2 v-if="faveLang">Favourite Language: {{faveLang}}</h2>
<h3 v-if="followers.length > 0">Followers ({{followers.length}}):</h3>
<ul v-if="followers.length > 0">
<li v-for="follower in followers">
{{follower}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'stats',
props: [
'username',
'avatar',
'faveLang',
'followers'
]
}
</script>
<style lang="stylus" scoped>
h1
font-size 44px
.avatar
height 200px
width 200px
border-radius 10%
.container
display flex
align-items center
flex-flow column
font-family Comic Sans MS
</style>
And here is UserForm.vue:
<template>
<form #submit.prevent="handleSubmit">
<input
class="input"
:value="inputValue"
#input="updateValue($event.target.value)"
type="text"
placeholder="Enter a GitHub username..."
>
<button class="button">Go!</button>
</form>
</template>
<script>
export default {
props: ['inputValue'],
name: 'user-form',
methods: {
updateValue (value) {
this.$emit('input', value)
},
handleSubmit () {
this.$emit('go')
}
}
}
</script>
<style lang="stylus" scoped>
input
width 320px
input,
button
font-size 25px
form
display flex
justify-content center
</style>
I wrote a trivial test for UserForm.vue which test's the outerHTML of the <button>:
import Vue from 'vue'
import UserForm from 'src/components/UserForm'
describe('UserForm.vue', () => {
it('should have a data-attribute in the button outerHTML', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(UserForm)
})
expect(vm.$el.querySelector('.button').outerHTML)
.to.include('data-v')
})
})
This works fine; the output when running npm run unit is:
UserForm.vue
✓ should have a data-attribute in the button outerHTML
However, when I tried to write a similarly simple test for Stats.vue based on the documentation, I ran into a problem.
Here is the test:
import Vue from 'vue'
import Stats from 'src/components/Stats'
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(Stats).$mount()
vm.username = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.querySelector('.username').textContent).toBe('foo')
done()
})
})
and here is the respective error when running npm run unit:
ERROR LOG: '[Vue warn]: Error when rendering root instance: '
✗ updates the rendered message when vm.message updates
undefined is not an object (evaluating '_vm.followers.length')
I have tried the following in an attempt to get the test working:
Change how the vm is created in the Stats test to be the same as the UserForm test - same error is returned
Test individual parts of the component, for example the textContent of a div in the component - same error is returned
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front? How can I get around this issue to be able to successfully test my component?
(Repo with all code: https://github.com/alanbuchanan/vue-github-lookup-2)
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front?
This piece of code is from the render function that Vue compiled your template into. _vm is a placeholder that gets inserted automatically into all Javascript expressions when vue-loader converts the template into a render function during build - it does that to provide access to the component.
When you do this in your template:
{{followers.length}}
The compiled result in the render function for this piece of code will be:
_vm.followers.length
Now, why does the error happen in the first place? Because you have defined a prop followers on your component, but don't provide any data for it - therefore, the prop's value is undefined
Solution: either you provide a default value for the prop:
// Stats.vue
props: {
followers: { default: () => [] }, // function required to return fresh object
// ... other props
}
Or you propvide acual values for the prop:
// in the test:
const vm = new Vue({
...Stats,
propsData: {
followers: [/* ... actual data*/]
}
}).$mount()