Problem with Router Push and Toast Notifications in Laravel and VUE 3 - laravel

I have a problem and it is that when switching between components with router.push the Toast notification is not shown. I have the user edit view and what I want to do is redirect to the user's profile once it has been edited and immediately show the toast notification.
Roughly, I have the following:
Routes.js
import { createRouter, createWebHistory } from 'vue-router';
//Modules
import DashboardIndex from './components/modules/dashboard';
//Users
import UsersIndex from './components/modules/users';
import UsersCreate from './components/modules/users/create';
import UsersEdit from './components/modules/users/edit';
import UsersView from './components/modules/users/view';
export default new createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'dashboard.index', component: DashboardIndex },
{ path: '/users', name: 'users.index', component: UsersIndex},
{ path: '/user/create', name: 'user.create', component: UsersCreate},
{ path: '/user/edit/:id', name: 'user.edit', props: true, component: UsersEdit},
{ path: '/user/:id', name: 'user.view', props: true, component: UsersView},
]
})
Composable/users.js
import axios from 'axios';
import { useRouter } from 'vue-router'
import { useToast } from 'primevue/usetoast';
export default function useUsers() {
const users = ref([])
const user = ref([])
const errors = ref([])
const router = useRouter()
const toast = useToast()
/**
*
* All Users
*
*/
const getUsers = async () => {
const response = await axios.get('/admin/users')
users.value = response.data
}
/**
*
* Create User
*
*/
const storeUser = async (data) => {
try {
let response = await axios.post('/admin/user', data)
await router.push({ name: 'user.view', params: { id: response.data.id } })
} catch (e) {
if (e.response.status === 422) {
errors.value = e.response.data.errors
}
}
}
/**
*
* Create User
*
*/
const updateUser = async (id) => {
try {
//this.showToast = true;
let response = await axios.put(`/admin/user/${id}`, user.value)
router.push({ name: 'user.view', params: { id: response.data.id } })
toast.add({severity:'success', summary: 'Éxito', detail: 'El Usuario ha sido modificado', life: 3000})
} catch (e) {
if (e.response.status === 422) {
errors.value = e.response.data.errors
}
}
}
/**
*
* View User
*
*/
const showUser = async (id) => {
let response = await axios.get(`/admin/user/${id}`)
user.value = response.data
}
/**
*
* Delete User
*
*/
const destroyUser = async (id) => {
await axios.delete(`/admin/user/${id}`)
}
return {errors, users, user, getUsers, storeUser, updateUser, showUser, destroyUser}
}
View.vue
<template>
<div class="container-fluid">
<Toast position="bottom-right"/>
USER PROFILE
</div>
</template>
<script>
import useUsers from '../../../composables/users'
import { onMounted } from 'vue';
export default {
props: {
id: { required: true }
},
setup(props) {
const { user, showUser } = useUsers();
const showUserMounted = async () => {
await showUser(props.id)
}
onMounted(showUserMounted)
return { user }
}
}
</script>
Important fact, I'm using Prime VUE.
And additionally I comment that, when I move to toast.add({severity:'success', summary: 'Success', detail: 'The User has been modified', life: 3000}) inside showUser it is shown well the toast message.
I think what I need is a flag that changes the value in the updateUser method (for example showToast = true) and from the showUser method verify if it is true, if so, execute the toast.add, but I don't know how to do this last.
Thank you very much.

Related

Problems in "Add to Cart" using react redux-toolkit and localstorage

I am adding "add to cart" feature to the eCommerce site using redux-toolkit. I am also using local storage and thunk but it is not working. Products are not added to the cart. Nothing shows on the console. What is wrong with this code?
Here is the CartSlice.js code
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const cartItemsFromStorage = localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [];
const initialState = {
cartItems: cartItemsFromStorage,
};
export const cartSlice = createSlice({
name: "cartPage",
initialState,
reducers: {
addItem(state, action) {
const item = action.payload;
const existItem = state.cartItems.find((x) => x.product === item.product);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
},
},
});
export const { addItem } = cartSlice.actions;
export default cartSlice.reducer;
export function addToCart(id, qty) {
return async function addToCartThunk(dispatch, getState) {
try {
const { data } = await axios.get(`/api/products/${id}`);
dispatch(
addItem({
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
})
);
localStorage.setItem("cartItems", JSON.stringify(getState.cart));
} catch (error) {
console.log(error);
}
};
}
Here is the Store.js code
import { configureStore } from "#reduxjs/toolkit";
import productReducer from "./productSlice";
import productDetailReducer from "./productDetailSlice";
import cartReducer from "./cartSlice";
const store = configureStore({
reducer: {
productList: productReducer,
productDetails: productDetailReducer,
cartPage: cartReducer,
},
});
export default store;
Here is the CartScreen.js code
import React from "react";
import { Link } from "react-router-dom";
import { useEffect } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { addToCart } from "../store/cartSlice";
const EMPTY_CART = { cartItems: [] };
const CartScreen = () => {
const { productId } = useParams();
const location = useLocation();
const qty = location.search ? Number(location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart || EMPTY_CART);
const { cartItems } = cart;
console.log(cartItems);
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, [dispatch, productId, qty]);
return <div>Cart Page</div>;
};
export default CartScreen;

Pass next-auth session to prisma via nexus

I'm wondering on how to pass the next-auth session as context to my nexus queries. The reson behind is that I want the sessions email to retrieve data from my database with nexus. I'm also using Apollo Server and next-connect here.
Here's what I tried:
The Apollo Server
import { ApolloServer } from "apollo-server-micro";
import { MicroRequest } from 'apollo-server-micro/dist/types';
import { ServerResponse } from 'http';
import { getRequestOrigin } from './../../server/get-request-origin';
import handler from "../../server/api-route";
import prisma from "../../server/db/prisma";
import { schema } from "../../server/graphql/schema";
export const config = {
api: {
bodyParser: false,
},
};
export interface GraphQLContext {
session?: {
user: {
name: string
email: string
image: string
},
expires: Date // This is the expiry of the session, not any of the tokens within the session
};
prisma: typeof prisma;
origin: string;
}
const apolloServer = new ApolloServer({
schema,
context: ({ req }): GraphQLContext => ({
session: req.user,
origin: getRequestOrigin(req),
prisma,
}),
})
const startServer = apolloServer.start();
export default handler().use((req: MicroRequest, res: ServerResponse) => {
startServer.then(() => {
apolloServer.createHandler({
path: "/api",
})(req, res);
});
});
My middleware to pass the session:
import { NextApiRequest, NextApiResponse } from "next";
import { Session } from 'next-auth';
import cookieSession from "cookie-session";
import { error } from "next/dist/build/output/log";
import { getSession } from 'next-auth/react';
import nc from "next-connect";
import { trustProxyMiddleware } from "./trust-proxy-middleware";
export interface Request extends NextApiRequest {
user?: Session | null;
}
const COOKIE_SECRET = process.env.COOKIE_SECRET;
/**
* Create an API route handler with next-connect and all the necessary middlewares
*
* #example
* ```ts
* export default handler().get((req, res) => { ... })
* ```
*/
function handler() {
if (!COOKIE_SECRET)
throw new Error(`Please add COOKIE_SECRET to your .env.local file!`);
return (
nc<Request, NextApiResponse>({
onError: (err, _, res) => {
error(err);
res.status(500).end(err.toString());
},
})
// In order for authentication to work on Vercel, req.protocol needs to be set correctly.
// However, Vercel's and Netlify's reverse proxy setup breaks req.protocol, which the custom
// trustProxyMiddleware fixes again.
.use(trustProxyMiddleware)
.use(
cookieSession({
name: "session",
keys: [COOKIE_SECRET],
maxAge: 24 * 60 * 60 * 1000 * 30,
// Do not change the lines below, they make cy.auth() work in e2e tests
secure:
process.env.NODE_ENV !== "development" &&
!process.env.INSECURE_AUTH,
signed:
process.env.NODE_ENV !== "development" &&
!process.env.INSECURE_AUTH,
})
)
.use(async (req: Request, res: NextApiResponse) => {
const session = await getSession({ req })
if (session) {
// Signed in
console.log("Session", JSON.stringify(session, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
})
);
}
export default handler;
And the nexus query
const queries = extendType({
type: "Query",
definition: (t) => {
t.field("currentUser", {
type: "User",
resolve: (_, __, ctx) => {
console.log(ctx);
if (!ctx.session?.user.email) return null;
return prisma.user.findUnique({
where: {
email: ctx.session?.user.email,
},
});
},
});
},
});

[updated]Intergrating NextJS and Redux State Management

this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)

How to redirect another router in Vue3 ? (used next.router in Laravel 8 with vue3)

It does not redirect after successfully logged in.
getting a console error TypeError: Cannot read property 'push' of undefine
Here my code.
I'm creating SPA in vue3 with Laravel 8.
import { ref } from "vue";
import { useRoute } from "vue-router";
export default {
setup() {
const form = ref(
{
email: "hudson.vilma#example.net",
password: "password",
isLoading: false,
},
);
const user = ref("");
function login() {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/login', this.form).then(response => {
this.$router.push('/dashboard')
// useRoute.push('/dashboard');
// this.$router.push({ name: "Dashboard" });
}).catch(error => console.log(error)); // credentials didn't match
});
}
return { form, login, user , useRoute};
},
};
</script>
in app.js instant of vue &
require('./bootstrap');
import { createApp } from "vue";
import App from "./view/App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
Try to use useRouter instead of useRoute and instantiate it like const router =useRouter() in setup function:
import { ref } from "vue";
import { useRouter } from "vue-router";
export default {
setup() {
const router =useRouter()
const form = ref(
{
email: "hudson.vilma#example.net",
password: "password",
isLoading: false,
},
);
const user = ref("");
function login() {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/login', this.form).then(response => {
router.push('/dashboard')
}).catch(error => console.log(error)); // credentials didn't match
});
}
return { form, login, user ,};
},
};
</script>
Note that this couldn't be used in composition API.
You are using this.$router.push('/dashboard') in setup(). This cannot be used in setup(). Instead you can use...
router.push('/dashboard')

unable to see vue component on logged in profile

I have a vue component which can be seen on other users's profile but not on logged in user.
when I visit other user's id i can see that component but when I come back to my id it disappears
profile url is /myplace/{username}
vue component :
<template>
<img src="https://cdn0.iconfinder.com/data/icons/basic-ui-elements-colored/700/09_bell-3-512.png" style="height:50px;margin-top: 30px; margin-left:0px!important;">
</template>
<script>
import $ from 'jquery'
import axios from 'axios'
import Notification from './Notification.vue'
export default {
components: { Notification },
props:['username'],
data: () => ({
total: 0,
notifications: []
}),
mounted () {
this.fetch()
if (window.Echo) {
this.listen()
}
this.initDropdown()
},
computed: {
hasUnread () {
return this.total > 0
}
},
methods: {
/**
* Fetch notifications.
*
* #param {Number} limit
*/
fetch (limit = 5) {
axios.get('/notifications', { params: { limit }})
.then(({ data: { total, notifications }}) => {
this.total = total
this.notifications = notifications.map(({ id, data, created }) => {
return {
id: id,
title: data.title,
body: data.body,
created: created,
action_url: data.action_url
}
})
})
},
in blade view:
<notifications-dropdown :username="{{json_encode($user)}}">
</notifications-dropdown></a>
app.js:
import './bootstrap'
import Vue from 'vue'
import NotificationsDemo from './components/NotificationsDemo.vue'
import NotificationsDropdown from './components/NotificationsDropdown.vue'
new Vue({
el: '#app',
components: {
NotificationsDemo,
NotificationsDropdown
}
})

Resources