diff --git a/app/Http/Controllers/Web/Account/EditProfileController.php b/app/Http/Controllers/Web/Account/EditProfileController.php new file mode 100644 index 0000000..e350f38 --- /dev/null +++ b/app/Http/Controllers/Web/Account/EditProfileController.php @@ -0,0 +1,38 @@ + auth()->user()->id], + [ + 'bio' => $request->post('bio'), + 'link' => $request->post('link') + ] + ); + + auth()->user()->name = $request->name; + $save = auth()->user()->save(); + + + if($profile_info && $save) { + return response()->json([ + 'status' => true, + 'type' => 'success', + 'message' => 'Данные успешно сохранены.' + ]); + } + + return response()->json([ + 'status' => false, + 'type' => 'error', + 'message' => 'Произошла ошибка. Попробуйте еще раз.' + ]); + } +} diff --git a/app/Http/Controllers/Web/Account/ProfileController.php b/app/Http/Controllers/Web/Account/ProfileController.php new file mode 100644 index 0000000..4b49379 --- /dev/null +++ b/app/Http/Controllers/Web/Account/ProfileController.php @@ -0,0 +1,15 @@ +profileInfo; + return view('pages/account/profile', compact('user', 'profile_info')); + } +} diff --git a/app/Http/Controllers/Web/Follow/FollowsController.php b/app/Http/Controllers/Web/Follow/FollowsController.php new file mode 100644 index 0000000..4402c07 --- /dev/null +++ b/app/Http/Controllers/Web/Follow/FollowsController.php @@ -0,0 +1,18 @@ +user()->toggleFollow($user); + + return response()->json([ + 'status' => true + ]); + } +} diff --git a/app/Http/Controllers/Web/Posts/PostsController.php b/app/Http/Controllers/Web/Posts/PostsController.php index ee1cf3a..2dc57e6 100644 --- a/app/Http/Controllers/Web/Posts/PostsController.php +++ b/app/Http/Controllers/Web/Posts/PostsController.php @@ -5,12 +5,18 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Posts\CreateRequest; use App\Models\PostsModel; +use App\Models\User; class PostsController extends Controller { - public function get() { + public function feed() { return auth()->user()->feed(); } + public function get($id) { + return User::find($id)->posts; + } + + public function create(CreateRequest $request) { $post = PostsModel::create([ 'user_id' => auth()->id(), diff --git a/app/Http/Requests/Account/EditProfileRequest.php b/app/Http/Requests/Account/EditProfileRequest.php new file mode 100644 index 0000000..1524d6a --- /dev/null +++ b/app/Http/Requests/Account/EditProfileRequest.php @@ -0,0 +1,59 @@ + 'required|min:3|string|max:255', + 'bio' => 'nullable|string|min:5|max:255', + 'link' => 'nullable|active_url' + ]; + } + + public function attributes() : array { + return [ + 'name' => 'Имя', + 'bio' => 'Описание', + 'link' => 'Ссылка' + ]; + } + + public function messages() : array { + return [ + 'required' => ":attribute является обязательным полем.", + 'min' => ':attribute должен быть минимум :min символов.', + 'string' => ':attribute должен быть строкой.', + 'max' => ':attribute может быть максимум :max символов.', + 'active_url' => ':attribute должна быть рабочей ссылкой.' + ]; + } + + protected function failedValidation(Validator $validator) : void { + throw new HttpResponseException(response()->json([ + 'status' => false, + 'type' => 'error', + 'message' => $validator->errors()->first() + ])); + + } + + public function failedAuthorization() : void { + throw new HttpResponseException(response()->json([ + 'status' => false, + 'type' => 'error', + 'message' => 'Вы не можете выполнить данное действие.' + ])); + } +} diff --git a/app/Models/ProfileInfoModel.php b/app/Models/ProfileInfoModel.php new file mode 100644 index 0000000..0897ded --- /dev/null +++ b/app/Models/ProfileInfoModel.php @@ -0,0 +1,21 @@ +hasMany(PostsModel::class)->with('user'); + return $this->hasMany(PostsModel::class)->with('user')->latest(); } - public function follows() { - return $this->belongsToMany(User::class, 'follows', 'user_id', 'following_user_id'); + public function profileLink() { + return route('profile.profile', $this->username); } - public function follow(User $user) { - return $this->follows()->save($user); + public function profileInfo() { + return $this->hasOne(ProfileInfoModel::class); } } diff --git a/app/Traits/Followable.php b/app/Traits/Followable.php new file mode 100644 index 0000000..eb8be10 --- /dev/null +++ b/app/Traits/Followable.php @@ -0,0 +1,38 @@ +belongsToMany(User::class, 'follows', + 'user_id', 'following_user_id'); + } + + public function follow(User $user) + { + return $this->follows()->save($user); + } + + public function unfollow(User $user) + { + return $this->follows()->detach($user); + } + + public function toggleFollow(User $user) { + if($this->isFollowing($user)) { + return $this->unfollow($user); + } + + return $this->follow($user); + } + + + public function isFollowing(User $user) + { + return $this->follows()->where('following_user_id', $user->id)->exists(); + } +} diff --git a/database/migrations/2023_08_26_213732_profile_info_migration.php b/database/migrations/2023_08_26_213732_profile_info_migration.php new file mode 100644 index 0000000..014e2d0 --- /dev/null +++ b/database/migrations/2023_08_26_213732_profile_info_migration.php @@ -0,0 +1,31 @@ +primary('user_id'); + $table->string('bio')->nullable(); + $table->string('link')->nullable(); + $table->foreignId('user_id'); + $table->timestamps(); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/public/js/script.js b/public/js/script.js index b4a6922..99478ab 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,3 +1,32 @@ +$('#edit_profile_form').submit(function (e) { + e.preventDefault(); + + const _self = $(this); + const submit_button = _self.find(':submit'); + submit_button.addClass('disabled'); + submit_button.prop('disabled', true); + + const url = _self.attr('action'); + const data = new FormData(this); + + axios.post(url, data) + .then(data => { + if(data.data.status) { + _self.closest('#edit_profile-modal').find('button[data-modal-hide]').trigger('click'); //закрытие модального окна + createToast({...data.data}); + } else { + createToast({...data.data}); + } + }) + .catch(() => { + createToast({message: "Произошла ошибка. Попробуйте еще раз.", type: "error"}); + }) + .finally(() => { + submit_button.removeClass('disabled'); + submit_button.prop('disabled', false); + }); +}); + $('#send_email_verification_form').submit(function (e) { e.preventDefault(); @@ -125,9 +154,11 @@ function displayPost(data) { ' avatar\n' + ' \n' + '
\n' + + ' ' + '
\n' + data.user.name + ' @'+ data.user.username +'\n' + + ' ' + ' ·\n' + ' '+ post_date +'\n' + '
\n' + @@ -139,8 +170,19 @@ function displayPost(data) { ); } -function getPosts() { - axios.get('/posts/get') +function getFeed() { + axios.get('/posts/feed') + .then(data => { + data.data.reverse(); + data.data.forEach(post => { + displayPost(post); + }); + }) + .catch(() => createToast({message: "Произошла ошибка. Попробуйте еще раз.", type: "error"})) +} + +function getPosts(user_id) { + axios.get('/posts/'+ user_id +'/get') .then(data => { data.data.reverse(); data.data.forEach(post => { diff --git a/resources/assets/js/editProfile.js b/resources/assets/js/editProfile.js new file mode 100644 index 0000000..306466d --- /dev/null +++ b/resources/assets/js/editProfile.js @@ -0,0 +1,28 @@ +$('#edit_profile_form').submit(function (e) { + e.preventDefault(); + + const _self = $(this); + const submit_button = _self.find(':submit'); + submit_button.addClass('disabled'); + submit_button.prop('disabled', true); + + const url = _self.attr('action'); + const data = new FormData(this); + + axios.post(url, data) + .then(data => { + if(data.data.status) { + _self.closest('#edit_profile-modal').find('button[data-modal-hide]').trigger('click'); //закрытие модального окна + createToast({...data.data}); + } else { + createToast({...data.data}); + } + }) + .catch(() => { + createToast({message: "Произошла ошибка. Попробуйте еще раз.", type: "error"}); + }) + .finally(() => { + submit_button.removeClass('disabled'); + submit_button.prop('disabled', false); + }); +}); diff --git a/resources/assets/js/posts.js b/resources/assets/js/posts.js index b31cf31..3ae5830 100644 --- a/resources/assets/js/posts.js +++ b/resources/assets/js/posts.js @@ -38,9 +38,11 @@ function displayPost(data) { ' avatar\n' + '
\n' + '
\n' + + ' ' + '
\n' + data.user.name + ' @'+ data.user.username +'\n' + + ' ' + ' ·\n' + ' '+ post_date +'\n' + '
\n' + @@ -52,8 +54,19 @@ function displayPost(data) { ); } -function getPosts() { - axios.get('/posts/get') +function getFeed() { + axios.get('/posts/feed') + .then(data => { + data.data.reverse(); + data.data.forEach(post => { + displayPost(post); + }); + }) + .catch(() => createToast({message: "Произошла ошибка. Попробуйте еще раз.", type: "error"})) +} + +function getPosts(user_id) { + axios.get('/posts/'+ user_id +'/get') .then(data => { data.data.reverse(); data.data.forEach(post => { diff --git a/resources/views/includes/_follows-list.blade.php b/resources/views/includes/_follows-list.blade.php index e22198d..388a97b 100644 --- a/resources/views/includes/_follows-list.blade.php +++ b/resources/views/includes/_follows-list.blade.php @@ -1,7 +1,7 @@
  • avatar - +
    {{$user->name}}
    {{'@'.$user->username}}
    diff --git a/resources/views/includes/_nav-links.blade.php b/resources/views/includes/_nav-links.blade.php new file mode 100644 index 0000000..3181c33 --- /dev/null +++ b/resources/views/includes/_nav-links.blade.php @@ -0,0 +1,11 @@ + diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index aa0394c..f0367c4 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -1,4 +1,4 @@ -@extends('layouts/default') +@extends('layouts/empty') @section('title', 'Y. It`s what`s happening / Y') diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 736ac6f..d53ba60 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -8,11 +8,44 @@ - +
    -
    -
    - @yield('content') +
    +
    + @yield('modals') +
    +
    +
    + @include('includes/_nav-links') +
    +
    + @yield('content') +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    Подписки
    +
      + @foreach(auth()->user()->follows->take(5) as $user) + @include('includes/_follows-list') + @endforeach +
    + Посмотреть всех +
    +
    +
    +
    diff --git a/resources/views/layouts/empty.blade.php b/resources/views/layouts/empty.blade.php new file mode 100644 index 0000000..736ac6f --- /dev/null +++ b/resources/views/layouts/empty.blade.php @@ -0,0 +1,24 @@ + + + + @yield('title') + + + + + + + +
    +
    +
    + @yield('content') +
    +
    + + + + +@yield('scripts') + + diff --git a/resources/views/pages/account/profile.blade.php b/resources/views/pages/account/profile.blade.php new file mode 100644 index 0000000..912aeb0 --- /dev/null +++ b/resources/views/pages/account/profile.blade.php @@ -0,0 +1,137 @@ +@extends('layouts/default') + +@section('title', "$user->name (@$user->username)") + +@section('modals') + +@endsection + +@section('content') +
    + banner +
    +
    + Rounded avatar +
    + @if($user->id === auth()->id()) +
    + +
    + @else +
    + @csrf + +
    + @endif +
    +
    +
    +
    {{$user->name}}
    + {{'@'.$user->username}} +
    + @if($profile_info && $profile_info->bio) +
    +

    {!!nl2br($profile_info->bio)!!}

    +
    + @endif +
    +
    + @if($profile_info && $profile_info->link) + + @endif +
    + + + + + Присоединился + {{$user->created_at->format('m Y')}} + +
    +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    + @csrf +
    + avatar + +
    +
    + +
    +
    +
    +
    +@endsection + +@section('scripts') + +@endsection diff --git a/resources/views/pages/home.blade.php b/resources/views/pages/home.blade.php index d617486..7949ef7 100644 --- a/resources/views/pages/home.blade.php +++ b/resources/views/pages/home.blade.php @@ -3,77 +3,35 @@ @section('title', 'home') @section('content') -
    -
    -
    - -
    -
    -
    -
    -

    Главная

    -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    - @csrf -
    - avatar - -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    -
    -
    Подписки
    -
      - @foreach(auth()->user()->follows->take(5) as $user) - @include('includes/_follows-list') - @endforeach -
    - Посмотреть всех -
    -
    + +
    @endsection @section('scripts') - + @endsection diff --git a/routes/web.php b/routes/web.php index b911c14..198ba18 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,7 +1,10 @@ ['auth', 'verified']], function () { Route::group(['prefix' => 'posts', 'as' => 'posts.'], function () { - Route::get('/get', [PostsController::class, 'get'])->name('get'); + Route::get('/feed', [PostsController::class, 'feed'])->name('feed'); + Route::get('/{id}/get', [PostsController::class, 'get'])->name('posts'); Route::post('/create', [PostsController::class, 'create'])->name('create'); }); + + Route::group(['prefix' => 'profile', 'as' => 'profile.'], function () { + Route::get('/{user:username}', [ProfileController::class, 'render'])->name('profile'); + Route::post('/{user:username}/follow', [FollowsController::class, 'follow']); + Route::post('/edit', [EditProfileController::class, 'edit'])->name('edit'); + }); });