Skip to content

Commit a372e6d

Browse files
components and profile template
1 parent 4f55393 commit a372e6d

File tree

12 files changed

+620
-0
lines changed

12 files changed

+620
-0
lines changed

app/components/button.hbs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<button
2+
data-test-btn={{@data-test-btn}}
3+
type='button'
4+
class={{if @disabled (concat @class ' btn-disabled') @class}}
5+
disabled={{@disabled}}
6+
{{on 'click' @onClick}}
7+
>
8+
{{#if @isLoading}}
9+
<i class='fa fa-spinner fa-spin' data-test-button-spinner></i>
10+
{{/if}}
11+
{{yield}}
12+
</button>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div class='image-cropper-container'>
2+
<img
3+
data-test-image-cropper
4+
class='image-cropper'
5+
id='image-cropper'
6+
alt='Cropper'
7+
src={{this.image}}
8+
onload={{this.loadCropper}}
9+
/>
10+
</div>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Component from '@glimmer/component';
2+
import Cropper from 'cropperjs';
3+
import { action } from '@ember/object';
4+
5+
export default class ImageCropperComponent extends Component {
6+
get image() {
7+
if (this.cropper) {
8+
this.cropper.destroy();
9+
}
10+
return this.args.image;
11+
}
12+
13+
@action loadCropper() {
14+
const image = document.getElementById('image-cropper');
15+
this.cropper = new Cropper(image, {
16+
autoCrop: true,
17+
viewMode: 1,
18+
dragMode: 'crop',
19+
aspectRatio: 1,
20+
cropBoxResizable: true,
21+
movable: false,
22+
zoomOnWheel: false,
23+
rotatable: false,
24+
toggleDragModeOnDblclick: false,
25+
ready: () => {
26+
this.setImageData();
27+
},
28+
cropend: () => {
29+
this.setImageData();
30+
},
31+
});
32+
}
33+
34+
setImageData() {
35+
const { x, y, width, height } = this.cropper.getData(true);
36+
this.args.setImageCoordinates({ x, y, width, height });
37+
}
38+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<section data-test-profile-field class={{if @showError 'profile-field profile-field-error' 'profile-field'}}>
2+
<label data-test-profile-field-label class='profile-field-label' for={{@id}}>{{@label}}</label>
3+
<div class="profile-field-input-container">
4+
<img data-test-profile-field-icon src={{@icon_url}} alt="">
5+
<Input
6+
data-test-profile-field-input
7+
@type="text"
8+
id={{@id}}
9+
@value={{@value}}
10+
placeholder={{@placeholder}}
11+
class="profile-field-input"
12+
required={{@required}}
13+
disabled={{@isDeveloper}}
14+
{{on 'input' this.inputFieldChanged}}
15+
{{on 'blur' this.checkInputValidation}}
16+
/>
17+
</div>
18+
{{#if @showError}}
19+
<p data-test-profile-field-error class="profile-field-error-message">
20+
{{@errorMessage}}
21+
</p>
22+
{{/if}}
23+
</section>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Component from '@glimmer/component';
2+
import { action } from '@ember/object';
3+
4+
export default class ProfileFieldComponent extends Component {
5+
@action
6+
inputFieldChanged(event) {
7+
const { id, onChange } = this.args;
8+
const value = event.target.value;
9+
10+
onChange(id, value);
11+
}
12+
13+
@action
14+
checkInputValidation(event) {
15+
const { id, onBlur } = this.args;
16+
let isValid = event.target.validity.valid;
17+
18+
onBlur(id, isValid);
19+
}
20+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{{#if this.isImageSelected}}
2+
<h1 class="image-h1">
3+
Crop Selected Image
4+
</h1>
5+
6+
<Profile::ImageCropper
7+
@image={{this.image}}
8+
@setImageCoordinates={{this.setImageCoordinates}}
9+
/>
10+
<div>
11+
<button
12+
class='image-form__button btn'
13+
type='button'
14+
disabled={{this.isImageUploading}}
15+
data-test-btn='back'
16+
{{on 'click' this.goBack}}
17+
>
18+
Back
19+
</button>
20+
<button
21+
class='image-form__button btn'
22+
type='button'
23+
data-test-btn='upload-image'
24+
disabled={{this.isImageUploading}}
25+
{{on 'click' this.onSubmit}}
26+
>
27+
{{#if this.isImageUploading}}
28+
<Spinner />
29+
{{else}}
30+
Upload
31+
{{/if}}
32+
</button>
33+
</div>
34+
<p
35+
class='{{if
36+
this.imageUploadSuccess
37+
"message-text__success"
38+
"message-text__failure"
39+
}}'
40+
>
41+
{{this.statusMessage}}
42+
</p>
43+
44+
{{else}}
45+
<h1 class="image-h1">
46+
Upload Image
47+
</h1>
48+
<p class="image-p">( Max size 2MB )</p>
49+
<div
50+
data-test-drop-area
51+
class='drop-area {{if this.overDropZone "drop-area__highlight"}}'
52+
{{on 'drop' this.handleDrop}}
53+
{{on 'dragover' this.handleDragOver}}
54+
{{on 'dragenter' this.handleDragEnter}}
55+
{{on 'dragleave' this.handleDragLeave}}
56+
>
57+
<form class='image-form'>
58+
<p class='image-form__text'>
59+
Drag and drop file here or
60+
</p>
61+
<label
62+
class='image-form__button btn
63+
{{if this.isImageUploading "image-form__button--disabled"}}'
64+
for='image'
65+
data-test-btn='browse'
66+
>
67+
Browse
68+
</label>
69+
<input
70+
class='image-form__input'
71+
type='file'
72+
id='image'
73+
accept='image/png, image/jpeg'
74+
{{on 'change' this.handleBrowseImage}}
75+
/>
76+
</form>
77+
</div>
78+
{{/if}}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import Component from '@glimmer/component';
2+
import { action } from '@ember/object';
3+
import { tracked } from '@glimmer/tracking';
4+
import { toastNotificationTimeoutOptions } from '../../constants/toast-notification';
5+
import { inject as service } from '@ember/service';
6+
7+
export default class UploadImageComponent extends Component {
8+
formData;
9+
@service toast;
10+
@service router;
11+
@tracked image;
12+
@tracked isImageSelected = false;
13+
@tracked overDropZone = false;
14+
@tracked isImageUploading = false;
15+
@tracked imageUploadSuccess = false;
16+
@tracked statusMessage;
17+
@tracked imageFileName;
18+
imageCoordinates = null;
19+
uploadUrl = this.args.uploadUrl;
20+
formKeyName = this.args.formKeyName;
21+
22+
@action goBack() {
23+
this.image = null;
24+
this.setImageSelected(false);
25+
}
26+
@action updateImage(file) {
27+
this.setStatusMessage('');
28+
const reader = new FileReader();
29+
30+
if (file) {
31+
this.updateFormData(file, this.formKeyName);
32+
reader.readAsDataURL(file);
33+
this.imageFileName = file.name;
34+
}
35+
reader.onload = () => {
36+
const image = reader.result;
37+
this.image = image;
38+
};
39+
}
40+
41+
@action setImageCoordinates(data) {
42+
this.imageCoordinates = data;
43+
}
44+
45+
@action handleBrowseImage(e) {
46+
const [file] = e.target.files;
47+
this.updateImage(file);
48+
this.setImageSelected(true);
49+
}
50+
51+
@action handleDrop(e) {
52+
this.preventDefaults(e); // This is used to prevent opening of image in new tab while drag and drop
53+
const [file] = e.dataTransfer.files;
54+
this.updateImage(file);
55+
this.setImageSelected(true);
56+
this.setOverDropZone(false);
57+
}
58+
@action handleDragOver(e) {
59+
this.preventDefaults(e);
60+
this.setOverDropZone(true);
61+
e.dataTransfer.dropEffect = 'move';
62+
}
63+
@action handleDragEnter(e) {
64+
this.preventDefaults(e);
65+
this.setOverDropZone(true);
66+
}
67+
@action handleDragLeave(e) {
68+
this.preventDefaults(e);
69+
this.setOverDropZone(false);
70+
}
71+
72+
@action onSubmit(e) {
73+
this.preventDefaults(e);
74+
this.formData.set('coordinates', JSON.stringify(this.imageCoordinates));
75+
this.uploadImage(this.formData);
76+
}
77+
78+
uploadImage(data) {
79+
const url = this.uploadUrl;
80+
this.setImageUploading(true);
81+
fetch(`${url}`, {
82+
method: 'POST',
83+
credentials: 'include',
84+
body: data,
85+
})
86+
.then(async (res) => {
87+
const status = res.status;
88+
const data = await res.json();
89+
const message = data.message;
90+
this.handleResponseStatusMessage(status, message);
91+
})
92+
.catch((err) => {
93+
this.setImageUploadSuccess(false);
94+
this.setStatusMessage(
95+
'Error occured, please try again and if the issue still exists contact administrator and create a issue on the repo with logs',
96+
);
97+
console.error(err);
98+
})
99+
.finally(() => {
100+
this.setImageUploading(false);
101+
});
102+
}
103+
104+
updateFormData(file, key) {
105+
const formData = new FormData();
106+
formData.append(key, file);
107+
this.formData = formData;
108+
}
109+
110+
handleResponseStatusMessage(status, message) {
111+
if (status === 200) {
112+
this.setImageUploadSuccess(true);
113+
this.args.outsideClickModel();
114+
this.toast.success(message, '', toastNotificationTimeoutOptions);
115+
} else {
116+
this.setImageUploadSuccess(false);
117+
this.setStatusMessage(message);
118+
}
119+
}
120+
121+
preventDefaults(e) {
122+
e.preventDefault();
123+
e.stopPropagation();
124+
}
125+
126+
setOverDropZone(bool) {
127+
this.overDropZone = bool;
128+
}
129+
130+
setImageSelected(bool) {
131+
this.isImageSelected = bool;
132+
}
133+
134+
setImageUploading(bool) {
135+
this.isImageUploading = bool;
136+
}
137+
138+
setImageUploadSuccess(bool) {
139+
this.imageUploadSuccess = bool;
140+
}
141+
142+
setStatusMessage(message) {
143+
this.statusMessage = message;
144+
}
145+
}

app/components/spinner.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<i class="fa fa-spinner fa-spin" ...attributes></i>

0 commit comments

Comments
 (0)