Skip to content

Commit d070b82

Browse files
author
Jovert Lota Palonpon
committedApr 4, 2019
Created a working example in Users CRUD jovertical#23
1 parent e7fd7c4 commit d070b82

File tree

8 files changed

+179
-31
lines changed

8 files changed

+179
-31
lines changed
 

Diff for: ‎app/Http/Controllers/Api/V1/UsersController.php

+19-2
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,26 @@ public function restore(Request $request, $id)
136136
*
137137
* @return Illuminate\Http\JsonResponse
138138
*/
139-
public function storeAvatar(Request $request, User $user)
139+
public function storeAvatar(Request $request, User $user) : JsonResponse
140140
{
141-
return response()->json($request->input('avatar'));
141+
if (! $user->upload($request->files->get('avatar'))) {
142+
return response()->json('Unable to process the upload', 422);
143+
}
144+
145+
return response()->json('Uploaded successfully!');
146+
}
147+
148+
/**
149+
* Destroy the user's avatar.
150+
*
151+
* @param Illuminate\Http\Request $request
152+
* @param App\User $user
153+
*
154+
* @return Illuminate\Http\JsonResponse
155+
*/
156+
public function destroyAvatar(Request $request, User $user) : JsonResponse
157+
{
158+
return response()->json($user->destroyUpload());
142159
}
143160

144161
/**

Diff for: ‎app/Traits/UploadsFiles.php

+27-3
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,49 @@
33
namespace App\Traits;
44

55
use App\Utils\Uploader;
6-
use Illuminate\Http\UploadedFile;
76

87
trait UploadsFiles
98
{
109
/**
1110
* Handle the upload for the uploader, and update it's attributes.
1211
*
13-
* @param Illuminate\Http\UploadedFile
12+
* @param mixed
1413
*
1514
* @return bool
1615
*/
17-
public function upload(UploadedFile $file)
16+
public function upload($file)
1817
{
1918
$upload = Uploader::upload($this->getDirectory(), $file);
2019

20+
// The upload attributes should be stored.
2121
foreach ($upload as $attribute => $value) {
2222
$this->attributes[$attribute] = $value;
2323
}
2424

25+
// Update the consumer.
26+
return $this->update();
27+
}
28+
29+
/**
30+
* Destroy an upload.
31+
*
32+
* @return bool
33+
*/
34+
public function destroyUpload()
35+
{
36+
$upload = collect($this->attributes)
37+
->only($this->uploadAttributes)
38+
->toArray();
39+
40+
// The upload attributes should be cleared.
41+
foreach ($this->uploadAttributes as $key => $attribute) {
42+
$this->attributes[$attribute] = null;
43+
}
44+
45+
// Remove the file(s) from the disk.
46+
Uploader::destroy($upload);
47+
48+
// Update the consumer.
2549
return $this->update();
2650
}
2751
}

Diff for: ‎app/User.php

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ class User extends Authenticatable implements JWTSubject, Uploader
3131
'password', 'remember_token',
3232
];
3333

34+
/**
35+
* The attributes used for uploads.
36+
*
37+
* @var array
38+
*/
39+
protected $uploadAttributes = [
40+
'directory',
41+
'filename',
42+
'original_filename',
43+
'filesize',
44+
'thumbnail_filesize',
45+
'url',
46+
'thumbnail_url'
47+
];
48+
3449
/**
3550
* Get the directory for uploads.
3651
*

Diff for: ‎app/Utils/Uploader.php

+29-5
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,24 @@
55
use Image;
66
use Storage;
77
use Illuminate\Support\Str;
8-
use Illuminate\Http\UploadedFile;
98

109
class Uploader
1110
{
1211
/**
1312
* Put the file into the storage.
1413
*
1514
* @param string $directory
16-
* @param Illuminate\Http\UploadedFile $file
15+
* @param mixed $file
1716
*
1817
* @return array
1918
*/
20-
public static function upload(string $directory, UploadedFile $file)
19+
public static function upload(string $directory, $file)
2120
{
2221
$disk = config('filesystems.default');
2322
$filename = str_random(64).'.'.$file->getClientOriginalExtension();
2423
$original_filename = $file->getClientOriginalName();
24+
$filesize = $file->getSize();
25+
$thumbnail_filesize = null;
2526

2627
// Upload the file
2728
$path = Storage::putFileAs($directory, $file, $filename);
@@ -42,15 +43,38 @@ public static function upload(string $directory, UploadedFile $file)
4243
$fullThumbnailPath =
4344
"{$fileSystemRoot}/{$thumbnailDirectory}/{$filename}";
4445

45-
Image::make($fullPath)
46+
$image = Image::make($fullPath)
4647
->fit(240)
4748
->save($fullThumbnailPath, 95);
4849

50+
$thumbnail_filesize = Storage::size($thumbnailPath);
4951
$thumbnail_url = Storage::url($thumbnailPath);
5052
}
5153

5254
return compact([
53-
'directory', 'filename', 'original_filename', 'url', 'thumbnail_url'
55+
'directory',
56+
'filename',
57+
'original_filename',
58+
'filesize',
59+
'thumbnail_filesize',
60+
'url',
61+
'thumbnail_url'
5462
]);
5563
}
64+
65+
/**
66+
* Destroy files from disk.
67+
*
68+
* @param array $upload
69+
*
70+
* @return bool
71+
*/
72+
public static function destroy(array $upload)
73+
{
74+
$path = "{$upload['directory']}/{$upload['filename']}";
75+
$thumbnailPath =
76+
"{$upload['directory']}/thumbnails/{$upload['filename']}";
77+
78+
return Storage::delete([$path, $thumbnailPath]);
79+
}
5680
}

Diff for: ‎database/migrations/2014_10_12_000000_create_users_table.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ public function up()
3232
$table->date('birthdate', 'Y-m-d')->nullable();
3333
$table->text('address')->nullable();
3434

35-
$table->text('path')->nullable();
3635
$table->string('directory')->nullable();
3736
$table->string('filename')->nullable();
37+
$table->string('original_filename')->nullable();
38+
$table->integer('filesize')->nullable();
39+
$table->integer('thumbnail_filesize')->nullable();
40+
$table->text('url')->nullable();
41+
$table->text('thumbnail_url')->nullable();
3842

3943
$table->string('created_by')->nullable();
4044
$table->string('updated_by')->nullable();

Diff for: ‎resources/js/ui/Dropzone.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,15 @@ FileIcon = withStyles(theme => ({
103103
function Dropzone(props) {
104104
const {
105105
classes,
106+
initialFiles,
106107
acceptedFileTypes,
107108
maxFiles,
108109
maxFileSize,
109110
handleUpload,
110111
handleFileRemoved,
111112
} = props;
112113

113-
const [files, setFiles] = useState([]);
114+
const [files, setFiles] = useState(initialFiles);
114115

115116
const getfileRejectedMessage = file => {
116117
let errors = [];
@@ -145,7 +146,7 @@ function Dropzone(props) {
145146
}
146147

147148
if (removedFile.status === 'uploaded') {
148-
handleFileRemoved(() => {
149+
handleFileRemoved(removedFile, () => {
149150
setFiles(files.filter(file => file.url !== removedFile.url));
150151
});
151152

@@ -427,6 +428,7 @@ function Dropzone(props) {
427428
}
428429

429430
Dropzone.propTypes = {
431+
initialFiles: PropTypes.array,
430432
acceptedFileTypes: PropTypes.array,
431433
maxFiles: PropTypes.number,
432434
maxFileSize: PropTypes.number,
@@ -435,6 +437,7 @@ Dropzone.propTypes = {
435437
};
436438

437439
Dropzone.defaultProps = {
440+
initialFiles: [],
438441
acceptedFileTypes: ['image/*'],
439442
maxFiles: 5,
440443
maxFileSize: 1,

Diff for: ‎resources/js/views/__backoffice/users/Create.js

+14-7
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import { Profile, Account, Avatar } from './Forms';
1919
class Create extends Component {
2020
state = {
2121
loading: false,
22-
activeStep: 2,
22+
activeStep: 0,
2323
formValues: [],
24+
user: {},
2425
errors: {},
2526
message: {},
2627
};
@@ -72,7 +73,7 @@ class Create extends Component {
7273
// Instruct the API the current step.
7374
values.step = activeStep;
7475

75-
await User.store({ ...previousValues, ...values });
76+
const user = await User.store({ ...previousValues, ...values });
7677

7778
// After persisting the previous values. Move to the next step...
7879
this.setState(prevState => {
@@ -93,8 +94,9 @@ class Create extends Component {
9394

9495
return {
9596
loading: false,
96-
message,
9797
formValues,
98+
user,
99+
message,
98100
activeStep: prevState.activeStep + 1,
99101
};
100102
});
@@ -113,7 +115,14 @@ class Create extends Component {
113115

114116
render() {
115117
const { classes, ...other } = this.props;
116-
const { loading, activeStep, formValues, errors, message } = this.state;
118+
const {
119+
loading,
120+
activeStep,
121+
formValues,
122+
user,
123+
errors,
124+
message,
125+
} = this.state;
117126

118127
const steps = ['Profile', 'Account', 'Avatar'];
119128

@@ -161,9 +170,7 @@ class Create extends Component {
161170
return (
162171
<Avatar
163172
{...other}
164-
values={{}}
165-
errors={errors}
166-
handleSubmit={this.handleSubmit}
173+
user={user}
167174
handleSkip={() =>
168175
this.props.history.push(
169176
NavigationUtils._route(

Diff for: ‎resources/js/views/__backoffice/users/Forms/Avatar.js

+65-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,59 @@ import { Button, Grid, Typography, withStyles } from '@material-ui/core';
66
import { Dropzone } from '../../../../ui';
77

88
class Avatar extends Component {
9+
state = {
10+
intitialFiles: [], // An item's format must comply to the File Object's.
11+
};
12+
13+
/**
14+
* Initial files to be fed to dropzone.
15+
*
16+
* @return {array}
17+
*/
18+
loadFiles = () => {
19+
const { user } = this.props;
20+
21+
if (!user.hasOwnProperty('filename')) {
22+
return;
23+
}
24+
25+
if (user.filename === null) {
26+
return;
27+
}
28+
29+
const files = [
30+
{
31+
name: user.original_filename,
32+
size: user.thumbnail_filesize,
33+
url: user.thumbnail_url,
34+
type: `image/${user.filename.split('.').reverse()[0]}`,
35+
status: 'uploaded',
36+
},
37+
];
38+
39+
this.setState({
40+
initialFiles: files,
41+
});
42+
};
43+
44+
/**
45+
* Handle the removal of files.
46+
*
47+
* @param {object} file The file that should be fed to the API.
48+
* @param {function} removed When called, will inform that the file is removed.
49+
*
50+
* @return {undefined}
51+
*/
52+
handleFileRemoved = async (file, removed) => {
53+
const { user } = this.props;
54+
55+
try {
56+
await axios.delete(`api/v1/users/${user.id}/avatar`);
57+
58+
removed();
59+
} catch (error) {}
60+
};
61+
962
/**
1063
* Handle the file upload.
1164
*
@@ -15,14 +68,13 @@ class Avatar extends Component {
1568
* @return {undefined}
1669
*/
1770
handleUpload = async (file, done) => {
18-
const { pageProps } = this.props;
19-
const { user } = pageProps;
71+
const { user } = this.props;
2072

2173
try {
2274
const formData = new FormData();
2375
formData.append('avatar', file);
2476

25-
const response = await fetch(`api/v1/users/${user.id}/avatar`, {
77+
await fetch(`api/v1/users/${user.id}/avatar`, {
2678
method: 'POST',
2779
headers: {
2880
Authorization:
@@ -32,11 +84,18 @@ class Avatar extends Component {
3284
},
3385
body: formData,
3486
});
87+
88+
done();
3589
} catch (error) {}
3690
};
3791

92+
componentDidMount() {
93+
this.loadFiles();
94+
}
95+
3896
render() {
3997
const { classes, handleSkip } = this.props;
98+
const { initialFiles } = this.state;
4099

41100
return (
42101
<>
@@ -45,14 +104,11 @@ class Avatar extends Component {
45104
</Typography>
46105

47106
<Dropzone
107+
initialFiles={initialFiles}
48108
maxFiles={2}
49109
maxFileSize={2}
50110
handleUpload={this.handleUpload}
51-
handleFileRemoved={removed => {
52-
setTimeout(() => {
53-
removed();
54-
}, Math.floor(Math.random() * Math.floor(10)) * 1000);
55-
}}
111+
handleFileRemoved={this.handleFileRemoved}
56112
/>
57113

58114
<div className={classes.sectionSpacer} />
@@ -75,9 +131,7 @@ class Avatar extends Component {
75131

76132
Avatar.propTypes = {
77133
classes: PropTypes.object.isRequired,
78-
values: PropTypes.object.isRequired,
79-
errors: PropTypes.object,
80-
handleSubmit: PropTypes.func.isRequired,
134+
user: PropTypes.object.isRequired,
81135
handleSkip: PropTypes.func.isRequired,
82136
};
83137

0 commit comments

Comments
 (0)
Please sign in to comment.