I am developing a function to send email to customers.
exports.sendRestockingNotification = functions.https.onCall((data) => {
try {
console.log("Sending confirmation email to the customer...");
sendEmailRestocking(data.numero, data.nombre, data.emails);
} catch (error) {
console.error("Unexpected error: ", error);
}
});
function sendEmailRestocking (numero, nombre, emails) {
console.log("Sending Email...");
return transport.sendMail({
from: "XXXXX <xxxxxxxxxxxxx#gmail.com>",
to: emails,
subject: "El artículo " + nombre + " (" + numero + ") vuelve a estar disponible.",
html:
`
<div class="container rounded" style="padding: 10px 30px;">
<div class="container rounded" style="background-color: #0bbe83;">
<h1 style="color: white; padding: 5px;">TejidosPulido</h1>
</div>
<div class="row" style="padding: 10px 50px;">
<h5 style="color: #0bbe83;">Hola, </h5>
<p>
El artículo ${nombre} (${numero}) vuelve a estar disponible. ¡Date prisa antes de que se termine!
</p>
</div>
<br>
<p style="padding: 10px 30px;">
Un saludo,
<br>
Tu equipo TejidosPulido
</p>
</div>
`,
});
}
But I am always getting the same in the Firebase Functions console and any mail is sent and the debug message console.log("Sending a confirmation email to the customer..."); is not showed neither:
sendRestockingNotification -> Function execution started
sendRestockingNotification -> Function execution took 14 ms, finished with status code: 204
Have a look at the doc for Callable Cloud Functions. You need to return data that can be JSON encoded.
So you need to wait for the asynchronous sendEmailRestocking() function to terminate before sending a response. For example with the following adaptations:
exports.sendRestockingNotification = functions.https.onCall(async (data) => { // See async
try {
console.log("Sending confirmation email to the customer...");
await sendEmailRestocking(data.numero, data.nombre, data.emails);
return {result: "mail send"}; // For example
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
console.error("Unexpected error: ", error);
}
});
Related
i'm beginner in angular and node i have a material datatable by this code:
<!-- Action Column -->
<ng-container matColumnDef="action" style="width: 30%;">
<th mat-header-cell *matHeaderCellDef > action </th>
<td mat-cell *matCellDef="let list_product">
<button mat-icon-button color="accent" color="primary" (click)="openDialog('Update',list_product)">
<mat-icon aria-label="Example icon-button with a heart icon"> EDIT </mat-icon>
</button>
<button mat-icon-button color="accent" (click)="openDialog('Delete',list_product)">
<mat-icon aria-label="Example icon-button with a heart icon"> DELETE </mat-icon>
</button>
</td>
</ng-container>
list_product is my datasource from backend
for openDialog i have this code :
openDialog(action,obj) {
obj.action = action;
const dialogRef = this.dialog.open(DialogBoxComponent, {
width: '250px',
data:obj
});
dialogRef.afterClosed().subscribe(result => {
if(result.event == 'Add'){
this.addRowData(result.data);
}else if(result.event == 'Update'){
this.updateRowData(result.data);
}else if(result.event == 'Delete'){
this.deleteRowData(result.data);
}
});
deleteRowData is code below:
deleteRowData(row_obj){
// this.dataSource.data = this.dataSource.data.filter((value,key)=>{
// console.log(row_obj);
const idno=row_obj.id;
console.log("idno is :" + row_obj.id);
this.http.post("http://localhost:3000/item_delete",{params: {idno}}).subscribe(
() => {}
);
// return value.id != row_obj.id;
}
back-end code :
app.post("/item_delete",(req,res)=>{
console.log('id number is :' + req.body.idno);
const deletequery="DELETE * FROM captiontbl WHERE id='" + req.body.idno + "')";
console.log(deletequery);
ConnectToDB().query(deletequery,(err,result)=>{
if (err){
console.log("error"+err);
res.sendStatus(500);
return;
}
res.send("deleted" + result);
console.log("record deleted");
res.end();
})
});
problem :
in deleteRowData(row_obj) in ts file the value of row_obj.id is true and sended to back-end but in back-end req.body.idno return undefined value and Delete query not work
You're sending { params: {idno: idno } }
then you will get in the backend server: req.body.params and there is nothing to do with fields.
please update your backend code to:
console.log('id number is :' + req.body.idno);
then will you know what you receive and adjust your code in according.
btw you can shorthand {idno:idno} to {idno} // Shorthand property names (ES2015)
I'm working on a NodeJS/Angular project and while trying to do a simple CRUD, I'm blocked when I try to get an element by ID.
I would like to retrieve all the info of a "Member" based on its ID and display the info in a table. I manage to get my JSON with the API call but when trying to display it in the table, it doesn't show anything.
My service, with the API call :
public getMember(id: number) {
return new Promise((resolve, reject) => {
this.http.get(this.config.apiServer + `astreintes/member/get/${id}`)
.subscribe((res) => {
resolve(res as Member);
console.log(res);
}, err => {
reject(err);
});
});
}
Result of the console.log: My correct JSON with the info of the member
My component.ts :
public search(){
this.memberService.getMember(this.id).then((data) => {
if(data){
this.member = (data as any).recordset;
console.log("Get member :"+ data);
this.indice = true;
}else{
}},
(error) => {
console.log(error);
}
);
}
Result of the console.log: "Get member : [Object Object]"
For the interface, I just have a dropdown list of all my members, and when I select one and click on the button "Search", it gets the info of my member correctly in the console. Then, I want to display it in my table below. My html code:
<form (submit)='search()' #searchMemberForm="ngForm" class="form-horizontal">
<select [(ngModel)]="id" name="member">
<option *ngFor="let member of membersList"
[value]="member.Id_OnCall_Member">{{member.Oncall_Member_Name}}</option>
</select>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-save btn-primary">Search</button>
</div>
</div>
</form>
<div *ngIf="indice">
<h1 style="text-align: center">
Informations
</h1>
<table class="table table-striped" *ngIf="indice">
<th>ID</th>
<th>Nom</th>
<th>Numéro de téléphone</th>
<th>Statut d'activité</th>
<tr *ngFor="let m of member">
<td>{{m.Id_OnCall_Member}}</td>
<td>{{m.Oncall_Member_Name}}</td>
<td>{{m.OnCall_Member_Phone}}</td>
<td>{{m.OnCall_Member_Status}}</td>
</tr>
</table>
</div>
Thanks for your help !
you used "+" in console.log: console.log("Get member :"+ data);
it means javascript is trying to convert the output to one type (string), but data is object. Below you can find how to get the correct output.
const res = {
memberId: 1
}
console.log(res) // {memberId: 1}
console.log('Member: '+ res); //Member: [object Object]
console.log('Member: ', res); // Member: {memberId: 1}
console.log('Member: '+ JSON.stringify(res)); // Member: {memberId: 1}
thanks for your answers.
My problem wasn't the console.log though, it was just an indication.
My real problem was that it didn't show anything in my table. But I solved it, here's how in case it might help other people.
In my component.ts, I replaced:
this.member = (data as any).recordset;
By this, simply:
this.member = data;
I'm using Laravel and VueJs,
I'm trying the following: I 've created a search bar to find users by their names, last name or email.
I used computed to write my filter but I've realized that my filter only filters over the 10 first elements (because I'm using paginate to show all users stored in my database)
...what can I do to make my filter works over all my users instead each ten that gives me paginate (if it's possible keeping paginate, please)?
This is my script and template (thank you very much):
<script>
import UpdateProfile from './users/UpdateProfile';
import CreateUser from './users/CreateUser';
import User from '../models/user';
export default {
components: {UpdateProfile, CreateUser},
data() {
return {
showUpdateModal: false,
showCreateModal: false,
users: [],
user: new User(),
search:'',
paginator: {
current: 1,
total: 1,
limit: 10,
}
}
},
mounted() {
this.goToPage(1);
},
methods: {
userPermissions(user) {
return this.CONSTANTS.getUserType(user.permissions);
},
addUser(user) {
this.showCreateModal = false;
this.api.post('/users', user).then(() => {
this.goToPage(this.paginator.current);
});
},
editUser(user) {
this.user = JSON.parse(JSON.stringify(user));
this.showUpdateModal = true;
},
updateUser(user) {
this.showUpdateModal = false;
this.api.put('/users/' + user.id, user).then(() => {
this.goToPage(this.paginator.current)
});
},
deleteUser(user) {
this.api.delete('/users/' + user.id).then(() => {
this.goToPage(this.paginator.current)
});
},
navigatePrev(page) {
this.goToPage(page)
},
navigateNext(page) {
this.goToPage(page)
},
goToPage(page) {
this.api.get('/users?page=' + page + '&limit=' + this.paginator.limit).then(response => {
this.users = response.data;
this.paginator = response.paginator;
});
}
},
computed:{
filteredUsers: function () {
return this.users.filter((user) => {
var searchByName = user.name.toLowerCase().match(this.search.toLowerCase());
var searchByLastName = user.lastname.toLowerCase().match(this.search.toLowerCase());
var searchByEmail = user.email.toLowerCase().match(this.search.toLowerCase());
if(searchByName){
return searchByName;
}
if(searchByLastName){
return searchByLastName;
}
if(searchByEmail){
return searchByEmail;
}
});
}
}
}
</script>
<template>
<div class="container">
<div class="button is-primary" #click="showCreateModal=true" v-if="CONSTANTS.hasRootPermissions()">
<span class="icon"><i class="fas fa-plus fa-lg"></i></span>
<span>Add User</span>
</div>
<br><br>
<create-user v-if="CONSTANTS.hasRootPermissions()"
:show="showCreateModal"
v-on:save="addUser"
v-on:close="showCreateModal=false"/>
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text" v-model="search" placeholder="Find a user"/>
</div>
<br><br>
<!--Search Users-->
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Admin</th>
<th>Permissions</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers">
<td>{{user.name}}</td>
<td>{{user.lastname}}</td>
<td>{{user.email}}</td>
<td>{{user.isAdmin ? 'yes' : 'no'}}</td>
<td>{{userPermissions(user)}}</td>
<td>
<div class="button is-info" #click="editUser(user)">
<span class="icon"><i class="far fa-edit"></i></span>
<span>Edit</span>
</div>
</td>
<td>
<div class="button is-danger" #click="deleteUser(user)">
<span class="icon"><i class="far fa-trash-alt"></i></span>
<span>Delete</span>
</div>
</td>
</tr>
</tbody>
</table>
<paginator :paginator="paginator" v-on:prev="navigatePrev" v-on:next="navigateNext"/>
<update-profile :data="user" :show="showUpdateModal" v-on:save="updateUser" v-on:close="showUpdateModal=false"/>
</div>
</template>
You can get all your users (if that's not too much data) at start and then paginate them on a clientside.
Something like:
mounted() {
this.api.get('/users').then(response => {
this.users = response.data;
this.paginator.total = Math.ceil(this.users.length / this.paginator.limit);
});
},
methods: {
goToPage(page) {
this.paginator.current = page;
}
},
computed:{
filteredUsers: function () {
return this.users.filter((user) => {
var searchByName = user.name.toLowerCase().match(this.search.toLowerCase());
var searchByLastName = user.lastname.toLowerCase().match(this.search.toLowerCase());
var searchByEmail = user.email.toLowerCase().match(this.search.toLowerCase());
if(searchByName){
return searchByName;
}
if(searchByLastName){
return searchByLastName;
}
if(searchByEmail){
return searchByEmail;
}
}).filter((el, index) => {
return ( index >= (this.paginator.current - 1) * this.paginator.limit
&& index < this.paginator.current * this.paginator.limit);
});
}
}
}
Update
Other option would be to perform serching on a serverside and to send a search string with every page request:
methods: {
goToPage(page) {
this.api.get('/users?page=' + page + '&limit=' + this.paginator.limit
+ '&search=' + this.search).then(response => {
this.users = response.data;
this.paginator = response.paginator;
});
},
performSearch() {
this.goToPage(1);
},
},
}
with search block in a template:
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text"
v-model="search" placeholder="Find a user"
#change="performSearch"/>
</div>
You can add debouncing to get results after you type or add a "search!" button after your search input field to trigger performSearch().
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text"
v-model="search" placeholder="Find a user"/>
<button #click="performSearch">Search!</button>
</div>
I need to add an additional field to my custom form, I want to add the name of the credit card.
I tried in the following way:
var cardNameElement = elements.create('cardName', {
style: style
//, placeholder: 'Custom card number placeholder',
});
cardNameElement.mount('#card-name-element');
<div id="card-name-element" class="field"></div>
But this does not work, in its documentation only allows to perform these procedures validating only four elements or data: cardNumber, cardExpiry, cardCvc, postalCode.
How can I add the name of the credit card and validate it using stripe.js
My code:
var stripe = Stripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
var elements = stripe.elements();
var style = {
base: {
iconColor: '#666EE8',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: 'Helvetica Neue',
fontSize: '15px',
'::placeholder': {
color: '#CFD7E0',
},
},
};
var cardNumberElement = elements.create('cardNumber', {
style: style
//, placeholder: 'Custom card number placeholder',
});
cardNumberElement.mount('#card-number-element');
var cardExpiryElement = elements.create('cardExpiry', {
style: style
});
cardExpiryElement.mount('#card-expiry-element');
var cardCvcElement = elements.create('cardCvc', {
style: style
});
cardCvcElement.mount('#card-cvc-element');
/*var postalCodeElement = elements.create('postalCode', {
style: style
});
postalCodeElement.mount('#postal-code-element');*/
function setOutcome(result) {
var successElement = document.querySelector('.success');
var errorElement = document.querySelector('.error');
successElement.classList.remove('visible');
errorElement.classList.remove('visible');
if (result.token) {
// In this example, we're simply displaying the token
successElement.querySelector('.token').textContent = result.token.id;
successElement.classList.add('visible');
// In a real integration, you'd submit the form with the token to your backend server
//var form = document.querySelector('form');
//form.querySelector('input[name="token"]').setAttribute('value', result.token.id);
//form.submit();
} else if (result.error) {
errorElement.textContent = result.error.message;
errorElement.classList.add('visible');
}
}
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
stripe.createToken(cardNumberElement).then(setOutcome);
});
<script src="https://code.jquery.com/jquery-2.0.2.min.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<form action="" method="POST">
<input type="hidden" name="token" />
<div class="group">
<div class="card-container1">
<label>
<span class="title-card">Card number</span>
<div id="card-number-element" class="field"></div>
<span class="brand"><i class="pf pf-credit-card" id="brand-icon"></i></span>
</label>
</div>
<div class="card-details">
<div class="expiration">
<label>
<span class="title-card">Expiry date</span>
<div id="card-expiry-element" class="field"></div>
</label>
</div>
<div class="cvv">
<label>
<span class="title-card">CVC</span>
<div id="card-cvc-element" class="field"></div>
</label>
</div>
</div>
</div>
<button type="submit">Pay $25</button>
<div class="outcome">
<div class="error"></div>
<div class="success">Success! Your Stripe token is <span class="token"></span></div>
</div>
</form>
What I want to do:
Elements does not support collecting the cardholder's name at the moment. It focuses on collecting:
Card number
Expiration date
CVC
ZIP code (in some countries)
If you want to collect the cardholder's name you have to build your own field for the name and submit it to the API during token creation:
var card_name = document.getElementById('card_name').value;
stripe.createToken(card, {name: card_name}).then(setOutcome);
You can see a live example on jsfiddle here: https://jsfiddle.net/7w2vnyb5/
As I struggled like an idoit on this for a while. As of Feb 2019 you can add tokenData object with information on the details of the card. For Example:
let custData = {
name: 'Firstname Lastname',
address_line1: '21 Great Street',
address_line2: 'Shilloong',
address_city: 'Chicago',
address_state: 'Illinois',
address_zip: '12345',
address_country: 'US'
};
stripe.createToken(card, custData).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
If you're using "PaymentIntents", which you probably should be if you're EU based / SCA compliant, then the format for this has changed again slightly...
stripe.confirmCardPayment(
'{PAYMENT_INTENT_CLIENT_SECRET}',
{
payment_method: {
card: cardElement,
billing_details: {
name: 'Jenny Rosen'
}
}
}
).then(function(result) {
// Handle result.error or result.paymentIntent
});
stripe.confirmCardPayment docs:
https://stripe.com/docs/stripe-js/reference#stripe-confirm-card-payment
billing_details object docs:
https://stripe.com/docs/api/payment_methods/create#create_payment_method-billing_details
I use Meta-Data for custom fields such as cardholder name:
... create({
amount: myAmount,
currency: 'USD,
description: "Put your full discription here",
source: tokenid,
metedata: {any: "set of", key: "values", that: "you want", cardholder: "name"}
},
idempotency_key "my_idempotency_key"
)}
resource: https://stripe.com/docs/payments/charges-api#storing-information-in-metadata
I definitely know that something is wrong with this snippet but can't figure out the right way to get it done. I want to paginate the page where students are displayed. If I put it plainly like this return SchoolStudents.find();, it works perfectly by returning all the students but this defeats the main purpose of pagination. I'm either not sure where the problem is, either in the publish function or the helper function. What I want to achieve is that the records in SchoolStudents colleciton should be paginated to display 2 records on a page.
This is the autorun
Session.setDefault('skip', 0);
Template.view.onCreated(function () {
Session.setPersistent('ReceivedSlug', FlowRouter.getParam('myslug'));
this.autorun(function () {
Meteor.subscribe('SchoolStudents', Session.get('skip'));
});
});
this is the helper method
students(){
let myslug = trimInput(Session.get('ReceivedSlug'));
if (myslug) {
let mySchoolDoc = SchoolDb.findOne({slug: myslug});
if (mySchoolDoc) {
let arrayModuleSchool = StudentSchool.find({schoolId: mySchoolDoc._id});
if (arrayModuleSchool) {
var arrayStudentIds = [];
arrayModuleSchool.forEach(function(studentSchool){
arrayStudentIds.push(studentSchool.studentId);
});
let subReadiness = SchoolStudents.find({_id: {$in: arrayStudentIds}}).fetch();
if (subReadiness) {
return subReadiness;
}
}
}
}
}
This is the publish method
Meteor.publish('SchoolStudents', function (skipCount) {
check(skipCount, Number);
user = Meteor.users.findOne({_id:this.userId})
if(user) {
if(user.emails[0].verified) {
return SchoolStudents.find({userId: this.userId}, {limit: 2, skip: skipCount});
} else {
throw new Meteor.Error('Not authorized');
return false;
}
}
});
Blaze template
<section class="tab-section" id="content4">
{{#each student in students}}
<div class="row" style="margin-top: -20px;">
<!-- Begin Listing: 609 W GRAVERS LN-->
<div class="brdr bgc-fff pad-10 box-shad btm-mrg-20 property-listing card-1">
<div class="media">
<div class="media-body fnt-smaller">
<h4 class="media-heading">{{student.firstname}} {{student.lastname}}</h4>
<p class="hidden-xs" style="margin-bottom: 5px; margin-top: -10px;">{{trimString student.useremail 0 110}}</p><span class="fnt-smaller fnt-lighter fnt-arial">{{course.createdAt}}</span>
</div>
</div>
</div><!-- End Listing-->
</div>
{{/each}}
<ul class="pager">
<li class="studentprevious">Previous </li>
<li class="studentnext">Next </li>
</ul>
</section>
the pagination event
'click .studentprevious': function () {
if (Session.get('skip') > 0 ) {
Session.set('skip', Session.get('skip') - 2 );
}
},
'click .studentnext': function () {
Session.set('skip', Session.get('skip') + 2 );
}