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
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()
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.
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();
})
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()
})
})
})
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>