I need your help to figure out what's wrong with my code.
I have a HomeLayout devided to 3 sections;
header
side
main
I have a conversation list ; it will be rendered in the main section and it contain a subsection called One_Conversation : when I click on conversation it's rendered
well right here it's ok : my problem that when I click on another conversation , the template won't be rendered
this is my code :
/routes.js
FlowRouter.route('/conversation', {
name: 'Conversation',
action(){
BlazeLayout.render('HomeLayout', {side: 'Messages', main: 'Conversation_list'});
}
});
FlowRouter.route('/conversation/:idConversation', {
name: 'oneConversation',
action(){
BlazeLayout.render('HomeLayout', {side: 'Messages', main: 'Conversation_list', conversation: 'One_conversation'});
}
});
/HomeLayout.html
<template name="HomeLayout">
<div class="app header-fixed sidebar-fixed aside-menu-fixed aside-menu-hidden">
{{> Header}}
<div class="app-body">
<div class="sidebar">
{{> Template.dynamic template=side }}
</div>
<main class="main">
<div class="container-fluid">
{{> Template.dynamic template=main }}
</div>
</main>
</div>
{{> Footer}}
</div>
</template>
/Conversation_list.html
<template name="Conversation_list">
<div class="messages">
////code
////code
////...
</div>
<conversation class="content">
{{> Template.dynamic template=conversation }}
</conversation>
</template>
and finnaly in my Conversation_list.events when I click to conversation
FlowRouter.go('/conversation/:idConversation', {idConversation:this._id});
this is /OneConversation.html Template
<template name="One_conversation">
{{#with oneConversation}}
<div class="contact-profile">
{{#if isDefault}}
<img src="{{contact.profile_pic}}" alt="" />
<p>{{contact.first_name}} {{contact.last_name}}</p>
{{else}}
<img src="http://emilcarlsson.se/assets/harveyspecter.png" alt="" />
<p>{{contactLiveChat.first_name}} {{contactLiveChat.last_name}}</p>
{{/if}}
<div class="social-media">
<i class="fa fa-facebook" aria-hidden="true"></i>
<i class="fa fa-twitter" aria-hidden="true"></i>
<i class="fa fa-instagram" aria-hidden="true"></i>
</div>
</div>
<div class="messages">
{{#each allMessagesOfConversation}}
<ul>
{{#if isFirst}}
<li class="replies">
<img src="http://emilcarlsson.se/assets/harveyspecter.png" alt="" />
<p>{{message}} </p>
</li>
{{else}}
<li class="sent">
<img src="{{contactM.profile_pic}}" alt="" />
<p>{{message}}</p>
</li>
{{/if}}
</ul>
{{/each}}
</div>
<div class="message-input">
<div class="wrap">
<input type="text" placeholder="Write your message..." />
<i class="fa fa-paperclip attachment" aria-hidden="true"></i>
<button class="submit"><i class="fa fa-paper-plane" aria-hidden="true"></i></button>
</div>
</div>
{{/with}}
</template>
and this is /OneConversation.js
import { Template } from ‘meteor/templating’;
import ‘./One_conversation.html’;
import {Meteor} from “meteor/meteor”;
var idConversation=’’;
var typeConversation=’’;
var token=’’;
var psid=’’;
Template.One_conversation.onCreated(function One_conversationOnCreated() {
idConversation = FlowRouter.getParam(“idConversation”);
// typeConversation= Session.get(‘typeConversation’);
//
//
// token= Session.get(‘token’);
// psid= Session.get(‘psid’);
// console.log("OneConversation psid: ",psid);
// console.log("OneConversation token: ",token);
//
// psid= FlowRouter.getParam(“psid”);
// console.log("OneConversation psid liveChat: ",psid);
Meteor.subscribe('allConversations');
Meteor.subscribe('allMessages');
Meteor.subscribe('allContacts');
Meteor.subscribe('allHotels');
Meteor.subscribe('allImMessenger');
});
Template.One_conversation.rendered = function () {
};
Template.One_conversation.helpers({
allMessagesOfConversation: function() {
return Messages.find({idConversation: idConversation},{sort: {createdAt: 1}}).map(function(message) {
if (message.typeMessage ==="1") {
message.isFirst=true;
return message;
}else {
return message;
}
});
},
oneConversation: function() {
return Conversations.findOne({_id: idConversation});
},
});
Template.One_conversation.events({
‘click .submit’(event,instance) {
const message = $(".message-input input").val();
if($.trim(message) === '') {
return false;
}
if(Session.get('typeConversation') ==='facebook'){
Meteor.call("sendMessage",Session.get('token'),Session.get('psid'),message, function (er) {
if (er) {
console.log("send message problem");
console.log(er);
} else {
console.log("message sended successfully");
$(".message-input input").val('');
}
});
}else{
var newMessageInfo = {
idSender: Session.get('psid'),
message: message,
type: "liveChat",
createdAt: new Date(),
idConversation: Session.get('idConversation'),
status: "seen",
typeMessage: '1'
};
Meteor.call('insertMessages', newMessageInfo, function (er) {
if (er) {
console.log("insert problem");
console.log(er);
} else {
console.log("message added successfully", "success");
$(".message-input input").val('');
}
});
}
},
});
what am I doing wrong ?
Well it's fixed , my problem was in the idConversation ;
i set the id of the conversation in a session conversationList.events and get it in the oneConversation Helper so the code become like this
/Conversation_list.js
import { Template } from 'meteor/templating';
import './conversationList.html';
import {Meteor} from "meteor/meteor";
Template.conversationList.onCreated(function conversationListOnCreated() {
Meteor.subscribe('allConversations');
Meteor.subscribe('allMessages');
Meteor.subscribe('allContacts');
Meteor.subscribe('allHotels');
Meteor.subscribe('allImMessenger');
});
var psid="";
var token="";
Template.conversationList.rendered = function () {
};
Template.conversationList.helpers({
toUpperCase: function(str) {
return str.toUpperCase();
},
allMessagesOfConversation: function() {
return Messages.find({idConversation: Session.get('conversationId')},{sort: {createdAt: 1}}).map(function(message) {
if (message.typeMessage ==="1") {
message.isFirst=true;
return message;
}else {
return message;
}
});
},
oneConversation: function() {
return Conversations.findOne({_id: Session.get('conversationId')});
},
allConversations: function() {
return Conversations.find({idHotel: Hotels.findOne({contractId: Meteor.users.findOne({_id: Meteor.userId()}).profile.contractId})._id,archived:false},{sort: {createdAt: -1}}).map(function(conversation, index) {
if (index === 0) {
conversation.isFirst = true;
}
if(conversation.typeConversation === 'facebook'){
conversation.isDefault = true;
}
return conversation;
});
},
});
Template.conversationList.events({
'click #archive'(event){
Meteor.call('archiveConversation',Session.get('idOneConversation'),function (err) {
if(err){
console.log(err);
}else{
FlowRouter.go('/conversation');
}
})
},
'click .contact'(event,instance) {
event.preventDefault();
if($("#contact_active").hasClass("active")){
$("#contact_active").removeClass("active");
$("#contact").addClass("active");
}else{
$("#contact").removeClass("active");
$("#contact_active").addClass("active");
}
Session.set('typeConversation',this.typeConversation);
if(this.typeConversation === 'facebook'){
const pageId = this.idPage;
console.log(pageId);
token = ImMessenger.findOne({pageId: pageId}).pageAccessToken;
Session.set('token', token);
psid = this.contact().idFacebook;
Session.set('psid', psid);
Session.set('conversationId', this._id);
}else{
psid = this.contactLiveChat()._id;
Session.set('idContactLiveChat', psid);
Session.set('conversationId', this._id);
}
FlowRouter.go('/conversation/:idConversation', {idConversation:this._id});
Session.set('idOneConversation',this._id);
},
'click .profile-img'(event) {
event.preventDefault();
$("#status-options").toggleClass("active");
},
'click .expand-button'(event) {
event.preventDefault();
$("#status-options").toggleClass("active");
},
'click .status-options ul li'(event) {
event.preventDefault();
$("#profile-img").removeClass();
$("#status-online").removeClass("active");
$("#status-away").removeClass("active");
$("#status-busy").removeClass("active");
$("#status-offline").removeClass("active");
$(this).addClass("active");
if($("#status-online").hasClass("active")) {
$("#profile-img").addClass("online");
} else if ($("#status-away").hasClass("active")) {
$("#profile-img").addClass("away");
} else if ($("#status-busy").hasClass("active")) {
$("#profile-img").addClass("busy");
} else if ($("#status-offline").hasClass("active")) {
$("#profile-img").addClass("offline");
} else {
$("#profile-img").removeClass();
};
$("#status-options").removeClass("active");
},
});
and this is
/oneConversation.js
import { Template } from 'meteor/templating';
import './oneConversation.html';
import {Meteor} from "meteor/meteor";
var idOneConversation='';
var typeConversation='';
var token='';
var psid='';
Template.oneConversation.onCreated(function oneConversationOnCreated() {
idOneConversation = FlowRouter.getParam("idConversation");
Session.set('idOneConversation',idOneConversation);
Meteor.subscribe('allConversations');
Meteor.subscribe('allMessages');
Meteor.subscribe('allContacts');
Meteor.subscribe('allHotels');
Meteor.subscribe('allImMessenger');
});
Template.oneConversation.rendered = function () {
};
Template.oneConversation.helpers({
allMessagesOfConversation: function() {
return Messages.find({idConversation: Session.get('idOneConversation')},{sort: {createdAt: 1}}).map(function(message) {
if (message.typeMessage ==="1") {
message.isFirst=true;
return message;
}else {
return message;
}
});
},
oneConversation: function() {
return Conversations.findOne({_id: Session.get('idOneConversation')});
},
});
Template.oneConversation.events({
'click #archive'(event){
Meteor.call('archiveConversation',Session.get('idOneConversation'),function (err) {
})
},
'click .submit'(event,instance) {
const message = $(".message-input input").val();
if($.trim(message) === '') {
return false;
}
if(Session.get('typeConversation') ==='facebook'){
Meteor.call("sendMessage",Session.get('token'),Session.get('psid'),message, function (er) {
if (er) {
console.log("send message problem");
console.log(er);
} else {
console.log("message sended successfully");
$(".message-input input").val('');
}
});
}else{
var newMessageInfo = {
idSender: Session.get('idContactLiveChat'),
message: message,
type: "liveChat",
createdAt: new Date(),
idConversation: Session.get('idOneConversation'),
status: "seen",
typeMessage: '1'
};
Meteor.call('insertMessages', newMessageInfo, function (er) {
if (er) {
console.log("insert problem");
console.log(er);
} else {
console.log("message added successfully", "success");
$(".message-input input").val('');
}
});
}
},
});
Related
I'm developing a website that I want to add a infinite scroll feature.
This site is currently using vue3 and vuetify next.
The following code is a component with a default slot.(It's working, but not as I wanted)
Component
<template>
<section>
<slot />
<VRow v-if="data?.loadMoreSection?.showLoadMoreSection" class="mt-10" no-gutters>
<VContainer fluid>
<VRow
v-if="data?.loadMoreSection?.showScrollDownOrLoadMoreText && !data.delayScroll"
>
<VSpacer />
<VCol cols="auto">
<span v-if="data?.loadMoreSection?.loadMoreType">
{{
getText({
...(data?.loadMoreSection?.scrollDownOrLoadMoreText ?? {}),
params: { type: getText(data?.loadMoreSection?.loadMoreType) },
})
}}
</span>
<span v-else>
{{ getText(data?.loadMoreSection?.scrollDownOrLoadMoreText) }}
</span>
</VCol>
<VSpacer />
</VRow>
<VRow v-if="data?.loadMoreSection?.showLoadMoreButton">
<VSpacer />
<VCol cols="auto">
<VBtn #click.stop="onLoadMore">
<span v-if="data?.loadMoreSection?.loadMoreType">
{{
getText({
...(data?.loadMoreSection?.loadMoreButtonText ?? {}),
params: { type: getText(data?.loadMoreSection?.loadMoreType) },
})
}}
</span>
<span v-else>
{{ getText(data?.loadMoreSection?.loadMoreButtonText) }}
</span>
</VBtn>
</VCol>
<VSpacer />
</VRow>
</VContainer>
</VRow>
</section>
</template>
<script setup lang="ts">
import { Events } from "#enums";
import { ILocaleText } from "#models/localeText";
export interface IInfiniteScrollLoaderEvents {
(e: Events.onScroll): void;
(e: Events.onLoadMore): void;
(e: Events.onScrollDelayed): void;
}
export interface IInfiniteScrollLoadMoreSectionProperties {
showLoadMoreSection?: boolean;
showLoadMoreButton?: boolean;
showScrollDownOrLoadMoreText?: boolean;
scrollDownText?: ILocaleText;
loadMoreButtonText?: ILocaleText;
loadMoreType?: ILocaleText;
scrollDownOrLoadMoreText?: ILocaleText;
}
export interface IInfiniteScrollLoaderProperties {
padding?: number;
scrollTimeoutMs?: number;
disableScroll?: boolean;
loadMoreSection?: IInfiniteScrollLoadMoreSectionProperties;
}
interface IInfiniteScrollLoaderData {
delayScroll: boolean;
loadMoreSection?: IInfiniteScrollLoadMoreSectionProperties;
}
const properties = withDefaults(defineProps<IInfiniteScrollLoaderProperties>(), {
padding: 50,
disableScroll: false,
scrollTimeoutMs: 1000,
});
const data = reactive<IInfiniteScrollLoaderData>({
delayScroll: false,
loadMoreSection: {
showLoadMoreSection: true,
showLoadMoreButton: true,
showScrollDownOrLoadMoreText: true,
loadMoreButtonText: {
key: "templates.loadMoreTypeEllipsis",
},
scrollDownOrLoadMoreText: {
key: "templates.scrollDownOrClickToLoadMoreTypeEllipsis",
},
},
});
onMounted(async () => {
await mergeLoadMoreSectionPropertiesWithData();
});
async function mergeLoadMoreSectionPropertiesWithData() {
const merged = {};
Object.assign(merged, data.loadMoreSection, properties.loadMoreSection);
data.loadMoreSection = merged;
}
const emits = defineEmits<IInfiniteScrollLoaderEvents>();
const isValidScroll = computed(
() =>
window.scrollY + window.innerHeight >= document.body.scrollHeight - properties.padding
);
function delayScroll() {
data.delayScroll = true;
setTimeout(() => {
data.delayScroll = false;
emits(Events.onScrollDelayed);
}, properties.scrollTimeoutMs ?? 1000);
}
function isScrollingDown(event: any) {
if (!event) return false;
return !(event.deltaY && event.deltaY < 0);
}
function onLoadMore(event: any) {
emits(Events.onLoadMore);
}
function onScroll(event: any) {
if (properties.disableScroll) return;
if (!isScrollingDown(event)) return;
if (!isValidScroll) return;
if (data.delayScroll) return;
emits(Events.onScroll);
delayScroll();
}
onMounted(async () => {
if (!properties.disableScroll) window.addEventListener("wheel", onScroll);
});
onUnmounted(async () => {
if (!properties.disableScroll) window.removeEventListener("wheel", onScroll);
});
</script>
Usage Example
<template>
<InfiniteScrollLoader
#on-scroll="fetchData"
#on-load-more="fetchData"
:scroll-timeout-ms="5000"
:disable-scroll="data.loading"
:loadMoreSection="props.loadMoreSection"
>
<div>content</div>
</InfiniteScrollLoader>
</template>
<script setup lang="ts">
async function fetchData(){
await setTimeout(()=>console.log('fetched'),5000)
}
</script>
I just want to make this component more smooth. But I'm terrible working with css/animations and etc, I don't have any idea how to implement it.
I am having an issue changing the color of the button using Vue class binding. Its throwing an Uncaught (in promise) TypeError: $options.isActiveClass is not a function. Can anyone help me please?
Here is my code
<template>
<div class="step-wrap">
<button :class="isActiveClass('her')" #click="getButtonActive('her'); isActiveBtn='her' ">For Her</button>
<button :class="isActiveClass('him')" #click="getButtonActive('him'); isActiveBtn='him' ">For Him</button>
<button :class="isActiveClass('kids')" #click="getButtonActive('kids'); isActiveBtn='kids' ">For Kids & Teens</button>
</div>
</template>
Here is My Script code
<script>
export default {
data() {
return {
isActiveBtn:null,
activeClass:'disabled'
}
},
computed:{
isActiveClass(value)
{
if(value === this.isActiveBtn)
{
return 'active';
}
else {
return 'disable'
}
}
}
}
</script>
Untested, but I would try something like this:
<script>
export default {
data() {
return {
isActiveBtn:null,
activeClass:'disabled'
}
},
methods:{
isActiveClass(value) {
return {
active: value === this.isActiveBtn,
disable: value !== this.isActiveBtn
}
}
}
}
</script>
EDIT:
Methods are not reactive, the above only works because it re-renders the template and thus is calling the method again.
A better way would be to remove the method altogether and just use the template like this:
<template>
<div class="step-wrap">
<button :class="{active: isActiveBtn === 'her', disable: isActiveBtn !== 'her'}" #click="isActiveBtn='her'">For Her</button>
<button :class="{active: isActiveBtn === 'him', disable: isActiveBtn !== 'him'}" #click="isActiveBtn='him'">For Him</button>
<button :class="{active: isActiveBtn === 'kids', disable: isActiveBtn !== 'kids'}" #click="isActiveBtn='kids'">For Kids & Teens</button>
</div>
</template>
computed:{
isActiveClass() {
return (value) => {
if(value === this.isActiveBtn)
{
return 'active';
}
else {
return 'disable'
}
}
}
}
I got the user profile from the Realtime database but when I have more than 1 account I get the second user profile too.
Here below you see the data from 2 users. But I want to get the user that is loggend in and that is the currentUser
The ID is the currentUser
This is the Realtime database:
This is my Profile.vue page:
<div class="container" v-for="profileData of profile" :key="profileData['.key']">
<div v-if="seen" class="row">
<div class="col">
<div class="card card-border" style="width: 30rem;">
<div class="card-body">
<h4 class="card-title text-center mb-4">Personal information</h4>
<p class="card-text">ID: {{profileData.CurrentUser}}</p>
<p class="card-text">First name: {{profileData.firstName}}</p>
<p class="card-text">Last name: {{profileData.lastName}}</p>
<p class="card-text">Phone number: {{profileData.phoneNumber}}</p>
<p class="card-text">Adress: {{profileData.adress}}</p>
<p class="card-text">Citizenship: {{profileData.citizenship}}</p>
<p class="card-text">Personal email: {{profileData.personalEmail}}</p>
</div>
</div>
</div>
<div class="col"></div>
<div class="col">
<div class="card card-border" style="width: 30rem;">
<div class="card-body">
<h4 class="card-title text-center mb-3">Business information</h4>
<p>Company name: {{profileData.companyName}}</p>
<p>Chamber Of Commerce Number: {{profileData.chamberOfCommerceNumber}}</p>
<p>Street: {{profileData.street}}</p>
<p>House number: {{profileData.houseNumber}}</p>
<p>ZIP code: {{profileData.zipCode}}</p>
<p>Location: {{profileData.location}}</p>
<p>Company email: {{profileData.companyEmail}}</p>
</div>
</div>
</div>
</div>
</div>
I added a if/else in the created() section below.
And this is the script:
<script>
import firebase from "firebase";
import { db } from '../../config/db';
export default {
data() {
return {
email: "",
password: "",
profileData: [],
isHidden: true,
seen: true,
isLoggedIn: false
}
},
firebase: {
profile: db.ref('profile')
},
methods: {
resetPassword() {
const auth = firebase.auth();
auth.sendPasswordResetEmail(auth.currentUser.email).then(() => {
console.log('Email send');
// Email sent.
}).catch((error) => {
// An error happened.
console.log(error);
});
}
},
created() {
if(firebase.auth().currentUser) {
this.isLoggedIn = true;
this.currentUser = firebase.auth().currentUser.email;
}
var user = firebase.auth().currentUser;
if (this.user == this.profileData.CurrentUser) {
this.seen = true;
} else {
this.seen = false;
}
}
};
</script>
In this Profile.vue page I have the add function:
AddProfile() {
console.log(JSON.stringify(this.profileData) + this.currentUser)
this.$firebaseRefs.profile.push({
firstName: this.profileData.firstName,
lastName: this.profileData.lastName,
phoneNumber: this.profileData.phoneNumber,
adress: this.profileData.adress,
citizenship: this.profileData.citizenship,
personalEmail: this.profileData.personalEmail,
companyName: this.profileData.companyName,
chamberOfCommerceNumber: this.profileData.chamberOfCommerceNumber,
street: this.profileData.street,
houseNumber: this.profileData.houseNumber,
zipCode: this.profileData.zipCode,
location: this.profileData.location,
companyEmail: this.profileData.companyEmail,
CurrentUser: this.currentUser
})
this.profileData.firstName = '';
this.profileData.lastName = '';
this.profileData.phoneNumber = '';
this.profileData.adress = '';
this.profileData.personalEmail = '';
this.profileData.companyName = '';
this.profileData.chamberOfCommerceNumber = '';
this.profileData.street = '';
this.profileData.houseNumber = '';
this.profileData.zipCode = '';
this.profileData.location = '';
this.profileData.companyEmail = '';
this.CurrentUser = '';
window.scrollTo(0,0, 0,0);
console.log('Added to database');
/* Waiting for 2 seconds here */
this.$router.push('/internship')
},
Apparently you are using Vuefire.
As you will see in the Vuefire documentation, by doing
firebase: {
profile: db.ref('profile')
},
you are using a declarative biding on the profile Realtime Database node and therefore it is normal that you get all the children of this node.
If you just want to display the node corresponding to the current user, you could use the programmatic binding, along the following lines:
<template>
<div class="home">
{{user}}
<ol></ol>
</div>
</template>
<script>
import { db } from "../firebase";
const profile = db.ref("profile");
export default {
name: "demo",
data() {
return {
user: null,
id: null
};
},
watch: {
id: {
immediate: true,
handler(id) {
this.$rtdbBind("user", profile.child(id));
}
}
},
created() {
if (firebase.auth().currentUser) {
this.id = currentUser.uid;
}
}
};
</script>
how can i access the doctor attribute from my doctor component ?
Vue.component('specialists', {
template: `
<div>
<div class="list-group-item title">
» {{ name }}
</div>
<doctor class="list-group-item" v-for="doctor in doctors">
<a href="#" class="list-group-item-heading">
{{ doctor.full_name }}
</a>
</doctor>
</div>
`,
props: {
name: {
default: '',
},
},
data: function() {
return {
algoliaClient: null,
algoliaIndex: null,
doctors: [],
};
},
created: function() {
this.algoliaClient = this.$parent.algoliaClient;
this.algoliaIndex = this.algoliaClient.initIndex('medical_doctors');
},
mounted: function() {
this.getDoctors();
},
methods: {
getDoctors: function() {
this.search(this.name);
},
search: function(input) {
var _this = this;
this.algoliaIndex.search(this.name, function(err, hits) {
_this.setDoctors(hits.hits);
});
},
setDoctors: function(data) {
this.doctors = data;
},
},
});
// my doctor component
Vue.component('doctor', {
template: `
<div><slot></slot></div>
`,
data: function() {
return {
doctor: null, // here. how can i pass value to it?
};
},
});
How can i access the doctor attribute from my specialists component ?
I've tried accessing the this.$children from specialists component but the child is null
I'd try something like this :
Vue.component('specialists', {
template: `
<div>
<div class="list-group-item title">
» {{ name }}
</div>
<doctor class="list-group-item" v-for="doctor in doctors" :doctor="doctor">
<a href="#" class="list-group-item-heading">
{{ doctor.full_name }}
</a>
</doctor>
</div>
`,
props: {
name: {
default: '',
},
},
data: function() {
return {
algoliaClient: null,
algoliaIndex: null,
doctors: [],
};
},
created: function() {
this.algoliaClient = this.$parent.algoliaClient;
this.algoliaIndex = this.algoliaClient.initIndex('medical_doctors');
},
mounted: function() {
this.getDoctors();
},
methods: {
getDoctors: function() {
this.search(this.name);
},
search: function(input) {
var _this = this;
this.algoliaIndex.search(this.name, function(err, hits) {
_this.setDoctors(hits.hits);
});
},
setDoctors: function(data) {
this.doctors = data;
},
},
});
// my doctor component
Vue.component('doctor', {
template: `
<div><slot></slot></div>
`,
props: {
doctor: {
default: '',
}
}
});
passing :doctor="doctor" in the for loop of the parent
and adding doctor to props in the child component
props: {
doctor: {
default: '',
},
},
I'm following along TheMeteorChef's Building a SaaS with Meteor: Stripe which is built with blaze templates. Tried to use react instead but I think I failed somewhere along the way. I've gotten to about half of the part 1 of 2 but enough to test if signing up with plan should work or not. Well, it doesn't work but also doesn't give any errors in console... I have very little experience, just started actually, so I'm hoping I could get some help. Thank you.
~/client/helpers/stripe.js
Meteor.startup(function() {
const stripeKey = Meteor.settings.public.stripe.testPublishableKey;
Stripe.setPublishableKey(stripeKey);
STRIPE = {
getToken: function(domElement, card, callback) {
Stripe.card.createToken(card, function(status, response) {
if(response.error) {
Bert.alert(response.error.message, "danger");
} else {
STRIPE.setToken(response.id, domElement, callback);
}
});
},
setToken: function(token, domElement, callback) {
$(domElement).append($('<input type="hidden" name="stripeToken" />').val(token));
callback();
}
}
});
~/client/components/SignUp.jsx
import React, {Component} from 'react';
import PlanSelectForm from '../components/PlanSelectForm.jsx';
import CreditCardForm from '../components/CreditCardForm.jsx';
export default class SignUp extends Component {
componentDidMount() {
$.validator.addMethod('usernameRegex', function(value, element) {
return this.optional(element) || /^[a-zA-Z0-9-_]+$/i.test(value);
}, "Username must contain only letters, numbers, underscores and dashes.");
$('#application-signup').validate({
rules: {
username: {
required: true,
usernameRegex: true,
minlength: 6
},
emailAddress: {
required: true,
email: true
},
password: {
required: true,
minlength: 6
}
},
messages: {
username: {
required: 'You can\'t leave this empty',
usernameRegex: 'You can use letter, numbers, underscores, and dashes.',
minlength: 'Too short. Use at least 6 characters.'
},
emailAddress: {
required: 'You can\'t leave this empty',
email: 'Email is invalid or already taken.'
},
password: {
required: 'You can\'t leave this empty',
minlength: 'Too short. Use at least 6 characters.'
}
},
handleSubmit: function() {
STRIPE.getToken('#application-signup', {
number: $('[data-stripe="cardNumber"]').val(),
exp_month: $('[data-stripe="expMo"]').val(),
exp_year: $('[data-stripe="expYr"]').val(),
cvc: $('[data-stripe="cvc"]').val()
}, function() {
const customer = {
username: $('[name="username"]').val(),
emailAddress: $('[name="emailAddress"]').val(),
password: $('[name="password"]').val(),
plan: $('[name="selectPlan"]:checked').val(),
token: $('[name="stripeToken"]').val()
};
const submitButton = $('input[type="submit"]').button('loading');
Meteor.call('createTrialCustomer', customer, function(error, response) {
if(error) {
alert(error.reason);
submitButton.button('reset');
} else {
if(response.error) {
alert(response.message);
submitButton.button('reset');
} else {
Meteor.loginWithPassword(customer.emailAddress, customer.password, function(error) {
if(error) {
alert(error.reason);
submitButton.button('reset');
} else {
Router.go('/chart');
submitButton.button('reset');
}
});
}
}
});
});
}
});
}
render() {
console.log(this);
return (
<form id="application-signup" className="signup">
<h4>Account details</h4>
<div className="form-group">
<label for="username">Username</label>
<input type="text"
name="username"
className="form-control"
placeholder="Username" />
</div>
<div className="form-group">
<label for="emailAddress">Email Address</label>
<input type="email"
name="emailAddress"
className="form-control"
placeholder="Email Address" />
</div>
<div className="form-group">
<label for="password">Password</label>
<input type="password"
name="password"
className="form-control"
placeholder="Password" />
</div>
<h4 className="page-header">Payment Information</h4>
<label>Which plan sounds <em>amazing</em>?</label>
<PlanSelectForm />
<div className="form-group">
<CreditCardForm />{/* data={signup} /> */}
</div>
<div className="form-group">
<input type="submit"
className="btn btn-success btn-block"
data-loading-text="Setting up your trial..."
value="Put me on the rocketship" />
</div>
</form>
)
}
}
Note: In the tutorial, TheMeteorChef uses a dynamic template for CreditCardForm with data="signup" context. I think he mentions the CC template will be used again after but I haven't gone that far yet. Anyways, I didn't know what "signup" means... so I left it commented out. If you know, please let me know about that as well.
~/client/components/PlanSelectForm.jsx
import React, {Component} from 'react';
export default class PlanSelectForm extends Component {
componentDidMount() {
const firstPlanItem = $('.select-plan a:first-child');
firstPlanItem.addClass('active');
firstPlanItem.find('input').prop('checked', true);
}
plans() {
return Meteor.settings.public.plans;
}
handleClickItem(e) {
const parent = $(e.target).closest('.list-group-item');
console.log(parent);
parent.addClass('active');
$('.list-group-item').not(parent).removeClass('active');
$('.list-group-item').not(parent).find('input[type="radio"]').prop('checked', false);
parent.find('input[type="radio"]').prop('checked', true);
}
render() {
let plans = this.plans();
if(!plans) {
return(<div>loading...</div>);
}
return (
<div className="list-group select-plan">
{plans.map((plan) => {
return (
<a key={plan.id}
href="#"
className="list-group-item"
onClick={this.handleClickItem.bind(this)}>
<input key={plan.id}
type="radio"
ref="selectPlan"
id={`selectPlan_${plan.id}`}
value={plan.name} />
{plan.name} {plan.amount.usd}/{plan.interval}
</a>
)
})}
</div>
)
}
}
~/client/components/CreditCardForm.jsx
import React, {Component} from 'react';
export default class CreditCardForm extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-12">
<div className="form-group">
<label className="text-success">
<i className="fa fa-lock"></i> Card Number
</label>
<input type="text"
data-stripe="cardNumber"
className="form-control card-number"
placeholder="Card Number" />
</div>
</div>
</div>
<div className="row">
<div className="col-xs-4">
<label>Exp. Mo.</label>
<input type="text"
data-stripe="expMo"
className="form-control exp-month"
placeholder="Exp. Mo." />
</div>
<div className="col-xs-4">
<label>Exp. Yr.</label>
<input type="text"
data-stripe="expYr"
className="form-control exp-year"
placeholder="Exp. Yr." />
</div>
<div className="col-xs-4">
<label>CVC</label>
<input type="text"
data-stripe="cvc"
className="form-control cvc"
placeholder="CVC" />
</div>
</div>
</div>
)
}
}
~/server/signup.js
Meteor.methods({
createTrialCustomer: function(customer) {
check(customer, {
name: String,
emailAddress: String,
password: String,
plan: String,
token: String
});
const emailRegex = new RegExp(customer.emailAddress, 'i');
const usernameRegex = new RegExp(customer.username, 'i');
const lookupEmail = Meteor.users.findOne({'emails.address': emailRegex});
const lookupUser = Meteor.users.findOne({'username': usernameRegex});
if(!lookupEmail) {
if(!lookupUser) {
const newCustomer = new Future();
Meteor.call('stripeCreateCustomer', customer.token, customer.emailAddress, function(error, stripeCustomer) {
if(error) {
console.log(error);
} else {
const customerId = stripeCustomer.id,
plan = customer.plan;
Meteor.call('stripeCreateSubscription', customerId, plan, function(error, response) {
if(error) {
console.log(error);
} else {
try {
const user = Accounts.createUser({
username: customer.username,
email: customer.emailAddress,
password: customer.password
});
const subscription = {
customerId: customerId,
subscription: {
plan: {
name: customer.plan,
used: 0
},
payment: {
card: {
type: stripeCustomer.sources.data[0].brand,
lastFour: stripeCustomer.sources.data[0].last4
},
nextPaymentDue: response.current_period_end
}
}
}
Meteor.users.update(user, {
$set: subscription
}, function(error, response) {
if(error) {
console.log(error);
} else {
newCustomer.return(user);
}
});
} catch(exception) {
newCustomer.return(exception);
}
}
});
}
});
return newCustomer.wait();
} else {
throw new Meteor.Error('username-exists', 'Sorry, that username is already active!');
}
} else {
throw new Meteor.Erro('email-exists', 'Sorry, that email is already active!')
}
},
})
~/server/stripe.js
const secret = Meteor.settings.private.stripe.testSecretKey;
const Stripe = StripeAPI(secret);
Meteor.methods({
stripeCreateCustomer: function(token, email) {
check(token, String);
check(email, String);
const stripeCustomer = new Future();
Stripe.customers.create({
source: token,
email: email
}, function(error, customer) {
if(error){
stripeCustomer.return(error);
} else {
stripeCustomer.return(customer);
}
});
return stripeCustomer.wait();
},
stripeCreateSubscription: function(customer, plan) {
check(customer, String);
check(plan, String);
const stripeSubscription = new Future();
Stripe.customers.createSubscription(customer, {
plan: plan
}, function(error, subscription) {
if(error) {
stripeSubscription.return(error);
} else {
stripeSubscription.return(subscription);
}
});
return stripeSubscription.wait();
}
})
packages
"dependencies": {
"meteor-node-stubs": "~0.2.0",
"react": "^15.0.2",
"react-addons-css-transition-group": "^15.0.2",
"react-dom": "^15.0.2",
"react-mounter": "^1.2.0"
},
accounts-base
accounts-password
session
check
random
kadira:flow-router
ultimatejs:tracker-react
meteortoys:allthings
fourseven:scss
fortawesome:fontawesome
themeteorchef:bert
themeteorchef:jquery-validation
momentjs:moment
mrgalaxy:stripe
Thanks for reading, I hope that it wasn't painful.
did you import mrgalaxy:stripe ?
it will be something like
import { StripeAPI } from 'meteor/mrgalaxy:stripe'
I guess.
anw, you can install by npm :
npm install stripe
import Stripe from 'stripe'
then
Stripe('your_secret_key')