aws amplify withAuth0 HOC: the auth0 client is not configured error - amazon-web-services

Having some trouble following the docs here federated with auth0 to add an extra login button that works to an existing project.
I have added the config object and created the app on the auth0 platform and configured cognito to use it as a federated login, but getting the button to work using the AWS Amplify withAuth0 HOC does not seem to work as expected. Either that or the docs are not clear.
I am getting the following error when clicking the button: the auth0 client is not configured
index.js
import "#babel/polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import AppWithAuth from './appWithAuth';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<AppWithAuth />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.js
import React, { Component } from 'react';
import CssBaseline from '#material-ui/core/CssBaseline';
import { MuiThemeProvider, createMuiTheme, withStyles } from '#material-ui/core/styles';
import { BrowserRouter as Router } from "react-router-dom";
import { MuiPickersUtilsProvider } from '#material-ui/pickers';
import MomentUtils from '#date-io/moment';
import 'typeface-roboto';
import { connect } from 'react-redux';
import { setUserMetaLoaded, setPremiumUser, setAdminUser } from './actions';
import API from './api/appsync';
import Header from './components/header';
import MyRouter from './router';
import { library } from '#fortawesome/fontawesome-svg-core';
import { faLink } from '#fortawesome/free-solid-svg-icons';
import Amplify from 'aws-amplify';
library.add(faLink)
/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
Amplify.configure(AWS_CONFIG);
const theme = createMuiTheme({
typography: { useNextVariants: true },
palette: {
primary: {
light: '#FDCF2A',
main: '#fe9e18',
dark: '#CC690B',
contrastText: '#ffffff',
}
},
});
const styles = theme => ({
root: {
display: 'flex',
flex: 1,
justifyContent: 'center',
padding: 20,
maxWidth: 1000,
margin: '0 auto',
flexDirection: 'column'
},
headerRoot: {
position: 'sticky',
top: 0,
paddingBottom: 3,
zIndex: theme.zIndex.appBar,
backgroundColor: theme.palette.background.default
}
})
class App extends Component {
async componentDidMount() {
const isAdmin = await API...
this.props.dispatch(setAdminUser(isAdmin));
const userMeta = await API...;
if(!userMeta) {
this.props.dispatch(setUserMetaLoaded(true));
this.props.dispatch(setPremiumUser(false));
return;
}
if(userMeta.hasOwnProperty('SubEnd')) {
const now = Math.floor(Date.now() / 1000);
if(userMeta['SubEnd'] > now) {
this.props.dispatch(setPremiumUser(true));
} else {
this.props.dispatch(setPremiumUser(false));
}
}
this.props.dispatch(setUserMetaLoaded(true));
}
render() {
const {classes} = this.props;
if (this.props.authState !== "signedIn") {
return null;
}
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<Router>
<CssBaseline />
<div className={classes.headerRoot}>
<Header />
</div>
<div className={classes.root}>
<MyRouter/>
</div>
</Router>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
export default connect()(
withStyles(styles)(App)
);
appWithAuth.js
import React from "react";
import { SignIn, SignUp, forgotPassword, Greetings } from "aws-amplify-react";
import { default as CustomSignIn } from "./components/login";
import App from "./App";
import { Authenticator } from "aws-amplify-react/dist/Auth";
/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
const config = AWS_CONFIG;
class AppWithAuth extends React.Component {
render() {
return (
<div>
<Authenticator hide={[SignIn, SignUp, forgotPassword, Greetings]} amplifyConfig={config}>
<CustomSignIn />
<App />
</Authenticator>
</div>
);
}
}
export default AppWithAuth;
The component to override the default login page from amplify
components/login/index.js
import React from "react";
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import Link from '#material-ui/core/Link';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import { withStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import { orange } from '#material-ui/core/colors';
import { SignIn } from "aws-amplify-react";
import ButtonAuth0 from "./../buttons/auth0"
const styles = theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1)
},
cognito: {
margin: theme.spacing(3, 0, 2),
color: theme.palette.getContrastText(orange[600]),
backgroundColor: orange[600],
'&:hover': {
backgroundColor: orange[700]
}
}
});
class CustomSignIn extends SignIn {
constructor(props) {
super(props);
this._validAuthStates = ["signIn", "signedOut", "signedUp"];
}
showComponent(theme) {
const {classes} = this.props;
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<ButtonAuth0 />
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="username"
label="Username"
name="username"
autoFocus
onChange={this.handleInputChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
onChange={this.handleInputChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="inherit"
className={classes.cognito}
onClick={(event) => super.signIn(event)}
>
Sign In With Cognito
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2" onClick={() => super.changeState("forgotPassword")}>
Reset password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2" onClick={() => super.changeState("signUp")}>
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
);
}
}
export default (withStyles(styles)(CustomSignIn));
The button component:
components/buttons/auht0.js
import React from 'react';
import { withAuth0 } from 'aws-amplify-react';
import Button from '#material-ui/core/Button';
import { red } from '#material-ui/core/colors';
import { withStyles } from '#material-ui/core';
import { Auth } from 'aws-amplify';
const auth0 = {
"domain": "<...>",
"clientID": "<...>",
"redirectUri": "http://localhost:3000",
"audience": "",
"responseType": "token id_token", // for now we only support implicit grant flow
"scope": "openid profile email", // the scope used by your app
"returnTo": "http://localhost:3000"
};
Auth.configure({
auth0
});
const styles = theme => ({
auth0: {
margin: theme.spacing(3, 0, 2),
color: theme.palette.getContrastText(red[700]),
backgroundColor: red[700],
'&:hover': {
backgroundColor: red[800]
}
}
});
const Auth0Button = (props) => (
<div>
<Button
fullWidth
variant="contained"
color="inherit"
className={props.classes.auth0}
onClick={props.auth0SignIn}
>
Sign In With auth0
</Button>
</div>
)
export default withAuth0(withStyles(styles)(Auth0Button));
Is this supposed to work on its own or do steps 1 to 4 of the docs in the link above still need to be followed?

Answering my own question here. Strangely I had somehow not tried this.
Looking at the source code, I tried passing the auth0 configuration object to the withAuth0 initialisation and got rid of that error message.
export default withAuth0(withStyles(styles)(Auth0Button), auth0);

Related

Vue Composition Api with Vuetify fails unit testing with jest

I try to run unit tests on vue components, the compnents are written with #vue/composition-api package and they also use vuetify.
The Application runs like expected, but when I run the tests I don't have access to the breakpoint property under context.root.$vuetify. When I print the context.root.$vuetify a see the vue component instance.
The error is "Cannot read property 'mdAndDown' of undefined" when i try to access it like that:
context.root.$vuetify.breakpoint.mdAndDown
This is is my jest config file:
module.exports = {
preset: '#vue/cli-plugin-unit-jest/presets/typescript-and-babel',
transform: { '^.*\\.js$': 'babel-jest' },
transformIgnorePatterns: ['node_modules/(?!vue-router|#babel|vuetify)'],
setupFiles: [
"<rootDir>/tests/unit/setup-env.ts",
],
};
This is the Component file:
<template>
<v-app
class="sst-app-container"
>
<cmp-loading
v-show="!loadedBasic"
/>
<div
id="app-wrp"
v-show="loadedBasic"
>
<cmp-side-bar />
<v-content
class="fill-height"
>
<router-view></router-view>
</v-content>
</div>
</v-app>
</template>
<script lang="ts">
import {
computed, createComponent, onMounted, reactive, toRefs, watch,
} from '#vue/composition-api';
import basicSetup from '#/modules/core/composables/basic-setup';
import initMd from '#/modules/core/devices/mouse/composables/init-md';
import store from '#/stores';
import cmpSideBar from '../components/sidebar/CoreSideBar.mouse.vue';
import cmpLoading from '../components/loading/CoreLoading.vue';
export default createComponent({
components: {
cmpLoading,
cmpSideBar,
},
setup(props, context) {
console.log(context.root.$vuetify)
const basics = basicSetup();
return {
...basics,
};
},
});
</script>
This is my test:
import Vue from 'vue';
import Vuetify from 'vuetify';
import BtnCmp from '../../../../../components/vc-btn.vue';
import CmApi from '#vue/composition-api';
import { library } from '#fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
import {
faPlug,
faSignOut,
faCog,
faUser,
faTachometer,
faArrowLeft,
faArrowRight,
} from '#fortawesome/pro-duotone-svg-icons';
library.add(
faPlug,
faSignOut,
faCog,
faUser,
faTachometer,
faArrowLeft,
faArrowRight,
);
import { Breakpoint, Theme, Application, Goto, Icons, Lang, Presets } from 'vuetify/lib/services';
import {
mount,
createLocalVue,
} from '#vue/test-utils';
import Core from '../views/Core.mouse.vue';
const vue = createLocalVue();
vue.component('font-awesome-icon', FontAwesomeIcon);
vue.component('vc-btn', BtnCmp);
vue.use(Vuetify);
vue.use(CmApi);
describe('Core', () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify({
mocks: {
breakpoint: new Breakpoint({
breakpoint: {
scrollBarWidth: 0,
thresholds: {
xs: 0,
sm: 0,
md: 0,
lg: 0,
},
},
},
});
});
it('renders the correct markup', async () => {
// Now mount the component and you have the wrapper
const wrapper = mount(Core, {
localVue: vue,
vuetify,
stubs: ['router-link', 'router-view'],
mocks: {
$t: () => 'some specific text'
},
});
expect(wrapper.html()).toContain('....');
});
});
This can be solved by adding a new instance of vuetify to the wrapper vuetify: new Vuetify()

How to filter elements using React, Graphql and Apollo with es6

I have a React project with a GraphQL using Apollo client. I am trying to figure out how to change the query result based on search text. I implemented query search in backend and its working perfectly.
But I dont know how to set up filter in React using that same query.
Despite there is tutotial on how to filter on https://www.howtographql.com/react-apollo/7-filtering-searching-the-list-of-links/, it doesnt use ES6 and I literaly dont know how to do it. Im stuck on this filter around 10 days.
I will show you my code.
App.js
import React from 'react';
import HeroesDota from './components/HeroesDota';
import Search from './components/HeroSearch'
import { ApolloProvider } from '#apollo/react-hooks';
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
const cache = new InMemoryCache();
const link = new HttpLink({
uri: "http://localhost:8000/graphql/"
});
const client = new ApolloClient({
cache,
link
});
const App = () => {
return (
<ApolloProvider client={client}>
<Search />
<HeroesDota />
</ApolloProvider>
)};
export default App;
HeroesDota.js (compoenent)
import React from 'react'
import gql from "graphql-tag";
import { useQuery } from '#apollo/react-hooks';
import '../index.css'
import styled from 'styled-components';
const Images = styled.img`
margin:0;
border: 3px solid #288eea;
display: inline;
width: 90px;
height: 50px;
`
const HEROES_DOTA2 = gql`
query {
heroes {
name
heroType
image
}
}
`;
const HeroesDota = () => {
const { loading, error, data } = useQuery(HEROES_DOTA2);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.heroes.map(({name, heroType, image }) => (
<div className="row" key={Math.random() + 1}>
<div className="column">
<button className="button-hero"><Images className="hero_images" src= {`${image}`} alt={name}></Images></button>
<div className="hero_info">{name} - {heroType}</div>
</div>
</div>
));
}
export default HeroesDota;
HeroSearch.js (compoenent that doesnt work as I expected)
import React, { useState } from 'react'
import gql from "graphql-tag";
import { withApollo } from 'react-apollo'
import Hero from './HeroesDota'
import '../index.css'
const SEARCH_HEROES = gql`
query ($search: String) {
heroes (search: $search) {
id
name
}
}
`;
const Search = () => {
const [heroes, setHeroes] = useState([])
const [search, setSearch] = useState('')
const _executeSearch = async () => {
const { search } = search
const result = await this.props.client.query({
query: SEARCH_HEROES,
variables: { search },
})
const heroes = result.data.heroes.name
setHeroes({ heroes })
}
return (
<div>
<div>
Search
<input
type='text'
onChange={e => setSearch({ search: e.target.value })}
/>
<button onClick={() => _executeSearch()}>OK</button>
</div>
{heroes.map((hero, index) => (
<Hero key={hero.id} hero={hero} index={index} />
))}
</div>
)
}
export default withApollo(Search)
After U oress OK button to exectue search i get following error.
Unhandled Rejection (ReferenceError): Cannot access 'search' before initialization.
If I try to do something similar like I did in Component HeroesDota i still cant make it.
Does anyone know how to filter query in React using es6, not class based compoenent like they did on this tutorial.
Thanks
const Search = (props) => {
props.client.query()
...
}
or
const Search = ({ client }) => {
client.query()
...
}
Although I would also recommend you use the the new hooks syntax.

Keep user session logged in when page refreshed in vue js

I'm create user login page in vue js and consuming data from django with axios. I have utilized jwt to create token session in client-side
The problem is the session is not saved when the page is refreshed. I have frustated because it. This is my source code :
In '../src/store/modules/auth.js'
import Vue from 'vue'
import Axios from 'axios'
import 'es6-promise/auto'
// In order that Axios work nice with Django CSRF
Axios.defaults.xsrfCookieName = 'csrftoken'
Axios.defaults.xsrfHeaderName = 'X-CSRFToken'
const state = {
authUser: {},
users: [],
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/auth/',
register: 'http://127.0.0.1:8000/signup/'
}
}
const mutations = {
setAuthUser: (state, {
authUser,
isAuthenticated
}) => {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken: (state, newToken) => {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken: (state) => {
localStorage.removeItem('token');
state.jwt = null;
},
}
const actions = {
refreshToken(){
const payload = {
token: this.state.jwt
}
Axios.post(state.endpoints.refreshJWT, payload)
.then((response)=>{
this.commit('updateToken', response.data.token)
})
.catch((error)=>{
console.log(error)
})
}
}
export default {
state,
mutations,
actions,
}
In '../src/store/index.js'
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import auth from './modules/auth'
Vue.use(Vuex)
// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
modules: {
auth
},
})
In '../src/components/login.vue'
<template>
<div class="login">
<form>
<label for="username">Username</label>
<input
type="text"
name="username"
v-model="username"
/><br>
<label for="password">Password</label>
<input
type="password"
name="password"
v-model="password"
/><br>
<input
type="button"
#click="login()"
value="Login"
/>
</form>
</template>
<script>
import axios from 'axios'
/* eslint-disable */
export default {
name: 'Login',
data(){
return {
username: '',
password: ''
}
},
methods: {
login(){
const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.auth.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
this.$store.commit('setAuthUser',
{
authUser: response.data,
isAuthenticated: true
}
)
this.$router.push({path: 'dashboard-user/id/list-vendor'})
})
.catch((error) => {
//NOTE: erase this when production
console.log(error);
console.debug(error);
console.dir(error);
alert("The username or password is incorrect");
})
}
}
}
</script>
In 'main.js'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import 'tachyons'
import routes from './routes'
import './styles.css'
import store from '#/store'
Vue.config.productionTip = false
Vue.use(VueRouter)
import '#/assets/fonts/all.css';
const router = new VueRouter({
mode: 'history',
routes
})
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Since use register auth as a module, you should use store.state.auth.jwt instead of store.state.jwt
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.auth.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.auth.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})

Vue.js w vuex : mocked action not executed

I am trying to test the following App.vue component when a click event is fired on the logout vue-router link...
App.vue
<template>
<div id="app">
<header id="header">
<nav>
<ul class="navigation">
<li id="home"><router-link :to="{ name: 'home' }">Home</router-link></li>
<li id="login" v-if="!isAuthenticated"><router-link :to="{ name: 'login' }">Login</router-link></li>
<li id="shoppinglists" v-if="isAuthenticated"><router-link :to="{ name: 'shoppinglists' }" >Shopping Lists</router-link></li>
<li id="logout" v-if="isAuthenticated">Logout</li>
</ul>
</nav>
</header><!-- /#header -->
<section id="page">
<router-view></router-view>
</section><!-- /#page -->
</div>
</template>
<script>
import store from '#/vuex/store'
import router from '#/router/index'
import { mapGetters } from 'vuex'
export default {
name: 'app',
computed: {
...mapGetters({ isAuthenticated: 'isAuthenticated' })
},
methods: {
logout () {
this. $store.dispatch('logout')
.then(() => {
window.localStorage.removeItem('vue-authenticate.vueauth_token')
this/$router.push({ name: 'home' })
})
}
},
store,
router
}
</script>
To test the logout click, I preset the isAuthenticated state to true, so the logout router link show up and I trigger the click event on it.
LOG: 'navigation: ', <ul class="navigation"><li id="home">
Home</li> <!---->
<li id="shoppinglists">Shopping Lists
</li> <li id="logout">Logout</li></ul>
I expect the action logout to have been called .. but it's not ... why ?
App.spec.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
beforeEach(() => {
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sinon.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
actions,
state: {
isAuthenticated: true,
currentUserId: ''
}
})
router
})
it('calls logout method', () => {
const wrapper = mount(App, { router, store })
console.log('navigation: ', wrapper.find('ul.navigation')[0].element)
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
expect(actions.logout.calledOnce).to.equal(true)
})
})
vuex/actions.js
import { IS_AUTHENTICATED, CURRENT_USER_ID } from './mutation_types'
import getters from './getters'
export default {
logout: ({commit}) => {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return true
}
}
vuex/mutations.js
import * as types from './mutation_types'
import getters from './getters'
export default {
[types.IS_AUTHENTICATED] (state, payload) {
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
state.currentUserId = payload.currentUserId
}
}
vuex/getters.js
export default {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
Finally , I found a way to test it :
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
let sandbox
let routerPush
beforeEach(() => {
sandbox = sinon.sandbox.create()
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
state: {
isAuthenticated: true,
currentUserId: ''
},
actions
})
router
})
afterEach(() => {
sandbox.restore()
})
it('calls logout method', (done) => {
const wrapper = mount(App, { store, router })
routerPush = sandbox.spy(wrapper.vm.$router, 'push')
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
wrapper.vm.$nextTick(() => {
expect(actions.logout.calledOnce).to.equal(true)
actions.logout().then(() => {
expect(routerPush).to.have.been.calledWith('/home')
})
done()
})
})
})

Ionic2 NativeStorage can't getItem(user)

I have an ionic 2 app and am using native FB Login to retrieve name/pic and saving it to NativeStorage. The flow is that I open WelcomePage, log in, and save the data. From there, navPush to HomePage. So far it works great.
However, I have a ProfilePage (accessible via tabRoot), the fails. The reason is that in my profile.html I have the following tag that should render Username (this works on HomePage, but not on ProfilePage):
{{ user.name }}
The error I get on XCode is:
2017-05-02 18:40:41.657374 FoxBox App[1102:226159] ERROR: Failed to navigate: undefined is not an object (evaluating 'co.user.picture')
Note that for some reason it prepends it with 'co.' which I have no idea where its coming from or what it means.
Here is the WelcomePage code:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { HomePage } from '../home/home';
import { AboutPage } from '../about/about';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
import {GoogleAnalytics} from 'ionic-native';
#Component({
selector: 'page-welcome',
templateUrl: 'welcome.html'
})
export class WelcomePage {
FB_APP_ID: number = 1234567890;
homePage = HomePage;
aboutPage = AboutPage;
constructor(
public navCtrl: NavController,
//public facebookAuth: FacebookAuth,
//public auth: Auth,
//public user: User,
) {
Facebook.browserInit(this.FB_APP_ID, "v2.8");
}
doFbLogin(){
//alert("fb is logged in");
let permissions = new Array();
let nav = this.navCtrl;
//the permissions your facebook app needs from the user
permissions = ["public_profile"];
Facebook.login(permissions)
.then(function(response){
let userId = response.authResponse.userID;
let params = new Array();
//Getting name and gender properties
Facebook.api("/me?fields=name,gender", params)
.then(function(user) {
user.picture = "https://graph.facebook.com/" + userId + "/picture?type=large";
//now we have the users info, let's save it in the NativeStorage
NativeStorage.setItem('user',
{
name: user.name,
gender: user.gender,
picture: user.picture,
email: user.email,
})
.then(function(){
nav.push(HomePage);
console.log("User Data Stored");
}, function (error) {
console.log(error);
})
})
}, function(error){
console.log(error);
});
}
}
Here is the HomePage code:
import { Component } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
import { ClaimPage } from '../claim/claim';
import { SocialSharing } from '#ionic-native/social-sharing';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
import {GoogleAnalytics} from 'ionic-native';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
posts: any;
sendme: any;
claimPage = ClaimPage;
user: any;
userReady: boolean = false;
constructor(
public navCtrl: NavController,
public http: Http,
private sharingVar: SocialSharing,
public platform: Platform,
) {
// Check to see if user already exists (via FB login)
let env = this;
NativeStorage.getItem('user')
.then(function (data){
env.user = {
name: data.name,
gender: data.gender,
picture: data.picture
};
env.userReady = true;
// console.log(data.name);
}, function(error){
console.log(error);
});
this.platform.ready().then(() => {
//alert("platform is ready");
GoogleAnalytics.trackView("Home-Page", "http://foxboxapp.com/home", true);
//alert("GA called");
});
this.http.get('http://getyourtryston.com/foox/sample.php').map(res => res.json()).subscribe(data => {
this.posts = data.data.children;
});
}
otherShare(){
this.sharingVar.share("FoxBox App","Get Awesome College Deals",null/*File*/,"http://fooxsocial.com")
.then(()=>{
//alert("Success");
},
()=>{
alert("Sharing Failed!")
})
}
}
And here is the ProfilePage code which fails:
import { Component } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
import { WelcomePage } from '../welcome/welcome';
import {GoogleAnalytics} from 'ionic-native';
import { SocialSharing } from '#ionic-native/social-sharing';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
//import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
#Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
user: any;
userReady: boolean = false;
constructor(
public navCtrl: NavController,
public platform: Platform,
private sharingVar: SocialSharing,
//public facebookAuth:FacebookAuth,
//public auth:Auth,
) {
// Check to see if user already exists (via FB login)
let env = this;
NativeStorage.getItem('user')
.then(function (data){
env.user = {
name: data.name,
gender: data.gender,
picture: data.picture
};
env.userReady = true;
// console.log(data.name);
}, function(error){
console.log(error);
});
// PLATFORM READY, do your thang!
this.platform.ready().then(() => {
// Ping Google Analytics
GoogleAnalytics.trackView("Profile Page", "http://foxboxapp.com/home", true);
});
}
otherShare(){
this.sharingVar.share("FOOX Social App","Get Awesome College Deals",null/*File*/,"http://fooxsocial.com")
.then(()=>{
//alert("Success");
},
()=>{
alert("Sharing Failed!")
})
}
doFbLogout(){
var nav = this.navCtrl;
Facebook.logout()
.then(function(response) {
//user logged out so we will remove him from the NativeStorage
NativeStorage.remove('user');
nav.push(WelcomePage);
}, function(error){
console.log(error);
});
}
}
And here is ProfilePage.html
<ion-header>
<ion-navbar color="light" hideBackButton="true">
<ion-buttons end>
<button ion-button icon-only (click)="otherShare()">
<ion-icon name="share"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card class="pCard">
<div class="pHeader" align="center">
<div *ngIf="user" class="pImgBox" align="center">
<img class="pImage" src="{{ user.picture }}">
</div>
<div class="pUsername" align="center">
<div *ngIf="user"> {{user.name}} </div>
<br>
<span class="pSchool">(Santa Monica College)</span>
</div>
</div>
<ion-list>
<ion-item class="pItems">
Share App
</ion-item>
<ion-item class="pItems">
Give Us Feedback
</ion-item>
<ion-item class="pItems">
Suggest Vendors
</ion-item>
<ion-item class="pItems">
Privacy & Terms of Service
</ion-item>
<ion-item class="pItems">
Log Out
</ion-item>
<ion-item class="pItems">
Delete Account
</ion-item>
</ion-list>
</ion-card>
<button ion-button round (click)="doFbLogout()">Log Out</button>
</ion-content>
I should mention that, if I remove {{ user.name }} and {{ user.picture }} from my ProfilePage.html, there does NOT seem to be any problems. In fact, if you notice in the ts of ProfilePage, I can both Alert and Console.log the username (data.name) without any issues.
I'm a beginner and would appreciate any concise help in this regard. Thank you.
I finally found a solution. In the html file (ProfilePage.html), I used an *ngIf conditional:
<div *ngIf="user"> {{user.name}} </div>
This will introduce a delay such that the 'user' object is no longer null as it reads from NativeStorage.
Alternatively, an Elvis Operator also works for me:
<div> {{ user?.name }} </div>