In my project, I am using WordPress woo-commerce as backend and next js as frontend. I am trying to implement stripe payment. I can send line items in stripe sessionData and they show perfectly on the stripe page, how ever when I am trying to send the tax amounts with line items I am getting errors.
import { createCheckoutSession } from "next-stripe/client"; // #see https://github.com/ynnoj/next-stripe
import { loadStripe } from "#stripe/stripe-js";
.....
.....
const createCheckoutSessionAndRedirect = async (orderData) => {
const sessionData = {
success_url:
window.location.origin +
`/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${orderData.orderId}`,
cancel_url: window.location.href,
customer_email: orderData.customer_email,
line_items: getStripeLineItems(orderData.products),
metadata: getMetaData(
orderData.billing,
orderData.shipping,
orderData.orderId
),
payment_method_types: ["card"],
mode: "payment",
total_details:{
amount_discount: 0,
amount_shipping: Math.round(10 * 100),
amount_tax: Math.round(10 * 100),
},
};
console.log("Session from another1:", sessionData);
const session = await createCheckoutSession(sessionData);
console.log("Session from another2:", sessionData);
console.log("from another2:", orderData);
try {
console.log("session data", session);
const stripe = await loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
);
if (stripe) {
stripe.redirectToCheckout({ sessionId: session.id });
}
} catch (error) {
console.log(error);
}
};
After many attempts, I could solve the problem.
This is the controller.
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const stripePaymentService = require('../service/stripePaymentService');
module.exports.checkoutSessionCreate = async (req, res) => {
const responseFromService = await stripePaymentService.checkoutSessionCreate(req.body);
console.log("this is session data", responseFromService);
try{
const session = await stripe.checkout.sessions.create(responseFromService);
res.json({ url: session.url });
console.log("success session:", session.url);
}
catch(e){
res.status(500).json({ error: e.message })
console.log("success session:", e.message);
}
}
This is the service...
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const lodash = require('lodash');
module.exports.checkoutSessionCreate = async (serviceData) => {
// console.log(serviceData.products);
const sessionData = {
success_url:`${process.env.CLIENT_URL}/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${serviceData.orderId}`,
cancel_url: `${process.env.CLIENT_URL}`,
customer_email: serviceData.customer_email,
line_items: await getStripeLineItems(serviceData.products),
metadata: getMetaData(
serviceData.billing,
serviceData.shipping,
serviceData.orderId
),
payment_method_types: ["card"],
mode: "payment",
};
return sessionData;
}
const getMetaData = (billing, shipping, orderId) => {
return {
billing: JSON.stringify(billing),
shipping: JSON.stringify(shipping),
orderId,
};
};
let getStripeLineItems =async (products) => {
if (lodash.isEmpty(products) && !isArray(products)) {
return [];
}
const productData = await Promise.all(
products.map(async (product) => {
const taxArr = await getTaxID(product.tax_data);
return {
quantity: product?.quantity ?? 0,
name: product?.name ?? "",
images: [product?.images ?? ""],
amount: Math.round(product?.amount * 100),
currency: product?.currency ?? "",
tax_rates: taxArr,
};
})
);
return productData;
};
let getTaxID = async (taxData) => {
let idArr = await Promise.all(
taxData.map(async (item)=>{
const taxRate = await stripe.taxRates.create({
display_name: item.display_name,
inclusive: item.inclusive,
percentage: item.percentage,
});
return taxRate?.id;
})
);
return idArr;
}
I hope this will help somebody....
You cannot set total_details directly as you are trying to do. That is a calculated property of the object (ref), not part of the create endpoint (ref).
To add tax to a Checkout session, you should either use automatic taxes with Stripe Tax, or provide explicit tax rates.
A noticed that onUploadProgress from axios are not being called at all on my Nuxt.js Project. After some debugging I found out it is related to the "#nuxtjs/pwa" and "#nuxtjs/firebase" modules. I use firebase auth, and the PWA module uses a service worker to take care of SSR auth and inject the auth token on outgoing requests.
This modules are interfering somehow on the axios onUploadProgress. I use axios to upload files to other Apis.
Once I remove the "#nuxtjs/pwa" module the onUploadProgress from axios gets called normally.
Does anyone have an idea how to fix that?
The versions of the modules:
"#nuxtjs/axios": "^5.13.6",
"#nuxtjs/firebase": "^7.6.1",
"#nuxtjs/pwa": "^3.3.5",
nuxt.config.js
firebase: {
....
services: {
auth: {
ssr: true,
persistence: 'local',
initialize: {
onAuthStateChangedAction: 'auth/onAuthStateChangedAction',
subscribeManually: false,
},
},
firestore: true,
functions: true,
},
}
pwa: {
meta: false,
icon: false,
workbox: {
importScripts: ['/firebase-auth-sw.js'],
dev: process.env.NODE_ENV === 'development',
},
},
Axios upload
const asset = await $axios.post(uploadUrl, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
console.log('onUploadProgress');
const prog = parseInt(
Math.round((progressEvent.loaded / progressEvent.total) * 100)
);
progress(prog);
},
});
the console.log isn't called at all.
firebase-auth-sw
const ignorePaths = ["\u002F__webpack_hmr","\u002F_loading","\u002F_nuxt\u002F"]
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js'
)
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js'
)
firebase.initializeApp({"apiKey":"AIzaSyDUjfwaCRNG72CaPznknOfbNLySkFQvfrs","authDomain":"j-a-developer-web-site.firebaseapp.com","projectId":"j-a-developer-web-site","storageBucket":"j-a-developer-web-site.appspot.com","messagingSenderId":"393360816421","appId":"1:393360816421:web:75c43cac27032d924502cc"})
// Initialize authService
const authService = firebase.auth()
/**
* Returns a promise that resolves with an ID token if available.
* #return {!Promise<?string>} The promise that resolves with an ID token if
* available. Otherwise, the promise resolves with null.
*/
const getIdToken = () => {
return new Promise((resolve) => {
const unsubscribe = authService.onAuthStateChanged((user) => {
unsubscribe()
if (user) {
// force token refresh as it might be used to sign in server side
user.getIdToken(true).then((idToken) => {
resolve(idToken)
}, () => {
resolve(null)
})
} else {
resolve(null)
}
})
})
}
const fetchWithAuthorization = async (original, idToken) => {
// Clone headers as request headers are immutable.
const headers = new Headers()
for (let entry of original.headers.entries()) {
headers.append(entry[0], entry[1])
}
// Add ID token to header.
headers.append('Authorization', 'Bearer ' + idToken)
// Create authorized request
const { url, ...props } = original.clone()
const authorized = new Request(url, {
...props,
mode: 'same-origin',
redirect: 'manual',
headers
})
return fetch(authorized)
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const expectsHTML = event.request.headers.get('accept').includes('text/html')
const isSameOrigin = self.location.origin === url.origin
const isHttps = (self.location.protocol === 'https:' || self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1')
const isIgnored = ignorePaths.some(path => {
if (typeof path === 'string') {
return url.pathname.startsWith(path)
}
return path.test(url.pathname.slice(1))
})
// https://github.com/nuxt-community/firebase-module/issues/465
if (!expectsHTML || !isSameOrigin || !isHttps || isIgnored) {
event.respondWith(fetch(event.request))
return
}
// Fetch the resource after checking for the ID token.
// This can also be integrated with existing logic to serve cached files
// in offline mode.
event.respondWith(
getIdToken().then(
idToken => idToken
// if the token was retrieved we attempt an authorized fetch
// if anything goes wrong we fall back to the original request
? fetchWithAuthorization(event.request, idToken).catch(() => fetch(event.request))
// otherwise we return a fetch of the original request directly
: fetch(event.request)
)
)
})
// In service worker script.
self.addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
sw
const options = {"workboxURL":"https://cdn.jsdelivr.net/npm/workbox-cdn#5.1.4/workbox/workbox-sw.js","importScripts":["/firebase-auth-sw.js"],"config":{"debug":true},"cacheOptions":{"cacheId":"J.A-Developer-Web-Site-dev","directoryIndex":"/","revision":"qIA7lTEhJ6Mk"},"clientsClaim":true,"skipWaiting":true,"cleanupOutdatedCaches":true,"offlineAnalytics":false,"preCaching":[{"revision":"qIA7lTEhJ6Mk","url":"/?standalone=true"}],"runtimeCaching":[{"urlPattern":"/_nuxt/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]},{"urlPattern":"/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]}],"offlinePage":null,"pagesURLPattern":"/","offlineStrategy":"NetworkFirst"}
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
store's index.js
export const state = () => ({});
export const actions = {
async nuxtServerInit({ dispatch, commit }, { res }) {
// initialize the store with user if already authenticated
if (res && res.locals && res.locals.user) {
const {
allClaims: claims,
idToken: token,
...authUser
} = res.locals.user;
await dispatch('auth/onAuthStateChangedAction', {
authUser,
claims,
token,
});
}
},
};
When I try to upload the generated PDF file to storage bucket, the firebase logs gives me that response after log "Init upload of file...":
Function execution took 3721 ms, finished with status: 'connection
error'
Maybe the problem can be the order of Promises. But I'm beginner with Cloud Functions and Node.js to reorder that.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const pdf = require('html-pdf');
const gcs = require('#google-cloud/storage')({keyFilename: './service_account.json'});
const handlebars = require('handlebars');
const path = require('path');
const os = require('os');
const fs = require('fs');
const bucket = gcs.bucket(bucketURL);
admin.initializeApp(functions.config().firebase);
var html = null;
exports.generatePdf = functions.https.onRequest((request, response) => {
// data to apply to template file
const user = {
"date": new Date().toISOString(),
"title": "Voucher",
"body": "Voucher body"
};
const options = {
"format": 'A4',
"orientation": "portrait"
};
const localPDFFile = path.join(os.tmpdir(), 'localPDFFile.pdf');
try {
const source = fs.readFileSync(__dirname + '/voucher.html', 'utf8');
html = handlebars.compile(source)(user);
} catch (error) {
console.error(error);
}
const phantomJsCloud = require("phantomjscloud");
const browser = new phantomJsCloud.BrowserApi(phantomApiKey);
var pageRequest = { content: html, renderType: "pdf" };
// // Send our HTML to PhantomJS to convert to PDF
return browser.requestSingle(pageRequest)
.then(function (userResponse) {
if (userResponse.statusCode !== 200) {
console.log("invalid status code" + userResponse.statusCode);
} else {
console.log('Successfully generated PDF');
// Save the PDF locally
fs.writeFile(localPDFFile, userResponse.content.data, {
encoding: userResponse.content.encoding,
}, function (err) {
console.log('Init upload of file...' + localPDFFile);
// Upload the file to our cloud bucket
return bucket.upload(localPDFFile, {
destination: '/pdfs/voucher.pdf',
metadata: {
contentType: 'application/pdf'
}
}).then(() => {
console.log('bucket upload complete: ' + localPDFFile);
response.status(200).send({
message: 'PDF Gerado com sucesso!',
address: localPDFFile
});
return true;
}).catch(error => {
response.status(400).send({
message: 'Error on bucket upload!',
error: error
});
return false;
});
});
return true;
}
return true;
});
})
I am using Redux Saga to handle async requests. I have tried to go onto to other posts but there were not of help for me
The issue
I have created a route in my file called IndexRoute, my issue is that when it loads the page, it automatically loads my saga function, when it should only be called when the user clicks on a button calling an action creator. Also, when I call an action inside of my index.js, an action is called, however, the saga function is not called. I am very confused, any help will be appreciated thank you
route.js
import { getAsyncInjectors } from 'utils/asyncInjectors';
import globalSagas from 'containers/App/sagas';
import App from 'containers/App';
const errorLoading = (err) => {
console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};
const loadModule = (cb) => (componentModule) => {
cb(null, componentModule.default);
};
export function createRoutes(store, auth) {
// create reusable async injectors using getAsyncInjectors factory
const { injectReducer, injectSagas } = getAsyncInjectors(store);
// injectReducer('global', globalReducer);
injectSagas(globalSagas);
const routes = [
{
path: '/',
name: 'main',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Main/actions'),
System.import('containers/Main/reducer'),
System.import('containers/Main/sagas'),
System.import('containers/Main'),
]);
const renderRoute = loadModule(cb);
importModules.then(([actions, reducer, sagas, component]) => {
injectReducer('main', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
indexRoute: {
path:'/',
name:'posts',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Dashboard/actions'),
System.import('containers/Dashboard/reducer'),
System.import('containers/Dashboard/sagas'),
System.import('containers/Dashboard'),
]);
const renderRoute = loadModule(cb);
importModules.then(([actions, reducer, sagas, component]) => {
injectReducer('posts', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
childRoutes: [
{
path: '/reports',
name: 'Reports List',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Reports/reducer'),
System.import('containers/Reports/sagas'),
System.import('containers/Reports'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('reportsList', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
childRoutes: [
{
path: '/reports/new',
name: 'Create a Report',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Reports/CreateReport'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
],
},
{
path: 'matrixView(/:reportId)',
name: 'Matrix View',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/MatrixView/reducer'),
System.import('containers/MatrixView/sagas'),
System.import('containers/MatrixView'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('matrixView', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
{
path: '/forbidden',
name: 'No Access',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/NoAccess'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
],
},
{
path: '/login',
name: 'login',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Login'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
{
path: '/signup',
name: 'signup',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Signup'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
// injectReducer('signup', reducer.default);
// injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
];
return {
component: App,
// path: '/',
// indexRoute: { onEnter: (nextState, replace) => replace('/account/me') },
childRoutes: routes,
};
}
actions.js
import {
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_SUCCESS,
CREATE_MATRIX_ERROR
} from './constants';
export function createMatrixRequest() {
return { type: CREATE_MATRIX_REQUEST};
}
export function createMatrixSuccess(data) {
return { type: CREATE_MATRIX_SUCCESS, data };
}
export function createMatrixError(error) {
return { type: CREATE_MATRIX_ERROR , error };
}
reducer.js
/*
* The reducer takes care of state changes in our app through actions
*/
import { fromJS } from 'immutable';
import {
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_SUCCESS,
CREATE_MATRIX_ERROR
} from './constants';
// The initial application state
const initialState = fromJS({
success: '',
error: ''
});
// Takes care of changing the application state
function createMatrixReducer(state = initialState, action) {
switch (action.type) {
case CREATE_MATRIX_REQUEST:
console.log('hello')
return state;
case CREATE_MATRIX_SUCCESS:
console.log('second hello')
return state.set('success', action.payload);
case CREATE_MATRIX_ERROR:
return state.set('error', action.payload);
default:
return state;
}
}
export default createMatrixReducer;
sagas.js
import { call, put } from 'redux-saga/effects';
import { createMatrix } from './utils';
import { CREATE_MATRIX_REQUEST, CREATE_MATRIX_SUCCESS, CREATE_MATRIX_ERROR } from './constants';
export function* createMatrixSaga(action) {
console.log('Generator called')
yield put({ type:CREATE_MATRIX_REQUEST});
try {
const data = yield call(createMatrix);
yield put({type: CREATE_MATRIX_SUCCESS, success: data})
} catch (error) {
yield put({type: CREATE_MATRIX_ERROR, error: error })
}
}
export default [
createMatrixSaga,
];
index.js
/*
* Dashboard
*
**/
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { Input, Button } from 'muicss/react';
import { Link } from 'react-router';
import { createMatrixRequest } from './actions';
import { UserIsAuthenticated } from 'config.routes/UserIsAuthenticated';
import styles from './styles.scss';
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
domain: '',
};
this.inputChange = this.inputChange.bind(this);
this.clearInput = this.clearInput.bind(this);
this.createMatrix = this.createMatrix.bind(this);
}
inputChange(event) {
const name = event.target.name;
const value = event.target.value;
this.setState({
[name]: value,
});
}
clearInput(){
this.setState({
domain: ''
})
}
createMatrix(){
this.props.createMatrixRequest();
}
render() {
console.log(this.props, 'This are the props')
return (
<div className={styles.dashboardContainer}>
<div className={styles.dashboardBody}>
<h1>Let's Get Started</h1>
<h5>Begin by entering a domain</h5>
<Input
className={styles.domainInput}
label="Domain Name"
type="text"
name="domain"
value={this.state.domain}
floatingLabel="true"
onChange={this.inputChange}
required
/>
<Button
variant="raised"
type="button"
onClick={this.createMatrix}
disabled={this.state.domain.length === 0}
>
</Button>
<h5 onClick={this.clearInput}><Link>Clear</Link> input</h5>
</div>
</div>
);
}
}
export function mapDispatchToProps(dispatch) {
return {
createMatrixRequest: () => dispatch(createMatrixRequest()),
};
}
function mapStateToProps(state){
return { matrix: state };
}
export default UserIsAuthenticated(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
I also tried adding it to the global sagas file that I import routes.js, but it didn't help
This filed is named the same as my above file, but it stays in another folder called App
sagas.js
// This file contains the sagas used for async actions in our app. It's divided into
// "effects" that the sagas call (`authorize` and `logout`) and the actual sagas themselves,
// which listen for actions.
// Sagas help us gather all our side effects (network requests in this case) in one place
import { take, call, put, race } from 'redux-saga/effects';
import { browserHistory } from 'react-router';
import auth from 'utils/auth';
import { toastr } from 'lib/react-redux-toastr';
import {
SENDING_REQUEST,
LOGIN_REQUEST,
REGISTER_REQUEST,
SET_AUTH,
LOGOUT,
FETCH_USER,
CHANGE_FORM,
REQUEST_ERROR,
SET_USER,
CLEAR_USER,
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_ERROR,
CREATE_MATRIX_SUCCESS
} from './constants';
/**
* Effect to handle authorization
* #param {string} email The email of the user
* #param {string} password The password of the user
* #param {object} options Options
* #param {boolean} options.isRegistering Is this a register request?
*/
export function* authorize({ name, email, password, accountType, isRegistering }) {
// We send an action that tells Redux we're sending a request
yield put({ type: SENDING_REQUEST, sending: true });
// We then try to register or log in the user, depending on the request
try {
// let salt = genSalt(email);
// let hash = hashSync(password, salt);
let response;
// For either log in or registering, we call the proper function in the `auth`
// module, which is asynchronous. Because we're using generators, we can work
// as if it's synchronous because we pause execution until the call is done
// with `yield`!
if (isRegistering) {
response = yield call(auth.register, name, email, password, accountType);
} else {
response = yield call(auth.login, email, password);
}
return response;
} catch (error) {
console.log('hi');
// If we get an error we send Redux the appropiate action and return
yield put({ type: REQUEST_ERROR, error: error.message });
return false;
} finally {
// When done, we tell Redux we're not in the middle of a request any more
yield put({ type: SENDING_REQUEST, sending: false });
}
}
/**
* Effect to handle logging out
*/
export function* logout() {
// We tell Redux we're in the middle of a request
yield put({ type: SENDING_REQUEST, sending: true });
// Similar to above, we try to log out by calling the `logout` function in the
// `auth` module. If we get an error, we send an appropiate action. If we don't,
// we return the response.
try {
const response = yield call(auth.logout);
yield put({ type: SENDING_REQUEST, sending: false });
return response;
} catch (error) {
yield put({ type: REQUEST_ERROR, error: error.message });
return error.message;
}
}
/**
* Log in saga
*/
export function* loginFlow() {
// Because sagas are generators, doing `while (true)` doesn't block our program
// Basically here we say "this saga is always listening for actions"
while (true) {
// And we're listening for `LOGIN_REQUEST` actions and destructuring its payload
const request = yield take(LOGIN_REQUEST);
const { email, password } = request.data;
// A `LOGOUT` action may happen while the `authorize` effect is going on, which may
// lead to a race condition. This is unlikely, but just in case, we call `race` which
// returns the "winner", i.e. the one that finished first
const winner = yield race({
auth: call(authorize, { email, password, isRegistering: false }),
logout: take(LOGOUT),
});
// If `authorize` was the winner...
if (winner.auth) {
// ...we send Redux appropiate actions
yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized)
yield put({ type: SET_USER, user: winner.auth });
yield put({ type: CHANGE_FORM, newFormState: { email: '', password: '' } }); // Clear form
yield call(forwardTo, '/'); // Go to dashboard page
// If `logout` won...
} else if (winner.logout) {
// ...we send Redux appropiate action
yield put({ type: SET_AUTH, newAuthState: false }); // User is not logged in (not authorized)
yield call(logout); // Call `logout` effect
yield call(forwardTo, '/login'); // Go to root page
}
}
}
/**
* Log out saga
* This is basically the same as the `if (winner.logout)` of above, just written
* as a saga that is always listening to `LOGOUT` actions
*/
export function* logoutFlow() {
while (true) {
yield take(LOGOUT);
yield put({ type: SET_AUTH, newAuthState: false });
yield call(logout);
yield put({ type: CLEAR_USER });
yield call(forwardTo, '/login');
toastr.success('Success!', 'You are now logged out.');
}
}
/**
* Get user information saga
*/
export function* getUserFlow() {
while (true) {
yield take(FETCH_USER);
try {
const response = yield call(auth.getUserInfo);
yield put({ type: SET_USER, user: response });
} catch (error) {
yield put({ type: REQUEST_ERROR, error: error.message });
return error.message;
}
}
}
/**
* Register saga
* Very similar to log in saga!
*/
export function* registerFlow() {
while (true) {
// We always listen to `REGISTER_REQUEST` actions
const request = yield take(REGISTER_REQUEST);
const { name, email, password, accountType } = request.data;
// We call the `authorize` task with the data, telling it that we are registering a user
// This returns `true` if the registering was successful, `false` if not
const wasSuccessful = yield call(authorize, {name, email, password, accountType, isRegistering: true });
// If we could register a user, we send the appropiate actions
if (wasSuccessful) {
yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized) after being registered
yield put({ type: CHANGE_FORM, newFormState: { name: '', password: '' } }); // Clear form
yield put({ type: LOGIN_REQUEST, data: { email, password } });
forwardTo('/dashboard'); // Go to dashboard page
}
}
}
// The root saga is what we actually send to Redux's middleware. In here we fork
// each saga so that they are all "active" and listening.
// Sagas are fired once at the start of an app and can be thought of as processes running
// in the background, watching actions dispatched to the store.
export default [
loginFlow,
logoutFlow,
registerFlow,
getUserFlow,
];
// Little helper function to abstract going to different pages
export function* forwardTo(location) {
yield call(browserHistory.push, location);
}
Found an answer, basically I forgot to add a watcher to my saga file, now it works perfectly!
import { call, put } from 'redux-saga/effects';
import { takeEvery } from 'redux-saga/effects'
import { createMatrix } from './utils';
import { CREATE_MATRIX_REQUEST, CREATE_MATRIX_SUCCESS, CREATE_MATRIX_ERROR } from './constants';
export function* createMatrixSaga(action) {
try {
const data = yield call(createMatrix);
yield put({type: CREATE_MATRIX_SUCCESS, success: data})
} catch (error) {
yield put({type: CREATE_MATRIX_ERROR, error: error })
}
}
function* watchFetchData() {
yield takeEvery(CREATE_MATRIX_REQUEST, createMatrixSaga)
}
export default [
watchFetchData,
];