while adding an item to the cart using JS buy SDK addLineItems (shopify) for the 2nd time. I'm getting an error saying "checkout is already completed" - sdk

While adding item to the cart the second time, It throws an error
Error: [{"message":"Checkout is already completed.","locations":[{"line":1,"column":3286}],"path":["checkoutLineItemsAdd"]}]
addItemToCheckout = async (variantId, quantity) => {
const lineItemsToAdd = [
{
variantId,
quantity: parseInt(quantity, 10),
},
];
try{
const checkout = await client.checkout.addLineItems(
this.state.checkout.id,
lineItemsToAdd
);
}catch(err){
console.log(err);
}
// this.setState({ checkout: checkout });
this.openCart();
};
<Button w="10rem" backgroundColor="#FF38BD" color="white" _hover={{ opacity: '70%' }}
onClick={() => addItemToCheckout(product.variants[0]?.id, 2)}>
Add To Cart
</Button>

Related

submitCount keeps reseting unexpectedly

I have a simple form that submits an update request to a server. I would like to display a success/error message for a few seconds after the form is submitted and then reset the form. However, if the user happens to submit subsequent requests in between the last submit and the end of the delay to perform the reset, then I don't want to perform the reset.
To accomplish this I was hoping to use the submitCount found in the FormState. However, I am finding that the submitCount is resetting automatically - even when I don't call reset at all. Also when I call reset and pass to it keepSubmitCount: true
Here's a skeleton of what I'm doing:
import { IToolPanelParams, SelectionChangedEvent } from "ag-grid-community";
import React, { useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { FieldValues, SubmitErrorHandler, SubmitHandler, useForm, useFormState} from "react-hook-form";
interface MyFieldValues extends FieldValues {
note: string;
}
export const MyToolPanel = (props: IToolPanelParams) => {
const [selected, setSelected] = useState<Record[]>();
const { register, handleSubmit, reset, control } = useForm<MyFieldValues>();
const { errors, submitCount, isSubmitting, isSubmitted, isSubmitSuccessful } = useFormState({ control });
...
const onSaveSubmit: SubmitHandler<MyFieldValues> = async (data, e) => {
return peform_update();
}
useEffect(() => {
if(isSubmitted) {
console.log(`successful? (${isSubmitSuccessful}) submit on #${submitCount}`);
const currSubmitCount = submitCount;
setTimeout(() => {
if(!isSubmitting && submitCount == currSubmitCount) {
console.log(`reset after submit #${currSubmitCount}`);
/*reset({
keepValues: true,
keepDefaultValues: true,
keepSubmitCount: true
});
*/
}
}, 3000);
}
}, [isSubmitted, submitCount, reset])
const onError: SubmitErrorHandler<BulkEditNotesFieldValues> = (errors) => {
console.error(`BulkEditNotesToolPanel::onError - errors.note. type: ${errors.note?.type}. types: ${JSON.stringify(errors.note?.types)}. message: ${errors.note?.message}`);
}
return (
<Container>
<Spin spinning={isSubmitting}>
<h2>Bulk Edit Records</h2>
{ !!selected?.length && (<>
<form onSubmit={handleSubmit(onSaveSubmit, onError)}>
<label htmlFor="Note">
<textarea
style={textAreaStyle}
{...register("note", {
required: { value: true, message: 'Note required'},
pattern: { value: /[^\s]/, message: 'Note cannot be empty'}
})}
/>
</label>
{ errors?.note && (
<Error>{errors.note.message}</Error>
)}
<button
type="submit"
style={submitStyle}
disabled={!selected?.length}
>
Save { selected?.length > 1? 'Records' : 'Record' }
</button>
</form>
</>)}
{ isSubmitted && isSubmitSuccessful && (
<SuccessMessage>Successfully Updated The Selected Record(s)</SuccessMessage>
)}
{ updateError && (<>
<Error>An error occurred while attempting to update the selected records...</Error>
</>)}
</Spin>
</Container>
);
};
While I can of course create my own submit counter via useState, I was hoping to figure out why react-hook-form's submitCount is resetting on me and how to prevent that? Thanks!

Expo Image Picker With Rails API gets Errno::ENOENT (No such file or directory # rb_sysopen

When I try to upload image via Expo's: ImagePicker I get:
Completed 500 Internal Server Error in 1ms (ActiveRecord: 2.8ms | Allocations: 637)
Errno::ENOENT (No such file or directory # rb_sysopen - file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540zilasino%252Frestaurant-management/ImagePicker/5f29f45c-4467-4360-bcc8-2651146837b3.jpg):
It does work fine when I hit the endpoint via Insomnia, the image is uploaded, but it doesn't work doing so via expo app running on Android Emulator. It seems like, the data is passing to rails, but for some reason rails is unable to find that file... Any ideas?
CreateMenuItem.js
const addMenuItem = async () => {
const menuItem = {
name,
price,
image: selectedImage.uri,
is_drink: isDrink ? true : false,
notes,
};
const resp = await createAccessory("menu_items", menuItem);
if(resp.type == 'success'){
fetchMenuItemData();
clearFields()
}
showMessage({
message: `${resp.msg}`,
position: "center",
duration: 2500,
style: { backgroundColor: resp.type == 'success' ? colors.PRIMARY : colors.ERROR },
titleStyle: { fontSize: 20 },
});
};
const createAccessory = async (accessory, payload) => {
return await axios.post(baseUrl + '/' + accessory, payload, {headers})
.then(resp => {
if (resp.data){
return { msg: i18n.t('saved'), type: 'success' }
} })
.catch(error => {
if (error.response) {
return { msg: error.response.data.name }
}
}
)
}
ImageUpload.js
const ImageUpload = ({ parentCallback, selectedImage }) => {
let openImagePickerAsync = async () => {
let permissionResult =
await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("Permission to access camera roll is required!");
return;
}
let pickerResult = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1
});
if (pickerResult.cancelled === true) {
return;
}
parentCallback(pickerResult);
};
if (selectedImage != null) {
return (
<>
<Image
source={{ uri: selectedImage.uri }}
style={styles.thumbnail}
resizeMode="contain"
/>
<Button
title="X"
color={colors.PRIMARY}
onPress={() => parentCallback(null)}
/>
</>
);
} else {
return (
<>
<TouchableOpacity onPress={openImagePickerAsync} style={styles.button}>
<Text style={styles.buttonText}>{i18n.t("pickAnImage")}</Text>
</TouchableOpacity>
</>
);
}
};
controller.rb
class MenuItemsController < ApplicationController
...
def create
image = Cloudinary::Uploader.upload(params[:image])
menu_item = MenuItem.new(
image: image['url'],
)
end
...
end
One possible issue could be that the path to the image file is incorrect. Try double-checking the path and make sure that the image file is in the correct location.
I figured it out, I had to pass the image to Rails as a Base64 and not just its URI.
I've just added base64: true option:
let pickerResult = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
base64: true
})
and change one line in rails controller like so:
image = Cloudinary::Uploader.upload('data:image/jpeg;base64,' + menu_item_params[:image])

Cordova Plugin Purchase - redirect after successful subscription

I am using this plugin (https://github.com/j3k0/cordova-plugin-purchase) to handle a in app subscription.
iap.validator = "https://validator.fovea.cc/v1/validate?appName=XXX";
//initiate initInAppPurchase function
useEffect(() => {
const init = async () => {
await initInAppPurchase();
}
init();
}, []);
//if on an ios or android device, then get product info
const initInAppPurchase = () => {
if ((isPlatform('ios')) || (isPlatform('android'))) {
iap.verbosity = iap.DEBUG;
iap.register({
id: "tpmonthly",
alias: "Monthly",
type: iap.PAID_SUBSCRIPTION
});
iap.ready(() => {
let product = iap.get('Monthly');
setPrice(product.price)
setProduct(product)
})
iap.refresh();
}
}
//if user clicks purchase button
const purchaseProduct = () => {
if (product.owned) {
alert('A subscription is currently active.')
} else {
iap.order('Monthly').then(() => {
iap.when("tpmonthly").approved((p: IAPProduct) => {
p.verify();
});
iap.when("tpmonthly").verified((p: IAPProduct) => {
p.finish();
history.push("/ios-signup/");
});
})
}
}
return (
<Button size="large" variant="outlined" onClick={purchaseProduct}>Subscribe Monthly for {productPrice}</Button>
);
What I am hoping to get is that once the subscription is verified that it then redirects the app to /ios-signup/ .. this is not happening.
Is this code correct? And why would it not redirect after p.finish?

How to dynamically disable the button of antd modal using button props

I have an antd Modal, i am trying to validate a field and provided validation to it. How can i enable/disable the Ok button based on the validation. If the validation is successful then button should be enabled else disabled.
<Form>
<Modal
title={modalHeader}
okText="ADD FIELD"
cancelText="CANCEL"
visible={visible}
onCancel={onCancelHandler}
onOk={() => onAddFieldHandler(fieldName)}
width={600}
okButtonProps={{disabled:true}}
>
<p>Please provide name</p>
<Form.Item
name="fieldName"
rules={[{ required: true, message: 'Please enter name' }]}
>
<FieldNameInput
placeholder="Field name..."
value={fieldName}
onChange={(event) => setFieldName(event.target.value)}
/>
</Form.Item>
</Modal>
</Form>
You can use onFieldsChange from Antd Forms API togehter with geFieldsError and the okButtonProps from Antd Modal API.
const [form] = Form.useForm();
const [buttonDisabled, setButtonDisabled] = useState(true);
return (
<Modal
...
okButtonProps={{ disabled: buttonDisabled }}
>
<Form
form={form}
...
onFieldsChange={() =>
setButtonDisabled(
form.getFieldsError().some((field) => field.errors.length > 0)
)
}
>
Here is a working Stackblitz.
In my case I had Form inside modal and there is onFieldChange prop when you can pass function to perform some operations due to changes on from so you can sth like that:
const SomeModal = ({ visible }) => {
const [form] = Form.useForm();
const [buttonDisabled, setButtonDisabled] = useState(true);
const handleOk = () => form.submit();
const handleAfterClose = () => {
setButtonDisabled(true);
form.resetFields();
}
const handleCancel = () => ...some action to hide modal;
const handleFormFinish = (values) => {
... some logic here
}
return (
<Modal
title={"Some title..."}
visible={visibile}
onOk={handleOk}
onCancel={handleCancel}
afterClose={handleAfterClose}
okButtonProps={{ disabled: buttonDisabled }}
>
<Form
form={form}
layout="vertical"
name="acceptform"
onFinish={handleFormFinish}
initialValues={{
...initial values here
}}
onFieldsChange={() => {
const actualFieldValues = form.getFieldsValue();
const anyError = form.getFieldsError().some((field) => field.errors.length > 0);
.. some logic if error etc..
if (anyError) {
setButtonDisabled(true);
}
else {
setButtonDisabled(false);
}
}}
>
and of course there is need to have some validators on fields
<Form.Item
label={"someLabel"}
id="field"
name="field"
hasFeedback
rules={[
{
type: "string",
validator: async (rule, value) => inputFieldValidate(value, "custom message")
},
]}
>
and validator looks alike:
const inputFieldValidate = async (value, message) => {
if (someCondition)) {
return Promise.reject(message);
}
return Promise.resolve();
};
here is some nice to know that validator isasync and to make it work without any warnings just handle promises
Having the Form inside the Modal, a way to update modal button status would be just running form instance's validateFields, but there are two things to take into account:
This function is a Promise, so the state must update after an await with the validation results.
I've experienced some looping issues when using onFieldsChange (maybe the validation triggers some kind of field update). Instead, onValuesChange has worked good enough for me.
Running the validation into a setTimeout callback seems to be mandatory. Doing it without the setTimeout returns a validation error even when all the fields are valid because of an outOfDate: true. It seems to be because of how the Antd Form update lifecycle works, and waiting until this process has ended (what we can easily achieve with the setTimeout) solves that problem.
A succesful validation returns the form values object, a failed one returns an errorInfo object with the errors list, the outOfDate status and the current form values. You can use the errors list in the latter to get the validation messages returned by Antd to display more descriptive and specific feedback.
In the end, my approach has this structure:
const MyModal = ({onFinish, ...otherProps}) => {
const [canSubmit, setCanSubmit] = useState(false);
const [form] = Form.useForm();
return (
<Modal
{...otherProps}
okButtonProps={{
disabled: !canSubmit
}}
>
<MyFormComponent
form={form}
onFinish={onFinish}
onValuesChange={() => {
setTimeout(() => {
form
.validateFields()
.then(() => {
/*
values:
{
username: 'username',
password: 'password',
}
*/
setCanSubmit(true);
})
.catch((err) => {
/*
errorInfo:
{
values: {
username: 'username',
password: 'password',
},
errorFields: [
{ name: ['password'], errors: ['Please input your Password!'] },
],
outOfDate: false,
}
*/
setCanSubmit(false);
});
});
}}
/>
</Modal>
);
};

App not authenticated with Firebase. Permission Denied error

I have been following this guide to add and retrieve items from firebase using React Native. If I set my rules on firebase to public, everything works but if I set it to the following, I get a permission denied error.
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
I have added all of my config data properly in my ios.js. Is there some basic step that I am missing here?
index.ios.js:
// Initialize Firebase
const firebaseConfig = {
apiKey: 'myapikey',
authDomain: 'myauthdomain',
databaseURL: 'https://url.firebaseio.com',
projectId: 'myProjectId',
storageBucket: 'projectid.appspot.com',
messagingSenderId: 'myMessagingSenderId'
};
const firebaseApp = firebase.initializeApp(firebaseConfig);
class MyNewAppreactold extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
};
this.itemsRef = this.getRef().child('items');
}
getRef() {
return firebaseApp.database().ref();
}
listenForItems(itemsRef) {
itemsRef.on('value', (snap) => {
// get children as an array
var items = [];
snap.forEach((child) => {
items.push({
title: child.val().title,
_key: child.key
});
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(items)
});
});
}
// componentWillMount() {
// firebase.initializeApp(firebaseConfig);
// }
componentDidMount() {
this.listenForItems(this.itemsRef);
}
render() {
return (
<View style={styles.container}>
<StatusBar title="Grocery List" />
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderItem.bind(this)}
enableEmptySections={true}
style={styles.listview}/>
<ActionButton onPress={this._addItem.bind(this)} title="Add" />
</View>
)
}
_addItem() {
AlertIOS.prompt(
'Add New Item',
null,
[
{text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
{
text: 'Add',
onPress: (text) => {
this.itemsRef.push({ title: text })
}
},
],
'plain-text'
);
}
_renderItem(item) {
const onPress = () => {
AlertIOS.alert(
'Complete',
null,
[
{text: 'Complete', onPress: (text) => this.itemsRef.child(item._key).remove()},
{text: 'Cancel', onPress: (text) => console.log('Cancelled')}
]
);
};
return (
<ListItem item={item} onPress={onPress} />
);
}
}
AppRegistry.registerComponent('MyNewAppreactold', () => MyNewAppreactold);
The code you shared doesn't authenticate the user. Since your security rules require that the user is authenticated to be allowed access to the data, they reject the unauthenticated user of your app.
To solve this problem, you'll need to authenticate the user. The simplest way to do that is to sign the user in anonymously:
firebase.auth().signInAnonymously();
Then attach your database listeners after the user is authenticated:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.listenForItems(this.itemsRef);
} else {
// User is signed out.
// ...
}
// ...
});

Resources