Cannot upload file and submit it via ngForm in angular - javascript

I have a problem with uploading a file from my pc via ngForm as part of a small project I do for a class. It seems that I cannot correctly upload the file as during debugging I get "fakedirectory/name-of-file" rather than the actual temp directory.
I did already search some related posts but they seem to be different than my case and I cannot get them to work.
I would greatly appreciate any help and guidance what can I try next.
I have a frontend part of the project and a separate rest api backend. I will paste the related code here:
HTML:
<form #addOfferForm="ngForm" (ngSubmit)="submitNewOffer(addOfferForm)">
Here I have other text inputs that work fine
...
<input (I tried with ngForm and without) type="file" accept="image/png" id="offerPhoto" (change)="handleOfferPhotoUpload($event)">
...
<button class="public">Publish</button>
Component:
offerPhoto?: File
handleOfferPhotoUpload(event: InputEvent){
const input: HTMLInputElement = event.target as HTMLInputElement;
this.offerPhoto = input.files[0]
console.log( "this.offerPhoto" + this.offerPhoto)
}
addOfferForm: FormGroup = new FormGroup({
offerName: new FormControl(''),
...
offerPhoto: new FormControl(''),
})
submitNewOffer(addOfferForm: NgForm): void {
this.offerService.addOffer$( addOfferForm.value).subscribe({
next: (offer) => {
this.router.navigate(['/offers'])
},
error: (error) => {
console.error(error)
}
})
Service:
addOffer$(body: { offerName: string, ... offerPhoto: File }): Observable<IOffer> //This is an interface that I use {
return this.http.post<IOffer>(`${apiUrl}/offers`, body, { withCredentials: true });
}
Then on the backend I have:
function createOffer(req, res, next) {
const { offerName, buyOrSell, cameraOrLens, offerDescription, offerPhoto, offerContact } = req.body;
const { _id: userId } = req.user;
uploadFile(offerPhoto).then(id => {
const offerPhoto = `https://drive.google.com/uc?id=${id}`
return offerModel.create({ offerName, ... offerPhoto, userId })
.then(offer => res.json(offer))
.catch(next);
})
The uploadFile function worked with a simpler form where I just update a photo that is already there but cannot seem to get the image uploaded as part of the form.
I am very stuck and don't know what else to try.
A very big thanks to anybody who can help me in advance!

Related

Adding file to dropzone Cypress

I am trying to add a pdf file to a dropzone in a Cypress test that Im creating
Ive added the cypress-upload-file package to help me do this.
In my commands.js file I have
Cypress.Commands.add("AddCandidate", function (candidate) {
cy.contains("Candidates").click()
cy.contains('Import Candidate').click()
cy.get('[id="resumeDz"]')
.attachFile({ './resumes': 'example.pdf', encoding: 'utf-8', subjectType: 'drag-n-drop' });})
and in my test I have
/// <reference types="cypress" />
describe('Add candidate', () => {
before(function () {
cy.visit(Cypress.env("home_page"));
cy.fixture('user').then(function (user) {
this.user = user
cy.SignIn({ email: (this.user.email), password: (this.user.password) })
})
})
it('Adds Candidate', function () {
cy.AddCandidate({})
})})
When running the test I get `"filePath" is not valid.
Please look into docs to find supported "filePath" values
Is there a specific way that i need to define the path ? Ive tried adding the full path, but Im still getting the same error. Is there something im missing ?`
SOLUTION
I ended up finding the solution.
Cypress.Commands.add("AddCandidate", function (candidate) {
cy.contains("Candidates").click()
cy.contains('Import Candidate').click()
cy.fixture('example.pdf', 'binary')
.then(Cypress.Blob.binaryStringToBlob)
.then(fileContent => {
cy.get('[type="file"]').attachFile({
fileContent,
filePath: 'example.pdf',
fileName: 'example.pdf',
});
cy.wait(1000)
cy.contains('Start').click()
cy.contains('Done').click()
});
})
The arguments to .attachFile() are a little messed up
cy.get('[id="resumeDz"]')
.attachFile('./resumes/example.pdf', {
encoding: 'utf-8',
subjectType: 'drag-n-drop'
});
presuming ./resumes/example.pdf is the path to the file relative to the fixtures folder.
With newer versions you can use the native cypress drag&drop action:
cy.get('#dropzone')
.selectFile('cypress/fixtures/file.txt', { action: 'drag-drop' });

Service ID invalid when trying to use EmailJS with React

I created a form to contact me on my website, for that I use EmailJS.
However when I try to send myself a mail through the contact form I got a 400 Error The service ID is invalid.
I followed every steps of that tutorial as I haven't use EmailJS before https://blog.mailtrap.io/react-send-email/
Here is my Contact component
class Contact extends React.Component {
constructor(props) {
super(props);
this.state = { feedback: '', name: 'Name', email: 'email#example.com' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<form className="test-mailing">
<h1>Let's see if it works</h1>
<div>
<textarea
id="test-mailing"
name="test-mailing"
onChange={this.handleChange}
placeholder="Post some lorem ipsum here"
required
value={this.state.feedback}
style={{width: '100%', height: '150px'}}
/>
</div>
<input type="button" value="Submit" className="btn btn--submit" onClick={this.handleSubmit} />
</form>
)
}
handleChange(event) {
this.setState({feedback: event.target.value})
}
handleSubmit() {
const templateId = 'template_id';
this.sendFeedback(templateId, {message_html: this.state.feedback, from_name: this.state.name, reply_to: this.state.email})
}
sendFeedback (templateId, variables) {
window.emailjs.send(
'gmail', templateId,
variables
).then(res => {
console.log('Email successfully sent!')
})
// Handle errors here however you like, or use a React error boundary
.catch(err => console.error('Oh well, you failed. Here some thoughts on the error that occured:', err))
}
}
And here is what I added in my index.html
`<script type="text/javascript"
src="https://cdn.jsdelivr.net/npm/emailjs-com#2.3.2/dist/email.min.js"></script>
<script type="text/javascript">
(function(){
emailjs.init("my_user_ID_here"); // Obtain your user ID at the dashboard https://dashboard.emailjs.com/integration
})();
`
To fix this, I had to swap out 'gmail' with my service ID.
sendFeedback (templateId, variables) {
window.emailjs.send(
***serviceID here***, templateId,
variables
).then(res => {
console.log('Email successfully sent!')
})
// Handle errors here however you like, or use a React error boundary
.catch(err => console.error('Oh well, you failed. Here some thoughts on the error that occured:', err))
}
The JavaScript console in my web browser helped identify this.
That was happening to me, and it was because I didn't have the account activated.
when you log in, click on 'email services' and select, for example, gmail with your account
pd: google translate
Had the same problem.
To fix it,
I had to paste NOT the 'gmail' string itself but the service_id which
is below the icon gmail
in the EmailJS website after log in. Everyone has its own specific number. Also the template_id is important to put the id generated for your template.
When you want to publish your project it is advisable to place your special ids to the .env file to stay secure.
Please try to check whether you are using the right integration id, check the id token you are using with the one under integration id on the dashboard, this was my issue
Might as well share a quick fix that would probably save someone's time. I just had the same issue while using the code below.
const notifyOwnerOfGuest = async () => {
const userId = 'user_...';
const serviceId = 'service_...';
const templateId = 'template_...';
const accessToken = 'e2e1...';
const postfields = {
user_id: userId,
service_id: serviceId,
template_id: templateId,
accessToken,
};
const response = await fetch('https://api.emailjs.com/api/v1.0/email/send', {
method: 'POST',
body: JSON.stringify(postfields),
// should explicitly add the header content-type here
});
if (!response.ok) throw await response.text();
};
I just explicitly added a Content-type header like so
headers: {
'Content-Type': 'application/json',
},
and now it works.

Stripe not being called

I am trying to use Vue.js for my front end to call Stripe and create a token which then is sent to my backend. I have tested everything using plain HTML/JS and it all works fine, my issue comes in trying to use Vue.js I think my issue might be in how I am binding the stripe public key. Below is my code, and I have zero output to speak of, I get just redriected to the same page but wth ? at the end of the URL. Nothing else, console shows nothing and no error message or anything send to my back end.
template code
There is more but not related
<div class="col-md-8">
<card class='stripe-card col-md-8'
:class='{ complete }'
:stripe='stripeKey'
:options='stripeOptions'
#change='complete = $event.complete'
/>
<button class='pay-with-stripe' #click='pay' :disabled='!complete'>Submit Payment Details</button>
<br>
</div>
script section with relavent added
import { Card, createToken } from 'vue-stripe-elements-plus'
import axios from 'axios';
export default {
components: { Card },
data() {
return {
errorMessage: null,
successMessage: null,
complete: false,
stripeKey: process.env.VUE_APP_STRIPE_PUB_KEY,
stripeOptions: {
// see https://stripe.com/docs/stripe.js#element-options for details
hidePostalCode: true
},
current: {
stripe: {
plan: null,
last4: null
}
},
}
},
methods: {
pay () {
createToken().then(result => {
axios.post('/billing/updateCard', {
token: result.token,
})
.then(res => {
if(res.data.success == true) {
this.successMessage = res.data.message
console.log(res.data.message)
}
if(res.data.success == false) {
this.errorMessage = res.data.message // Display error message from server if an error exists
}
})
.catch((err) => {
if(err) console.log(err)
if(err) this.$router.push('/company/settings?success=false')
})
});
}
}
}
</script>
I have checked that the API key is actually in the data value by doing <p>{{ stripeKey }}</p> and seeing the value show up. So yes the key is there and the key is valid (tested copy/paste into my HTML/JS test)
created(){
this.key=process.env.VUE_APP_STRIPE_KEY;
}
try this, i used this piece of code in my project and it worked... the issue maybe is that your key is not yet initialized when card us rendered idk. maybe key isnt issue at all. try this and let me know if works and we will debug it together.

Upload and read a file in react

Im trying to upload a file with React and see its contents, but what it gives me is C:\fakepath\. I know why it gives fakepath, but what is the correct way to upload and read the contents of a file in react?
<input type="file"
name="myFile"
onChange={this.handleChange} />
handleChange: function(e) {
switch (e.target.name) {
case 'myFile':
const data = new FormData();
data.append('file', e.target.value);
console.log(data);
default:
console.error('Error in handleChange()'); break;
}
},
To get the file info you want to use event.target.files which is an array of selected files. Each one of these can be easily uploaded via a FormData object. See below snippet for example:
class FileInput extends React.Component {
constructor(props) {
super(props)
this.uploadFile = this.uploadFile.bind(this);
}
uploadFile(event) {
let file = event.target.files[0];
console.log(file);
if (file) {
let data = new FormData();
data.append('file', file);
// axios.post('/files', data)...
}
}
render() {
return <span>
<input type="file"
name="myFile"
onChange={this.uploadFile} />
</span>
}
}
ReactDOM.render(<FileInput />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="root"></div>
You may want to look into FileReader which can help if you want to handle the file on the client side, for example to display an image.
https://developer.mozilla.org/en-US/docs/Web/API/FileReader
You can use React Dropzone Uploader, which gives you file previews (including image thumbnails) out of the box, and also handles uploads for you.
In your onChangeStatus prop you can react to the file's meta data and the file itself, which means you can do any kind of client-side processing you want before or after uploading the file.
import 'react-dropzone-uploader/dist/styles.css'
import Dropzone from 'react-dropzone-uploader'
const Uploader = () => {
return (
<Dropzone
getUploadParams={() => ({ url: 'https://httpbin.org/post' })} // specify upload params and url for your files
onChangeStatus={({ meta, file }, status) => { console.log(status, meta, file) }}
onSubmit={(files) => { console.log(files.map(f => f.meta)) }}
accept="image/*,audio/*,video/*"
/>
)
}
Uploads have progress indicators, and they can be cancelled or restarted. The UI is fully customizable.
Full disclosure: I wrote this library.
Try to use Multer and gridfs-storage on the back end and store the fileID along with your mongoose schema.
// Create a storage object with a given configuration
const storage = require('multer-gridfs-storage')({
url: 'MONGOP DB ATLAS URL'
});
// Set multer storage engine to the newly created object
const upload = multer({ storage }).single('file');
router.post('/', upload, (req, res) => {
const newreminder = new Reminders({
category: req.body.category,
name:req.body.name,
type: req.body.type,
exdate: req.body.exdate,
location:req.body.location,
notes:req.body.notes,
fileID: req.file.id
});
newreminder.save(function(err){
if(err){
console.log(err);
return;
}
res.json({ "success": "true"});
});
});
Then on the front end treat it normally (with Axios) and upload the entire file and grab a hold of all the info in the normal react way:
onSubmit = (e) => {
e.preventDefault;
const formData = new FormData();
formData.append({ [e.target.name]: e.target.value })
formData.append('file', e.target.files[0]);
axios.post({
method:'POST',
url:'EXPRESS JS POST REQUEST PATH',
data: formData,
config:{ headers: {'Content-Type':'multipart/form-data, boundary=${form._boundary}'}}
})
.then(res => console.log(res))
.catch(err => console.log('Error', err))
}
Have you use dropzone ?
see this react-dropzone
easy implement, upload and return url if this important.
onDrop: acceptedFiles => {
const req = request.post('/upload');
acceptedFiles.forEach(file => {
req.attach(file.name, file);
});
req.end(callback);
}
You can use FileReader onload methods to read the file data and then can send it to the server.
You can find this useful to handle files using File Reader in React ReactJS File Reader
To add to the other answers here, especially for anyone new to React, it is useful to understand that react handles forms a little differently than people may be used to.
At a high level, react recommends using 'Controlled components" :
In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
This essentially means that the user input, e.g. a text field, is also a state of the component and as the user updates it the state is updated and the value of the state if displayed in the form. This means the state and the form data are always in synch.
For an input type of file this will not work because the file input value is read-only. Therefore, a controlled component cannot be used and an 'uncontrolled component' is used instead.
In React, an is always an uncontrolled component because its value can only be set by a user, and not programmatically.
The recommended way to input a file type (at the time of writing) is below, from the react documentation here https://reactjs.org/docs/uncontrolled-components.html#the-file-input-tag:
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
The documentation includes a codepen example which can be built on.

Getting PUT routes to work in Angular

I'm seeking some wisdom from the Angular community. I am working on a simple project using the MEAN stack. I have set up my back-end api and everything is working as expected. Using Postman, I observe expected behavior for both a GET and PUT routes to retrieve/update a single value - a high score - which is saved in it's own document in its own collection in a MongoDB. So far so good.
Where things go off track is when trying to access the PUT api endpoint from within Angular. Accessing the GET endpoint is no problem, and retrieving and displaying data works smoothly. However, after considerable reading and searching, I am stll unable to properly access the PUT endpoint and update the high score data when that event is triggered by gameplay. Below are the snippets of code that I believe to be relevant for reference.
BACK-END CODE:
SCHEMA:
const _scoreSchema = {
name: { type: String, required: true },
value: { type: Number, "default": 0 }
};
ROUTES:
router
.route('/api/score/:highScore')
.put(scoreController.setHighScore);
CONTROLLER:
static setHighScore(req, res) {
scoreDAO
.setHighScore(req.params.highScore)
.then(highScore => res.status(200).json(highScore))
.catch(error => res.status(400).json(error));
}
DAO:
scoreSchema.statics.setHighScore = (value) => {
return new Promise((resolve, reject) => {
score
.findOneAndUpdate(
{"name": "highScore"},
{$set: {"value": value} }
)
.exec(function(err, response) {
err ? reject(err)
: resolve(response);
});
});
}
ANGULAR CODE:
CONTROLLER:
private _updateHighScore(newHighScore): void {
console.log('score to be updated to:', newHighScore)
this._gameService
.updateHighScore(newHighScore);
}
SERVICE:
updateHighScore(newHighScore: Number): Observable<any> {
console.log(newHighScore);
let url = '/api/score/' + newHighScore;
let _scoreStringified = JSON.stringify({value: newHighScore});
let headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http
.put(url , _scoreStringified, {headers})
.map((r) => r.json());
}
Note that the console.log(newHighScore) in the last block of code above correctly prints the value of the new high score to be updated, it's just not being written to the database.
The conceptual question with PUT routes in angular is this: If the api is already set up such that it receives all the information it needs to successfully update the database (via the route param) why is it required to supply all of this information again in the Angular .put() function? It seems like reinventing the wheel and not really utilizing the robust api endpoint that was already created. Said differently, before digging into the docs, I naively was expecting something like .put(url) to be all that was required to call the api, so what is the missing link in my logic?
Thanks!

Categories

Resources