I'm at a hackathon and trying to use Ionic 4 (react) which I've never used before to connect to a Rails database that I made (auth is done on the backend) that is hosted on heroku. I really just need to connect the auth actions to it on the frontend and I'm running into so many issues, and everything I find for answers is in Angular Ionic and not for React Ionic.
The app is super simple and really just consists of 4 main pages, one is a start page, one is a resource page, one is a home page, and the other is a login page. The Login page will have a sign up and sign in button (when not authenticated) and change password and sign out (when authenticated). I'm having separate pages for sign in, sign up, and change password. I've looked in docs for examples and found none, is there any kind of example I could go off of for something similar/how do I go about learning how to do this? Any input is super helpful, thanks!
So far this is what garbage I have, mostly taken from :
import { IonContent, IonHeader, IonItem, IonLabel, IonList, IonPage, IonTitle, IonToolbar } from '#ionic/react';
// updateUserName = (event: any) => {
// this.setState({ username: event.detail.value });
// };
const SignIn: React.FC = () => {
login= () => {
let url , credentials;
if(this.state.action == 'Login'){
url = CONFIG.API_ENDPOINT + '/users/login';
credentials = {
"user": {
"email": this.state.email,
"password": this.state.password
}
}
} else {
url = CONFIG.API_ENDPOINT + '/users';
credentials = {
"user": {
"email": this.state.email,
"password": this.state.password,
"username": this.state.username
}
}
}
fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials)
})
.then((res) => {
console.log(res);
if(res.status == 200){
return res.json();
} else {
if(this.state.action == 'SignUp') {
throw new Error("Error creating user");
} else {
throw new Error("Error Logging in")
}
}
} )
.then(
(result) => {
console.log(result);
localStorage.setItem("token",result.user.token);
localStorage.setItem("username", result.user.username);
localStorage.setItem("isLogin", "true");
localStorage.setItem("email", result.user.email);
this.event = new CustomEvent('loggedIn', {
detail: true
});
window.dispatchEvent(this.event);
},
(error) => {
console.error(error);
this.setState({toastMessage: error.toString(), toastState: true});
}
)
};
render() {
return(
<IonHeader title="Login">Sign</IonHeader>
<IonContent padding>
<div className="ion-text-center">
<img src={image} alt="logo" width="25%" />
</div>
<h1 className="ion-text-center conduit-title">conduit</h1>
<IonToast
isOpen={this.state.toastState}
onDidDismiss={() => this.setState(() => ({ toastState: false }))}
message= {this.state.toastMessage}
duration={400}
>
</IonToast>
<form action="">
<IonItem>
<IonInput onIonChange={this.updateEmail} type="email" placeholder="Email"></IonInput>
</IonItem>
{this.state.action === 'SignUp' ?
<IonItem>
<IonInput onIonChange={this.updateUserName} type="text" placeholder="Username"></IonInput>
</IonItem>
: <></>
}
<IonItem>
<IonInput onIonChange={this.updatePassword} type="password" placeholder="Password"></IonInput>
</IonItem>
</form>
<IonButton onClick={this.login}>{this.state.action}</IonButton>
</IonContent>
<IonFooter>
<IonToolbar text-center>
Click here to <a onClick={this.toggleAction}>{this.state.action === 'Login'? 'SignUp' : 'Login'}</a>
</IonToolbar>
</IonFooter>
</>
)
}
}
Related
I have a problem. I want to create instant search, without any search button, that when i'm typing e.g. more than 3 letters, my results will be instant show below.
My code:
<template>
<div class="nav-scroller py-1 mb-2">
<div class="nav d-flex justify-content-between">
<input v-model="keyword" class="form-control" type="text" placeholder="Search" aria-label="Search">
<div v-bind:key="result.id" v-for="result in results">
<p>Results are: {{ result.title }}</p>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
components: {
},
data() {
return {
keyword: '',
results: [],
}
},
methods: {
getResults() {
axios.get("http://127.0.0.1:8000/api/v1/books/?search="+this.keyword)
.then(res => (this.results = res.data))
.catch(err => console.log(err));
}
},
created() {
this.getResults()
}
}
</script>
Now my 'keyword' parameter is probably not passed to the url, because when I refresh the page, all records from APi are the results.
Could you help me?
You should either call method when input changes
<input v-model="keyword" #input="getResults">
and method:
getResults() {
if (this.keyword.length > 3)
axios.get("http://127.0.0.1:8000/api/v1/books/?search="+this.keyword)
.then(res => (this.results = res.data))
.catch(err => console.log(err));
}
}
Or watcher can be used. When keyword changes watcher will call getResults method.
watch: {
keyword: "getResults"
}
Use watcher for the keyword value update.
Whenever keyword is more than 3 letters, request the getResults() method to search.
export default {
name: 'Home',
components: {
},
data() {
return {
keyword: '',
results: [],
}
},
watch: {
keyword: function(newVal) {
if (newVal.length >2) {
this.getResults();
}
}
},
methods: {
getResults() {
axios.get("http://127.0.0.1:8000/api/v1/books/?search="+this.keyword)
.then(res => (this.results = res.data))
.catch(err => console.log(err));
}
},
created() {
this.getResults()
}
}
I am trying to create payment page using braintree's hosted fields.
I have created sandbox account.
But i am not getting additional details like Card brand, error message like Drop in UI.
How to get those functionalities using Hosted fields.
import React from 'react';
var braintree = require('braintree-web');
class BillingComponent extends React.Component {
constructor(props) {
super(props);
this.clientDidCreate = this.clientDidCreate.bind(this);
this.hostedFieldsDidCreate = this.hostedFieldsDidCreate.bind(this);
this.submitHandler = this.submitHandler.bind(this);
this.showPaymentPage = this.showPaymentPage.bind(this);
this.state = {
hostedFields: null,
errorOccurred: false,
};
}
componentDidCatch(error, info) {
this.setState({errorOccurred: true});
}
componentDidMount() {
this.showPaymentPage();
}
showPaymentPage() {
braintree.client.create({
authorization: 'sandbox_xxxxx_xxxxxxx'
}, this.clientDidCreate);
}
clientDidCreate(err, client) {
braintree.hostedFields.create({
onFieldEvent: function (event) {console.log(JSON.stringify(event))},
client: client,
styles: {
'input': {
'font-size': '16pt',
'color': '#020202'
},
'.number': {
'font-family': 'monospace'
},
'.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
'card-brand-id': true,
supportedCardBrands: 'visa'
},
cvv: {
selector: '#cvv',
type: 'password'
},
expirationDate: {
selector: '#expiration-date',
prefill: "12/21"
}
}
}, this.hostedFieldsDidCreate);
}
hostedFieldsDidCreate(err, hostedFields) {
let submitBtn = document.getElementById('my-submit');
this.setState({hostedFields: hostedFields});
submitBtn.addEventListener('click', this.submitHandler);
submitBtn.removeAttribute('disabled');
}
submitHandler(event) {
let submitBtn = document.getElementById('my-submit');
event.preventDefault();
submitBtn.setAttribute('disabled', 'disabled');
this.state.hostedFields.tokenize(
function (err, payload) {
if (err) {
submitBtn.removeAttribute('disabled');
console.error(err);
}
else {
let form = document.getElementById('my-sample-form');
form['payment_method_nonce'].value = payload.nonce;
alert(payload.nonce);
// form.submit();
}
});
}
render() {
return (
<div className="user-prelogin">
<div className="row gutter-reset">
<div className="col">
<div className="prelogin-container">
<form action="/" id="my-sample-form">
<input type="hidden" name="payment_method_nonce"/>
<label htmlFor="card-number">Card Number</label>
<div className="form-control" id="card-number"></div>
<label htmlFor="cvv">CVV</label>
<div className="form-control" id="cvv"></div>
<label htmlFor="expiration-date">Expiration Date</label>
<div className="form-control" id="expiration-date"></div>
<input id="my-submit" type="submit" value="Pay" disabled/>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default BillingComponent;
I am able to get basic functionalities like getting nonce from card details. But i am unable to display card brand image/error message in the page as we show in Drop in UI.
How to show card brand image and error message using hosted fields?
Page created using Hosted fields:
Page created Drop in UI - Which shows error message
Page created Drop in UI - Which shows card brand
Though we do not get exact UI like Drop in UI, we can get the card type and display it ourselves by using listeners on cardTypeChange.
hostedFieldsDidCreate(err, hostedFields) {
this.setState({hostedFields: hostedFields});
if (hostedFields !== undefined) {
hostedFields.on('cardTypeChange', this.cardTypeProcessor);
hostedFields.on('validityChange', this.cardValidator);
}
this.setState({load: false});
}
cardTypeProcessor(event) {
if (event.cards.length === 1) {
const cardType = event.cards[0].type;
this.setState({cardType: cardType});
} else {
this.setState({cardType: null});
}
}
cardValidator(event) {
const fieldName = event.emittedBy;
let field = event.fields[fieldName];
let validCard = this.state.validCard;
// Remove any previously applied error or warning classes
$(field.container).removeClass('is-valid');
$(field.container).removeClass('is-invalid');
if (field.isValid) {
validCard[fieldName] = true;
$(field.container).addClass('is-valid');
} else if (field.isPotentiallyValid) {
// skip adding classes if the field is
// not valid, but is potentially valid
} else {
$(field.container).addClass('is-invalid');
validCard[fieldName] = false;
}
this.setState({validCard: validCard});
}
Got the following response from braintree support team.
Hosted fields styling can be found in our developer documentation. Regarding the logos, you can download them from the card types official websites -
Mastercard
Visa
AMEX
Discover
JCB
Or online from other vendors.
Note: Drop-In UI will automatically fetch the brand logos and provide validation errors unlike hosted fields as it is less customizable.
I am new to Vue but have experience with Django. I am using this boilerplate from Github: https://github.com/gtalarico/django-vue-template
I really like the structure of that boilerplate because it is not overwelming at all and not a lot of code is written to succesfully interact with the back-end API of Django.
It has GET, POST & DELETE already pre-installed and connected to Django REST. So far so good. However I try to add a PUT method to it so I can update models. I try to follow the same structure but I can't get it to work.
My productService.js:
import api from '#/services/api'
export default {
fetchProducts() {
return api.get(`products/`)
.then(response => response.data)
},
postProduct(payload) {
return api.post(`products/`, payload)
.then(response => response.data)
},
deleteProduct(proId) {
return api.delete(`products/${proId}`)
.then(response => response.data)
},
updateProduct(proId) {
return api.put(`products/${proId}`)
.then(response => response.data)
}
}
The updateProduct is the new code I added.
Then in store --> products.js:
const actions = {
getProducts ({ commit }) {
productService.fetchProducts()
.then(products => {
commit('setProducts', products)
})
},
addProduct({ commit }, product) {
productService.postProduct(product)
.then(() => {
commit('addProduct', product)
})
},
deleteProduct( { commit }, proId) {
productService.deleteProduct(proId)
commit('deleteProduct', proId)
},
updateProduct( { commit }, proId) {
productService.updateProduct(proId)
commit('updateProduct', proId)
}
}
const mutations = {
setProducts (state, products) {
state.products = products
},
addProduct(state, product) {
state.products.push(product)
},
deleteProduct(state, proId) {
state.products = state.products.filter(obj => obj.pk !== proId)
},
updateProduct(state, proId) {
state.products = state.products.filter(obj => obj.pk !== proId)
}
}
Here again I added updateProduct.
Then in my Products.vue:
......
<b-tbody>
<b-tr v-for="(pro, index) in products" :key="index">
<b-td>{{ index }}</b-td>
<b-td variant="success">{{ pro.name }}</b-td>
<b-td>{{ pro.price }}</b-td>
<b-td>
<b-button variant="outline-primary" v-b-modal="'myModal' + index">Edit</b-button>
<b-modal v-bind:id="'myModal' + index" title="BootstrapVue">
<input type="text" :placeholder="pro.name" v-model="name">
<input type="number" :placeholder="pro.price" v-model="price">
<b-button type="submit" #click="updateProduct(pro.pk)" variant="outline-primary">Update</b-button>
</b-modal>
</b-td>
<b-td><b-button #click="deleteProduct(pro.pk)" variant="outline-primary">Delete</b-button></b-td>
</b-tr>
.....
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: "Products",
data() {
return {
name: "",
price: "",
};
},
computed: mapState({
products: state => state.products.products
}),
methods: mapActions('products', [
'addProduct',
'deleteProduct',
'updateProduct'
]),
created() {
this.$store.dispatch('products/getProducts')
}
};
</script>
Everything works fine except the PUT action to update a product. I figured that you have to use the ID of a product to be able to edit it with PUT. So that's why I used the same snippet as DELETE. But right now I am still deleting it instead of editing.
I also used now placeholder to display the text of a product entry, which is also not the correct way.. I want to use the modal to edit a product entry and then update it.
Can someone point me in the right direction?
I'm having a hard time getting server data to render in my underscore template. The underscore markup displays on the page as a string, for example inside the div, you can see <%= test %>.
I'm using backbone, require, and the running on the python flask framework.
I've tried multiple different approaches as suggested in a variety of posts but I can't seem to figure out what the issue is. I'm not seeing any errors in the console. Everything is working as expected, and the request to the server is executing, and data is being returned appropriately so at least that portion of the backbone app is working. The router is rendering the appropriate page, but the _.template, just doesn't seem to be working.
Here's the view:
define(['jquery',
'underscore',
'backbone',
'bootstrap',
'text!/static/html/templates/security.html'
],function($,_,Backbone,b, security){
var SecurityView = Backbone.View.extend({
template: _.template($(security).html()),
events: {
"click #submit": 'submit_security'
},
initialize: function(options) {
this.options = options || {};
var currentInstance = this;
},
render: function() {
var that = this;
var template = this.template({test: 'blahblah'});
this.$el.html(template);
return this;
},
});
return SecurityView;
});
The Router:
define(['jquery', 'underscore', 'backbone',
'models/login', 'models/register', 'models/security',
'collections/questions',
'views/login', 'views/register', 'views/security'
],
function($,_,Backbone, Login, Register, Security,
Questions,
LoginView, RegisterView, SecurityView) {
var app = {};
app.models = app.models || {};
app.collections = app.collections || {};
app.current_content = null;
app.current_dialog = null;
app.current_logout = null;
var Router = Backbone.Router.extend({
routes: {
"login": "login",
"register": "register",
"book_writer": "book_writer",
"book_setup": "book_setup",
"error_page": "error_page",
"security": "security_questions"
},
replace_cur_content: function(view, nologout){
//if (app.current_content) {
// app.current_content.remove();
//}
app.current_content = view;
if (view) {
var display = view.render();
$('#app').append($(display.el));
}
},
security_questions: function() {
app.models.security = new Security();
app.models.security.fetch({
reset: true,
success: function(model) {
var state = model.get('session_state');
if (state === 'ready') {
var securityView = new SecurityView({model: model});
app.AppRouter.replace_cur_content(securityView);
} else if (state === 'error') {
// error page
} else if (state === 'logged_out') {
Backbone.history.navigate('login', {trigger: true});
}
}
});
}
});
app.AppRouter = new Router();
$(document).ajaxError(function(event, xhr){
if (xhr.status == 401) {
console.log('Ajax Error - 401');
app.AppRouter.login();
} else if (xhr.status == 403) {
console.log('Ajax Error - 403');
app.AppRouter.noverify();
} else if (xhr.status == 404) {
console.log('Ajax Error - 404');
app.AppRouter.notfound();
} else {
app.AppRouter.error();
}
});
return app;
});
The template:
<div class="form-content">
<div class="form-box">
<div class="form" id="security_form">
<h3 class="form-heading">Security Questions: </h3>
<%= test %>
</div>
</div>
</div>
No error messages are available in the console. Expected is: the underscore markup should not display as text on the page after render.
I am using Chai, Sinon, and Mocha to test.
I am using Redux-Forms, along with ReactJS.
I want to test what happens after I click submit on a Forgot Password page.
Here's my code so far:
react file:
propTypes: {
fields: React.PropTypes.object.isRequired,
message: React.PropTypes.string.isRequired,
handleSubmit: React.PropTypes.func.isRequired,
},
renderForm: function() {
const { fields: {email}, handleSubmit, isFetching } = this.props;
const iconClass = "fa fa-star-o";
return(
<form form="forgotPassword" name="forgotPassword" className="stack-input">
<ReduxFormTextInput
{...email}
floatingLabelText="Email Address" />
<div className="clearfix" />
<div className="form-footer">
<ButtonSpinner spinner={isFetching} onClick={handleSubmit} ripple={true} raised={true} primary={true} >
<i className={iconClass} />Send
</ButtonSpinner>
</div>
</form>
);
},
renderMessage: function() {
return(
<div>
<i className="fa fa-exclamation-circle" style={{marginRight: '4px'}}/>
If your email is in the system, then it will be sent.
</div>
);
},
//Checks if user has submitted email.
emailSubmit: function(){
var locus = this, props = locus.props;
if(props.message === ''){
return null;
}
else if(props.message === 'SENT'){
return true;
}
return null;
},
render(){
var locus = this, props= locus.props;
return(
<div className="page-wrap">
<div className="page-column">
<h2>Forgot Your Password</h2>
{this.emailSubmit() ? this.renderMessage(): this.renderForm()}
</div>
</div>
);
}
unittest file:
describe('ForgotPasswordForm', () => {
const component = setup({
fields: {
email: {
onChange: spy(),
onBlur: spy(),
onFocus: spy()
}
}, // React.PropTypes.object.isRequired,
message: '', // React.PropTypes.string.isRequired,
handleSubmit: spy() // React.PropTypes.func.isRequired,
//onClearMessage:spy() //, React.PropTypes.func.isRequired
}),
domRoot = TestUtils.findRenderedDOMComponentWithClass(component, 'page-wrap'),
title = TestUtils.findRenderedDOMComponentWithTag(component, 'h2'),
submitButton = TestUtils.findRenderedDOMComponentWithClass(component, 'material-D-button'),
form = TestUtils.findRenderedDOMComponentWithTag(component, 'form'),
inputs = TestUtils.scryRenderedDOMComponentsWithTag(component, 'input'),
emailInput = inputs[0];
This test keeps failing, despite multiple attempts. I am not experienced with Spy(), so I'm not sure if I am suppose to be using calledWith.
it ('should display "If your email is in the system, then it will be sent." on submit', () => {
TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});
TestUtils.Simulate.click(submitButton);
expect(domColumn.text).to.equal("Forgot Your Password");
});
This is the response I get.
+"If your email is in the system, then it will be sent."
- -"Forgot Your PasswordSend"
I used innerHTML to get a sense of what's being populated after the click, and I don't think the click is even registering.
When I try to do TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});, it doesn't work. I have to populate the value of the email in the component.
You should be assigning your handleSubmit spy to a constant so you can at least be able to check whether it's getting called. (Probably the same for the other spies).
const handleSubmitSpy = spy()
const component = setup({
...
handleSubmit: handleSubmitSpy
Now you can check expect(handleSubmitSpy).toHaveBeenCalled().