Compare commits
47 Commits
main
...
ffee3b257d
Author | SHA1 | Date | |
---|---|---|---|
ffee3b257d
|
|||
957e90aff6
|
|||
252f5ee6b7
|
|||
2a2869e2c6
|
|||
6a033d108c
|
|||
620217e1d5
|
|||
a7e93681f3
|
|||
9d7da548e3
|
|||
2349008131
|
|||
908b1e4bec
|
|||
54db2e8e2b
|
|||
127ebe79ae
|
|||
010f8cf278
|
|||
a7ccb3100f
|
|||
753421a5a0
|
|||
7fd3be06ea
|
|||
1ccc934561
|
|||
f32f13604f
|
|||
8974721c9c
|
|||
d47c719d13
|
|||
af3aa905bd
|
|||
d0b3f9094c
|
|||
d943e81da4
|
|||
f5977c1b5d
|
|||
992326ebf0
|
|||
021dfc85f9
|
|||
93bbf2296d
|
|||
0c7d2c9f24
|
|||
a09a75b69e
|
|||
58003e5d18
|
|||
f500c8691f
|
|||
5b71fa9781
|
|||
7c58a0bc63
|
|||
e294dc07e0
|
|||
996b1b4faf
|
|||
99f0bafe93
|
|||
ac90d45519
|
|||
95ae8c562d
|
|||
65f0a49cdb
|
|||
5839ef2a54
|
|||
e049990606
|
|||
d35421a97e
|
|||
390c5b8087
|
|||
9518d6a811
|
|||
08133d0b05
|
|||
2cbcaee7f5
|
|||
26a56e6e5b
|
@@ -14,3 +14,6 @@ DB_PASSWORD=password
|
||||
EXTERNAL_WEBSERVER_PORT=80
|
||||
CURRENT_UID=1000
|
||||
XDG_CONFIG_HOME=/tmp
|
||||
|
||||
VITE_CV_APP_URL=https://cv.kamilcraft.com
|
||||
VITE_PORT=3001
|
||||
|
55
app/Console/Commands/CVInfo.php
Normal file
55
app/Console/Commands/CVInfo.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CV;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CVInfo extends Command
|
||||
{
|
||||
protected $signature = 'cv:info {id}';
|
||||
|
||||
protected $description = 'Show info about CV';
|
||||
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (! ($cv = CV::find($id = $this->argument('id')))) {
|
||||
$this->error('CV not found!');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->line('ID: '. $cv->id);
|
||||
$this->line('Token: '. $cv->token);
|
||||
$this->line('Company: '. $cv->recipient);
|
||||
$this->line('Phone: '. $cv->formattedPhoneNumber .', '. $cv->PhoneNumber);
|
||||
$this->line('Locations: '. implode(' / ', $cv->locations));
|
||||
$this->line('Actual views: '. $cv->info()->select('id')->get()->count());
|
||||
$this->line('Registered views: '. $cv->views);
|
||||
$this->line('Mission: '. (is_null($mission = $cv->mission) ? 'default' : $mission));
|
||||
$this->line('RODO: '. (is_null($rodo = $cv->rodo) ? 'default' : $rodo));
|
||||
$this->line('');
|
||||
|
||||
$this->line('Showed list:');
|
||||
$listCVInfo = [];
|
||||
foreach (($cvInfoList = $cv->info()->orderByDesc('id')->get(['id', 'ip', 'created_at'])) as $cvInfo) {
|
||||
$listCVInfo[] = [
|
||||
'id' => $cvInfo->id,
|
||||
'ip' => $cvInfo->ip,
|
||||
'created' => $cvInfo->created_at->format('d-m-Y H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($cvInfoList->count() === 0)
|
||||
$this->warn('Empty!');
|
||||
else
|
||||
$this->table(
|
||||
['Id', 'IP', 'Showed'],
|
||||
$listCVInfo,
|
||||
);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
51
app/Console/Commands/CreateCV.php
Normal file
51
app/Console/Commands/CreateCV.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CV;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Command\Command as CommandAlias;
|
||||
|
||||
class CreateCV extends Command
|
||||
{
|
||||
protected $signature = 'cv:create
|
||||
{recipient : Company}
|
||||
{email : E-mail address}
|
||||
{phone : Phone number - with spaces}
|
||||
{location?* : List of locations}
|
||||
{--mission= : Description of mission}
|
||||
{--rodo= : Description of rodo}
|
||||
{--position= : Set position value}';
|
||||
|
||||
protected $description = 'Create CV';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$recipient = $this->argument('recipient');
|
||||
$email = $this->argument('email');
|
||||
$phone = $this->argument('phone');
|
||||
$locations = $this->argument('location');
|
||||
$mission = $this->option('mission');
|
||||
$rodo = $this->option('rodo');
|
||||
$position = $this->option('position');
|
||||
|
||||
CV::query()
|
||||
->create([
|
||||
'token' => Str::random(50),
|
||||
'recipient' => $recipient,
|
||||
'email' => $email,
|
||||
'phone_number' => $phone,
|
||||
'locations' => $locations,
|
||||
'mission' => $mission,
|
||||
'rodo' => $rodo,
|
||||
'position' => $position,
|
||||
]);
|
||||
|
||||
$this->info('Created!');
|
||||
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
}
|
44
app/Console/Commands/ListCV.php
Normal file
44
app/Console/Commands/ListCV.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Http\Resources\CVResource;
|
||||
use App\Models\CV;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ListCV extends Command
|
||||
{
|
||||
protected $signature = 'cv:list';
|
||||
|
||||
protected $description = 'List of CV';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$cvList = CV::all();
|
||||
$cvListCollection = [];
|
||||
|
||||
/** @var CV $cv */
|
||||
foreach ($cvList as $cv) {
|
||||
$cvResource = (new CVResource($cv))->setAsConsole()->toArray();
|
||||
$cvListCollection[] = [
|
||||
'id' => $cvResource['id'],
|
||||
'token' => $cvResource['token'],
|
||||
'email' => $cvResource['email'],
|
||||
'recipient' => $cvResource['recipient'],
|
||||
'locations' => implode(' / ', $cvResource['locations']),
|
||||
'phone' => $cvResource['phone']['formattedPhoneNumber'],
|
||||
'views' => $cvResource['views'] . '/' . $cvResource['registeredViews'],
|
||||
];
|
||||
}
|
||||
|
||||
if (count($cvListCollection) > 0)
|
||||
$this->table(
|
||||
['Id', 'Token', 'E-mail', 'Company', 'Locations', 'Phone', 'Views'],
|
||||
$cvListCollection
|
||||
);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
25
app/Console/Commands/RemoveIPFromList.php
Normal file
25
app/Console/Commands/RemoveIPFromList.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\CVInfo;
|
||||
|
||||
class RemoveIPFromList extends Command
|
||||
{
|
||||
protected $signature = 'cv:rm-ip {ip : IP address on list.}';
|
||||
|
||||
protected $description = 'Remove IP address from show list.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$ip = $this->argument('ip');
|
||||
$cvInfo = CVInfo::query()->where('ip', $ip);
|
||||
$cvInfo->delete();
|
||||
$this->info('IP '. $ip .' deleted from database.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
80
app/Console/Commands/UpdateCV.php
Normal file
80
app/Console/Commands/UpdateCV.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\CV;
|
||||
|
||||
class UpdateCV extends Command
|
||||
{
|
||||
protected $signature = 'cv:update
|
||||
{id : ID CV element}
|
||||
{--company= : Company name}
|
||||
{--phone= : Phone number}
|
||||
{--begin-location : Add begin}
|
||||
{--add-location=* : Add locations}
|
||||
{--remove-location=* : Remove lcoations}
|
||||
{--mission= : Set new text value}
|
||||
{--rodo= : Set new text value}
|
||||
{--position= : Set position value}';
|
||||
|
||||
protected $description = 'Update CV element';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (! ($id = $this->argument('id')) || $id <= 0) {
|
||||
$this->error('Incorrect id');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$cv = CV::find($id);
|
||||
|
||||
if ($company = $this->option('company')) {
|
||||
$cv->recipient = $company;
|
||||
}
|
||||
if ($phone = $this->option('phone')) {
|
||||
$cv->phone_number = $phone;
|
||||
}
|
||||
if (count($addLocations = $this->option('remove-location')) > 0) {
|
||||
$locations = $cv->locations;
|
||||
$locations = array_diff($locations, $addLocations);
|
||||
$cv->locations = $locations;
|
||||
}
|
||||
if (count($addLocations = $this->option('add-location')) > 0) {
|
||||
$locations = $cv->locations;
|
||||
|
||||
$clearLocations = [];
|
||||
foreach ($addLocations as $location) {
|
||||
if (in_array($location, $locations)) {
|
||||
$this->warn('"'. $location .'" exists! This value was not added.');
|
||||
$clearLocations[] = $location;
|
||||
}
|
||||
}
|
||||
$addLocations = array_diff($addLocations, $clearLocations);
|
||||
|
||||
if ($this->option('begin-location'))
|
||||
$locations = array_merge($addLocations, $locations);
|
||||
else
|
||||
$locations = array_merge($locations, $addLocations);
|
||||
$cv->locations = $locations;
|
||||
}
|
||||
|
||||
if ($mission = $this->option('mission')) {
|
||||
$cv->mission = $mission === 'null' ? null : $mission;
|
||||
}
|
||||
if ($rodo = $this->option('rodo')) {
|
||||
$cv->rodo = $rodo === 'null' ? null : $rodo;
|
||||
}
|
||||
if ($position = $this->option('position')) {
|
||||
$cv->position = $position === 'null' ? null : $position;
|
||||
}
|
||||
|
||||
$cv->save();
|
||||
|
||||
$this->info('Updated!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
24
app/Http/Controllers/Api/CVController.php
Normal file
24
app/Http/Controllers/Api/CVController.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CVResource;
|
||||
use App\Models\CV;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CVController extends Controller
|
||||
{
|
||||
public function show(CV $cv): JsonResource
|
||||
{
|
||||
$cv->info()
|
||||
->create([
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
]);
|
||||
$cv->update(['views' => $cv->views+=1]);
|
||||
|
||||
return new CVResource($cv);
|
||||
}
|
||||
}
|
27
app/Http/Controllers/Api/MessageController.php
Normal file
27
app/Http/Controllers/Api/MessageController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\MessageRequest;
|
||||
use App\Models\Message;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class MessageController extends Controller
|
||||
{
|
||||
public function store(MessageRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->toArray();
|
||||
Message::query()->create([
|
||||
'message' => $data['message'],
|
||||
'email' => $data['email'],
|
||||
'sender' => $data['sender'],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Dziękuję za wiadomość! Odpowiem możliwie najszybciej.'
|
||||
]);
|
||||
}
|
||||
}
|
@@ -8,8 +8,7 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\View\View;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
@@ -43,12 +42,12 @@ class LoginController extends Controller
|
||||
return redirect()->route('admin.auth.login');
|
||||
}
|
||||
|
||||
public function login(): View|RedirectResponse
|
||||
public function login(): InertiaResponse|RedirectResponse
|
||||
{
|
||||
if (Auth::check())
|
||||
return redirect()->route('admin.home');
|
||||
|
||||
return view('auth.login');
|
||||
return inertia('Login');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Repository\Interfaces\CategoryRepository;
|
||||
use App\Repository\Interfaces\ProjectRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class AdminPanelController extends Controller
|
||||
{
|
||||
@@ -16,14 +16,15 @@ class AdminPanelController extends Controller
|
||||
private ProjectRepository $projectRepository
|
||||
) {
|
||||
$this->categoryRepository->auth = true;
|
||||
$this->projectRepository->auth = true;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request): View
|
||||
public function __invoke(Request $request): InertiaResponse
|
||||
{
|
||||
$categories = $this->categoryRepository->all();
|
||||
$projects = $this->projectRepository->all();
|
||||
|
||||
return view('dashboard.home', compact('categories', 'projects'));
|
||||
return inertia('Dashboard/Index', compact('categories', 'projects'));
|
||||
}
|
||||
|
||||
}
|
||||
|
122
app/Http/Controllers/Dashboard/CVController.php
Normal file
122
app/Http/Controllers/Dashboard/CVController.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CVRequest;
|
||||
use App\Http\Resources\CVInfoCollection;
|
||||
use App\Http\Resources\FullCVCollection;
|
||||
use App\Http\Resources\FullCVResource;
|
||||
use App\Models\CV;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class CVController extends Controller
|
||||
{
|
||||
public function index(Request $request): InertiaResponse
|
||||
{
|
||||
return inertia('CV/Index', [
|
||||
'cvs' => new FullCVCollection(CV::all()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(CV $cv): InertiaResponse
|
||||
{
|
||||
return inertia('CV/Show', [
|
||||
'cv' => new FullCVResource($cv),
|
||||
'cvInfo' => new CVInfoCollection($cv->info()->orderByDesc('id')->get()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): InertiaResponse
|
||||
{
|
||||
return inertia('CV/Create');
|
||||
}
|
||||
|
||||
public function store(CVRequest $request): RedirectResponse
|
||||
{
|
||||
CV::query()
|
||||
->create([
|
||||
'token' => Str::random(50),
|
||||
'recipient' => $request->get('recipient'),
|
||||
'email' => $request->get('email'),
|
||||
'phone_number' => $request->get('phone_number'),
|
||||
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
||||
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||
'position' => $request->get('position'),
|
||||
'notes' => $request->get('notes'),
|
||||
]);
|
||||
return redirect()
|
||||
->route('admin.cv.store')
|
||||
->with('success', 'Utworzono nowe CV dla firmy ' . $request->get('recipient'));
|
||||
}
|
||||
|
||||
public function updateSendStatus(CV $cv): RedirectResponse
|
||||
{
|
||||
$cv->update([
|
||||
'sended' => true,
|
||||
'sended_timestamp' => now()
|
||||
]);
|
||||
return redirect()
|
||||
->route('admin.cv.show', ['cv' => $cv])
|
||||
->with('success', 'Status wysłania ustawiono jako "wysłano".');
|
||||
}
|
||||
|
||||
public function edit(CV $cv): InertiaResponse
|
||||
{
|
||||
return inertia('CV/Edit', [
|
||||
'cv' => new FullCVResource($cv),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(CVRequest $request, CV $cv): RedirectResponse
|
||||
{
|
||||
$toUpdate = [
|
||||
'recipient' => $request->get('recipient'),
|
||||
'email' => $request->get('email'),
|
||||
'phone_number' => $request->get('phone_number'),
|
||||
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
||||
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||
'position' => $request->get('position'),
|
||||
'notes' => $request->get('notes'),
|
||||
];
|
||||
|
||||
if ($cv->sended && ! $request->boolean('sended')) {
|
||||
$toUpdate = array_merge($toUpdate, [
|
||||
'sended' => false,
|
||||
'sended_timestamp' => null,
|
||||
]);
|
||||
} else if (! $cv->sended && $request->boolean('sended')) {
|
||||
$toUpdate = array_merge($toUpdate, [
|
||||
'sended' => true,
|
||||
'sended_timestamp' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$cv->update($toUpdate);
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', 'Zaktualizowano CV dla firmy ' . $request->get('recipient'));
|
||||
}
|
||||
|
||||
public function delete(CV $cv): InertiaResponse
|
||||
{
|
||||
return inertia('CV/ConfirmDelete', compact('cv'));
|
||||
}
|
||||
|
||||
public function destroy(CV $cv): RedirectResponse
|
||||
{
|
||||
$name = $cv->recipient;
|
||||
$cv->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.cv.index')
|
||||
->with('info', 'Usunięto CV dla firmy "'. $name .'"');
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ use App\Models\Category;
|
||||
use App\Repository\Interfaces\CategoryRepository;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class CategoryController
|
||||
{
|
||||
@@ -21,35 +22,35 @@ class CategoryController
|
||||
{
|
||||
$validate = $request->validated();
|
||||
if ($this->categoryRepository->update($category, $validate)) {
|
||||
return back()->with('message', 'Zaktualizowano kategorię!');
|
||||
return back()
|
||||
->with('success', 'Zaktualizowano kategorię!');
|
||||
}
|
||||
|
||||
return back()->withError(['message_error', 'Wystąpił błąd podczas aktualizacji!']);
|
||||
return back()
|
||||
->with(['error', 'Wystąpił błąd podczas aktualizacji!']);
|
||||
}
|
||||
|
||||
public function store(CategoryRequest $request)
|
||||
{
|
||||
$validate = $request->validated();
|
||||
if ($category = $this->categoryRepository->create($validate)) {
|
||||
return redirect()->route('admin.category.update', ['category' => $category])->with('message', 'Utworzono kategorię!');
|
||||
}
|
||||
|
||||
return back()->withError(['message_error', 'Wystąpił błąd podczas tworzenia!']);
|
||||
$category = $this->categoryRepository->create($request->validated());
|
||||
return redirect()
|
||||
->route('admin.category.update', compact('category'))
|
||||
->with('message', 'Utworzono kategorię!');
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
public function create(): InertiaResponse
|
||||
{
|
||||
return view('dashboard.categories.create');
|
||||
return inertia('Categories/Create');
|
||||
}
|
||||
|
||||
public function edit(Category $category): View
|
||||
public function edit(Category $category): InertiaResponse
|
||||
{
|
||||
return view('dashboard.categories.edit', compact('category'));
|
||||
return inertia('Categories/Edit', compact('category'));
|
||||
}
|
||||
|
||||
public function delete(Category $category): View
|
||||
public function delete(Category $category): InertiaResponse
|
||||
{
|
||||
return view('dashboard.categories.delete', compact('category'));
|
||||
return inertia('Categories/ConfirmDelete', compact('category'));
|
||||
}
|
||||
|
||||
public function destroy(Category $category): RedirectResponse
|
||||
@@ -57,7 +58,9 @@ class CategoryController
|
||||
$name = $category->name;
|
||||
$category->delete();
|
||||
|
||||
return redirect()->route('admin.home')->with('message', 'Usunięto kategorię "'. $name .'"');
|
||||
return redirect()
|
||||
->route('admin.home')
|
||||
->with('info', 'Usunięto kategorię "'. $name .'"');
|
||||
}
|
||||
|
||||
}
|
||||
|
45
app/Http/Controllers/Dashboard/MessageController.php
Normal file
45
app/Http/Controllers/Dashboard/MessageController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\MessageCollection;
|
||||
use App\Http\Resources\MessageResource;
|
||||
use App\Models\Message;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class MessageController extends Controller
|
||||
{
|
||||
public function index() : InertiaResponse {
|
||||
return inertia('Messages/Index', [
|
||||
'messages' => new MessageCollection(Message::query()->orderByDesc('id')->get()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Message $message) : InertiaResponse
|
||||
{
|
||||
return inertia('Messages/Show', [
|
||||
'message' => new MessageResource($message),
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Message $message) : InertiaResponse
|
||||
{
|
||||
return inertia('Messages/ConfirmDelete', [
|
||||
'message' => new MessageResource($message),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Message $message) : RedirectResponse
|
||||
{
|
||||
$sender = $message->sender;
|
||||
$message->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.message.index')
|
||||
->with(['success' => 'Wiadomość od '. $sender .' została usunięta']);
|
||||
}
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Requests\ProjectRequest;
|
||||
use App\Models\Project;
|
||||
use App\Repository\Interfaces\ProjectRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
|
||||
class ProjectController
|
||||
{
|
||||
@@ -18,24 +19,25 @@ class ProjectController
|
||||
$this->projectRepository->auth = true;
|
||||
}
|
||||
|
||||
public function edit(Project $project): View
|
||||
public function edit(Project $project): InertiaResponse
|
||||
{
|
||||
return view('dashboard.projects.edit', compact('project'));
|
||||
return inertia('Projects/Edit', compact('project'));
|
||||
}
|
||||
|
||||
public function update(ProjectRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
if ($this->projectRepository->update($project, $validated)) {
|
||||
return back()->with('message', 'Zaktualizowano projekt!');
|
||||
if ($this->projectRepository->update($project, $request->validated())) {
|
||||
return back()
|
||||
->with('success', 'Zaktualizowano projekt!');
|
||||
}
|
||||
|
||||
return back()->withError(['message_error', 'Wystąpił błąd podczas aktualizacji!']);
|
||||
return back()
|
||||
->with(['error', 'Wystąpił błąd podczas aktualizacji!']);
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
public function create(): InertiaResponse
|
||||
{
|
||||
return view('dashboard.projects.create');
|
||||
return inertia('Projects/Create');
|
||||
}
|
||||
|
||||
public function store(ProjectRequest $request): RedirectResponse
|
||||
@@ -44,22 +46,24 @@ class ProjectController
|
||||
if ($project = $this->projectRepository->create($validated)) {
|
||||
return redirect()
|
||||
->route('admin.project.update', compact('project'))
|
||||
->with('message', 'Utworzono projekt!');
|
||||
->with('success', 'Utworzono projekt!');
|
||||
}
|
||||
|
||||
return back()->withError(['message_error', 'Wystąpił błąd podczas tworzenia!']);
|
||||
return back()->withError(['error', 'Wystąpił błąd podczas tworzenia!']);
|
||||
}
|
||||
|
||||
public function delete(Project $project): View
|
||||
public function delete(Project $project): InertiaResponse
|
||||
{
|
||||
return view('dashboard.projects.delete', compact('project'));
|
||||
return inertia('Projects/ConfirmDelete', compact('project'));
|
||||
}
|
||||
|
||||
public function destroy(Project $project): RedirectResponse
|
||||
{
|
||||
$title = $project->title;
|
||||
$project->delete();
|
||||
return redirect()->route('admin.home')->with('message', 'Usunięto projekt "'. $title .'"');
|
||||
return redirect()
|
||||
->route('admin.home')
|
||||
->with('info', 'Usunięto projekt "'. $title .'"');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
Core::class
|
||||
Core::class,
|
||||
];
|
||||
|
||||
protected $middlewareGroups = [
|
||||
@@ -28,6 +28,7 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
28
app/Http/Middleware/HandleInertiaRequests.php
Normal file
28
app/Http/Middleware/HandleInertiaRequests.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Middleware;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
public function share(Request $request): array
|
||||
{
|
||||
return array_merge(parent::share($request), [
|
||||
'messages' => $this->getFlashData($request),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFlashData(Request $request): Closure
|
||||
{
|
||||
return fn(): array => [
|
||||
'success' => $request->session()->get('success'),
|
||||
'error' => $request->session()->get('error'),
|
||||
'info' => $request->session()->get('info'),
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
|
37
app/Http/Requests/CVRequest.php
Normal file
37
app/Http/Requests/CVRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CVRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'recipient' => 'required|string',
|
||||
'email' => 'required|email',
|
||||
'phone_number' => 'required|regex:/^([0-9\s\+]*)$/i|min:12|max:15',
|
||||
'locations' => 'required|array',
|
||||
'mission' => 'nullable|string',
|
||||
'rodo' => 'nullable|string',
|
||||
'position' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
'sended' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'sended' => $this->toBoolean($this->sended),
|
||||
]);
|
||||
}
|
||||
|
||||
private function toBoolean($booleable): bool
|
||||
{
|
||||
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
}
|
@@ -1,34 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|min:3|max:25',
|
||||
'slug' => 'required|string|min:3|max:25',
|
||||
'priority' => 'required|numeric|min:0|max:10',
|
||||
'default' => 'nullable|in:yes,1,true,on',
|
||||
'visible' => 'nullable|in:yes,1,true,on'
|
||||
'default' => 'nullable|boolean',
|
||||
'visible' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'default' => $this->toBoolean($this->default),
|
||||
'visible' => $this->toBoolean($this->visible),
|
||||
]);
|
||||
}
|
||||
|
||||
private function toBoolean($booleable): bool
|
||||
{
|
||||
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
|
34
app/Http/Requests/MessageRequest.php
Normal file
34
app/Http/Requests/MessageRequest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MessageRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'message' => 'required|string|min:3|max:500',
|
||||
'sender' => 'required|string|min:3|max:50',
|
||||
'email' => 'required|email|max:250',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'message.required' => 'Pole wiadomości jest wymagane.',
|
||||
'sender.required' => 'Pole nadawcy jest wymagane.',
|
||||
'email.required' => 'Pole e-mail jest wymagane.',
|
||||
'message.min' => 'Pole wiadomości wymaga 3 znaki.',
|
||||
'sender.min' => 'Pole nadawcy wymaga 3 znaki.',
|
||||
'message.max' => 'Pole wiadomości może mieć maksymalnie 50 znaków.',
|
||||
'sender.max' => 'Pole nadawcy może mieć maksymalnie 500 znaków.',
|
||||
'email.email' => 'Pole musi być e-mailem.',
|
||||
'email.max' => 'Pole e-mail może mieć maksymalnie 250 znaków.',
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,27 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProjectRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|min:3|max:255',
|
||||
@@ -36,8 +23,20 @@ class ProjectRequest extends FormRequest
|
||||
|
||||
'project_url' => 'nullable|string',
|
||||
'project_version' => 'nullable|string',
|
||||
'description' => 'nullable|string',
|
||||
'visible' => 'nullable|in:yes,1,true,on'
|
||||
'description' => 'required|string|min:3',
|
||||
'visible' => 'required|boolean'
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'visible' => $this->toBoolean($this->visible),
|
||||
]);
|
||||
}
|
||||
|
||||
private function toBoolean($booleable): bool
|
||||
{
|
||||
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
|
17
app/Http/Resources/CVCollection.php
Normal file
17
app/Http/Resources/CVCollection.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class CVCollection extends ResourceCollection
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
}
|
17
app/Http/Resources/CVInfoCollection.php
Normal file
17
app/Http/Resources/CVInfoCollection.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class CVInfoCollection extends ResourceCollection
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
}
|
21
app/Http/Resources/CVInfoResource.php
Normal file
21
app/Http/Resources/CVInfoResource.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CVInfoResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'ip' => $this->ip,
|
||||
'created_at' => $this->created_at->format('d-m-Y H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
50
app/Http/Resources/CVResource.php
Normal file
50
app/Http/Resources/CVResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CVResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
private bool $isConsole = false;
|
||||
|
||||
public function toArray($request = null): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->when($this->isConsole, fn (): int => $this->id),
|
||||
'token' => $this->token,
|
||||
'email' => $this->email,
|
||||
'recipient' => $this->when($this->isConsole, fn (): string => $this->recipient),
|
||||
'phone' => new PhoneResource($this->resource),
|
||||
'locations' => $this->locations,
|
||||
'views' => $this->when(
|
||||
$this->isConsole,
|
||||
fn (): int => $this->resource->info()->select('id')->get()->count()
|
||||
),
|
||||
'registeredViews' => $this->when($this->isConsole, fn (): int => $this->views),
|
||||
'mission' => $this->when(
|
||||
!is_null($this->mission),
|
||||
fn (): array => explode(PHP_EOL, $this->mission, 5)
|
||||
),
|
||||
'rodo' => $this->when(
|
||||
!is_null($this->rodo),
|
||||
$this->rodo
|
||||
),
|
||||
'position' => $this->when(
|
||||
!is_null($this->position),
|
||||
$this->position
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function setAsConsole(): self
|
||||
{
|
||||
$this->isConsole = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -1,17 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class CategoryCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return CategoryResource::collection($this->collection);
|
||||
|
@@ -1,21 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CategoryResource extends JsonResource
|
||||
{
|
||||
|
||||
public static $wrap = null;
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
|
16
app/Http/Resources/FullCVCollection.php
Normal file
16
app/Http/Resources/FullCVCollection.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class FullCVCollection extends ResourceCollection
|
||||
{
|
||||
public function toArray($request): array|JsonResource
|
||||
{
|
||||
return FullCVResource::collection($this->collection);
|
||||
}
|
||||
}
|
36
app/Http/Resources/FullCVResource.php
Normal file
36
app/Http/Resources/FullCVResource.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class FullCVResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request = null): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'token' => $this->token,
|
||||
'email' => $this->email,
|
||||
'recipient' => $this->recipient,
|
||||
'phone' => new PhoneResource($this->resource),
|
||||
'locations' => $this->locations,
|
||||
'views' => $this->resource->info()->select('id')->get()->count(),
|
||||
'registeredViews' => $this->views,
|
||||
'sended' => [
|
||||
'status' => $this->sended,
|
||||
'datetime' => $this->sended_timestamp?->format('d-m-Y H:i:s'),
|
||||
],
|
||||
'position' => $this->position,
|
||||
'mission' => explode(PHP_EOL, $this->mission ?? '', 5),
|
||||
'rodo' => $this->rodo,
|
||||
'notes' => $this->notes,
|
||||
'created' => $this->created_at->format('d-m-Y H:i:s'),
|
||||
'updated' => $this->updated_at->format('d-m-Y H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
17
app/Http/Resources/MessageCollection.php
Normal file
17
app/Http/Resources/MessageCollection.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class MessageCollection extends ResourceCollection
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
}
|
22
app/Http/Resources/MessageResource.php
Normal file
22
app/Http/Resources/MessageResource.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class MessageResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'sender' => $this->sender,
|
||||
'email' => $this->email,
|
||||
'message' => $this->message,
|
||||
];
|
||||
}
|
||||
}
|
20
app/Http/Resources/PhoneResource.php
Normal file
20
app/Http/Resources/PhoneResource.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PhoneResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'number' => $this->phone_number,
|
||||
'formattedNumber' => $this->formattedPhoneNumber,
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,18 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ProjectCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
public function toArray($request): JsonResource
|
||||
{
|
||||
return ProjectResource::collection($this->collection);
|
||||
}
|
||||
|
@@ -1,21 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ProjectResource extends JsonResource
|
||||
{
|
||||
|
||||
public static $wrap = null;
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
@@ -30,5 +25,4 @@ class ProjectResource extends JsonResource
|
||||
'description' => $this->description,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
69
app/Models/CV.php
Normal file
69
app/Models/CV.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $token
|
||||
* @property string $recipient
|
||||
* @property string $email
|
||||
* @property string|null $phoneNumber
|
||||
* @property array $locations
|
||||
* @property string|null $mission
|
||||
* @property string|null $rodo
|
||||
* @property string|null $position
|
||||
* @property string|null $notes
|
||||
* @property int $views
|
||||
* @property bool $sended
|
||||
*/
|
||||
class CV extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'cvs';
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'locations' => 'array',
|
||||
'views' => 'integer',
|
||||
'sended' => 'boolean',
|
||||
'sended_timestamp' => 'datetime',
|
||||
];
|
||||
|
||||
protected function phoneNumber(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn (mixed $value): string => str_replace(' ', '', $value ?? ''),
|
||||
set: fn (mixed $value): string => str_replace(' ', '', $value ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
protected function formattedPhoneNumber(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function (mixed $value, array $attributes): ?string {
|
||||
$number = str_replace(' ', '', $attributes['phone_number'] ?? '');
|
||||
for ($i = 3; $i < 12; $i+=4) {
|
||||
$number = substr_replace($number, ' ', $i, 0);
|
||||
}
|
||||
return $number;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public function info(): HasMany
|
||||
{
|
||||
return $this->hasMany(CVInfo::class, 'cv_id');
|
||||
}
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'token';
|
||||
}
|
||||
}
|
28
app/Models/CVInfo.php
Normal file
28
app/Models/CVInfo.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $cv_id
|
||||
* @property int $ip
|
||||
*/
|
||||
class CVInfo extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'cv_infos';
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function cv(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CV::class, ownerKey: 'cv_id');
|
||||
}
|
||||
}
|
23
app/Models/Message.php
Normal file
23
app/Models/Message.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $message
|
||||
* @param string $email
|
||||
* @param string $sender
|
||||
*/
|
||||
class Message extends Model
|
||||
{
|
||||
use HasFactory,
|
||||
SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
}
|
@@ -48,7 +48,7 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
public function update(Category $category, array $data = []): bool
|
||||
{
|
||||
$data = $this->parseToArray($data);
|
||||
if (!$category->default && isset($data['default']) && $data['default'] === true)
|
||||
if (!$category->default && $data['default'] === true)
|
||||
$this->unsetDefault();
|
||||
|
||||
return $category
|
||||
@@ -58,7 +58,7 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
public function create(array $data = []): Category
|
||||
{
|
||||
$data = $this->parseToArray($data);
|
||||
if (isset($data['default']) && $data['default'] === true)
|
||||
if ($data['default'] === true)
|
||||
$this->unsetDefault();
|
||||
|
||||
return $this->category
|
||||
@@ -85,18 +85,12 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
if (isset($data['priority']) && !is_integer($data['priority']))
|
||||
$toSave['priority'] = (int)$data['priority'];
|
||||
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['default'], ['yes', 'on', 1, true])
|
||||
) $toSave['default'] = true;
|
||||
else $toSave['default'] = false;
|
||||
$toSave['default'] = $data['default'];
|
||||
|
||||
if (
|
||||
(isset($toSave['default']) && $toSave['default'] === true) ||
|
||||
(isset($data['visible']) &&
|
||||
in_array($data['visible'], ['yes', 'on', 1, true]))
|
||||
) $toSave['visible'] = true;
|
||||
else $toSave['visible'] = false;
|
||||
if ($toSave['default'] === true)
|
||||
$toSave['visible'] = true;
|
||||
else
|
||||
$toSave['visible'] = $data['visible'];
|
||||
|
||||
return $toSave;
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ use App\Http\Resources\ProjectCollection;
|
||||
use App\Http\Resources\ProjectResource;
|
||||
use App\Models\Project;
|
||||
use App\Repository\Interfaces\ProjectRepository as ProjectRepositoryInterface;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ProjectRepository implements ProjectRepositoryInterface
|
||||
@@ -104,11 +103,7 @@ class ProjectRepository implements ProjectRepositoryInterface
|
||||
else
|
||||
$toSave['update_date'] = null;
|
||||
|
||||
if (
|
||||
isset($data['visible']) &&
|
||||
in_array($data['visible'], ['yes', 'on', 1, true])
|
||||
) $toSave['visible'] = true;
|
||||
else $toSave['visible'] = false;
|
||||
$toSave['visible'] = $data['visible'];
|
||||
|
||||
return $toSave;
|
||||
}
|
||||
|
@@ -7,8 +7,10 @@
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"inertiajs/inertia-laravel": "^0.6.9",
|
||||
"laravel/framework": "^9.19",
|
||||
"laravel/tinker": "^2.7"
|
||||
"laravel/tinker": "^2.7",
|
||||
"spatie/server-side-rendering": "^0.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
|
978
composer.lock
generated
978
composer.lock
generated
File diff suppressed because it is too large
Load Diff
29
database/migrations/2023_06_15_220540_create_cvs_table.php
Normal file
29
database/migrations/2023_06_15_220540_create_cvs_table.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cvs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('token', 50);
|
||||
$table->string('recipient', 255);
|
||||
$table->string('email', 255);
|
||||
$table->string('phone_number', 15);
|
||||
$table->json('locations');
|
||||
$table->integer('views')->nullable()->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cvs');
|
||||
}
|
||||
};
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('cv_infos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('cv_id')->index();
|
||||
$table->string('ip', 255);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('cv_infos');
|
||||
}
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->text('mission')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->dropColumn('mission');
|
||||
});
|
||||
}
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->text('rodo')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->dropColumn('rodo');
|
||||
});
|
||||
}
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->string('position', 255)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->dropColumn('position');
|
||||
});
|
||||
}
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->text('notes')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->dropColumn('notes');
|
||||
});
|
||||
}
|
||||
};
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('message', 500);
|
||||
$table->string('email', 250);
|
||||
$table->string('sender', 50);
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('messages');
|
||||
}
|
||||
};
|
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->boolean('sended')->nullable()->default(false)->after('views');
|
||||
$table->timestamp('sended_timestamp')->nullable()->after('sended');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('cvs', function (Blueprint $table) {
|
||||
$table->dropColumn('sended');
|
||||
$table->dropColumn('sended_timestamp');
|
||||
});
|
||||
}
|
||||
};
|
@@ -1,8 +1,8 @@
|
||||
version: "3.9"
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
image: nginx:1.21-alpine
|
||||
container_name: kamilcraft-api_www
|
||||
working_dir: /application
|
||||
ports:
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
entrypoint: [ 'npm' ]
|
||||
ports:
|
||||
- '3000:3000'
|
||||
- '3001:3001'
|
||||
- '${VITE_PORT:-3001}:${VITE_PORT:-3001}'
|
||||
volumes:
|
||||
- .:/application
|
||||
networks:
|
||||
@@ -68,5 +68,5 @@ networks:
|
||||
|
||||
volumes:
|
||||
mysql-db-data:
|
||||
name: ib-mysql-data
|
||||
name: kamilcraft-api-mysql-data
|
||||
driver: local
|
||||
|
@@ -1,9 +1,9 @@
|
||||
FROM php:8.1-fpm-alpine
|
||||
|
||||
ARG XDEBUG_VERSION=3.1.6
|
||||
ARG XDEBUG_VERSION=3.2.1
|
||||
ARG INSTALL_XDEBUG=false
|
||||
|
||||
ARG COMPOSER_VERSION=2.4.4
|
||||
ARG COMPOSER_VERSION=2.5.8
|
||||
ENV COMPOSER_HOME=/application/.composer
|
||||
ENV COMPOSER_MEMORY_LIMIT=-1
|
||||
|
||||
|
16009
package-lock.json
generated
16009
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -1,21 +1,36 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:ssr": "npm run build && vite build --ssr",
|
||||
"ssr": "npm run build:ssr && node bootstrap/ssr/ssr.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||
"@inertiajs/inertia": "^0.11.1",
|
||||
"@inertiajs/inertia-vue3": "^0.6.0",
|
||||
"@inertiajs/server": "^0.1.0",
|
||||
"@inertiajs/vue3": "^1.0.9",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"@vue/server-renderer": "^3.2.45",
|
||||
"vue": "^3.2.45"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.21",
|
||||
"laravel-mix": "^6.0.6",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"axios": "^1.3.4",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"laravel-vite-plugin": "^0.7.4",
|
||||
"lodash": "^4.17.19",
|
||||
"postcss": "^8.1.14",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.49.7",
|
||||
"sass-loader": "^12.4.0"
|
||||
"postcss": "^8.4.19",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"vite": "^3.0.0",
|
||||
"vite-plugin-iso-import": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
6
postcss.config.js
vendored
Normal file
6
postcss.config.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
2
public/.gitignore
vendored
Normal file
2
public/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
hot
|
||||
build/
|
37
resources/js/Pages/CV/ConfirmDelete.vue
Normal file
37
resources/js/Pages/CV/ConfirmDelete.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
cv: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function confirmDelete() {
|
||||
router.delete(`/dashboard/cv/${props.cv.token}/delete`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Usuwanie CV" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<h1 class="text-3xl font-roboto font-light">Usuwanie CV</h1>
|
||||
</header>
|
||||
<div class="max-w-[600px]">
|
||||
<p class="mb-4">Na pewno usunąć CV dla firmy {{ cv.recipient }}?</p>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/cv"
|
||||
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||
<button
|
||||
@click.prevent="confirmDelete"
|
||||
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="whitespace-nowrap overflow-hidden overflow-ellipsis">Usuń CV dla firmy {{ cv.recipient }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
122
resources/js/Pages/CV/Create.vue
Normal file
122
resources/js/Pages/CV/Create.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const locations = ref([]);
|
||||
const locationsToString = computed({
|
||||
get: () => locations.value.join(', '),
|
||||
set: (val) => {
|
||||
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||
val = val.split(',');
|
||||
val.forEach((element, key) => {
|
||||
val[key] = element.trim();
|
||||
});
|
||||
locations.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
const mission = ref([]);
|
||||
const missionToString = computed({
|
||||
get: () => mission.value.join("\n"),
|
||||
set: (value) => {
|
||||
mission.value = value.split("\n");
|
||||
}
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
recipient: null,
|
||||
email: null,
|
||||
phone_number: null,
|
||||
locations: locations,
|
||||
mission: missionToString,
|
||||
rodo: null,
|
||||
position: null,
|
||||
notes: null,
|
||||
});
|
||||
|
||||
function createCV() {
|
||||
form.post('/dashboard/cv');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Nowe dane do CV" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/cv"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Nowe dane do CV</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="createCV">
|
||||
<Input
|
||||
id="recipient"
|
||||
label="Firma gdzie jest składane CV"
|
||||
placeholder="Oki doki sp. z.o.o"
|
||||
v-model="form.recipient"
|
||||
:error="form.errors.recipient"
|
||||
/>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
label="E-mail"
|
||||
placeholder="Adres e-mail wyświetlany na CV"
|
||||
v-model="form.email"
|
||||
:error="form.errors.email"
|
||||
/>
|
||||
<Input
|
||||
id="phone"
|
||||
label="Numer telefonu"
|
||||
placeholder="+48 123 456 789"
|
||||
v-model="form.phone_number"
|
||||
:error="form.errors.phone_number"
|
||||
/>
|
||||
<Input
|
||||
id="locations"
|
||||
label="Lokalizacje"
|
||||
placeholder="Miejsca pracy."
|
||||
v-model="locationsToString"
|
||||
:error="form.errors.locations"
|
||||
/>
|
||||
<Input
|
||||
id="position"
|
||||
label="Stanowisko (opcjonalne)"
|
||||
placeholder="Stanowisko na jakie jest rekrutacja."
|
||||
v-model="form.position"
|
||||
:error="form.errors.position"
|
||||
/>
|
||||
<Input
|
||||
id="notes"
|
||||
type="textarea"
|
||||
label="Notatki (opcjonalne)"
|
||||
placeholder="Notatka dla administratora"
|
||||
v-model="form.notes"
|
||||
:error="form.errors.notes"
|
||||
/>
|
||||
<Input
|
||||
id="mission"
|
||||
type="textarea"
|
||||
label="Misja - wstęp (opcjonalne)"
|
||||
placeholder="Krótki opis, list motywacyjny."
|
||||
v-model="form.mission"
|
||||
:error="form.errors.mission"
|
||||
/>
|
||||
<Input
|
||||
id="rodo"
|
||||
type="textarea"
|
||||
label="RODO (opcjonalne)"
|
||||
placeholder="Klauzula informacyjna RODO"
|
||||
v-model="form.rodo"
|
||||
:error="form.errors.rodo"
|
||||
/>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Utwórz nowe CV</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
149
resources/js/Pages/CV/Edit.vue
Normal file
149
resources/js/Pages/CV/Edit.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
cv: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const locations = ref(props.cv.locations);
|
||||
const locationsToString = computed({
|
||||
get: () => locations.value.join(', '),
|
||||
set: (val) => {
|
||||
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||
val = val.split(',');
|
||||
val.forEach((element, key) => {
|
||||
val[key] = element.trim();
|
||||
});
|
||||
locations.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
const mission = ref(props.cv.mission);
|
||||
const missionToString = computed({
|
||||
get: () => mission.value.join("\n"),
|
||||
set: (value) => {
|
||||
mission.value = value.split("\n");
|
||||
}
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
recipient: props.cv.recipient,
|
||||
email: props.cv.email,
|
||||
phone_number: props.cv.phone.formattedNumber,
|
||||
locations: locations,
|
||||
mission: missionToString,
|
||||
rodo: props.cv.rodo,
|
||||
position: props.cv.position,
|
||||
notes: props.cv.notes,
|
||||
sended: props.cv.sended.status,
|
||||
});
|
||||
|
||||
function updateCV() {
|
||||
form.put(`/dashboard/cv/${props.cv.token}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Edycja danych do CV" />
|
||||
<div class="p-4">
|
||||
<header class="flex items-center justify-between pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/cv"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Edycja danych do CV</h1>
|
||||
</div>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||
title="Usuń CV"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="updateCV">
|
||||
<Input
|
||||
id="recipient"
|
||||
label="Firma gdzie jest składane CV"
|
||||
placeholder="Oki doki sp. z.o.o"
|
||||
v-model="form.recipient"
|
||||
:error="form.errors.recipient"
|
||||
/>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
label="E-mail"
|
||||
placeholder="Adres e-mail wyświetlany na CV"
|
||||
v-model="form.email"
|
||||
:error="form.errors.email"
|
||||
/>
|
||||
<Input
|
||||
id="phone"
|
||||
label="Numer telefonu"
|
||||
placeholder="+48 123 456 789"
|
||||
v-model="form.phone_number"
|
||||
:error="form.errors.phone_number"
|
||||
/>
|
||||
<Input
|
||||
id="locations"
|
||||
label="Lokalizacje"
|
||||
placeholder="Miejsca pracy."
|
||||
v-model="locationsToString"
|
||||
:error="form.errors.locations"
|
||||
/>
|
||||
<Input
|
||||
id="position"
|
||||
label="Stanowisko"
|
||||
placeholder="Stanowisko na jakie jest rekrutacja."
|
||||
v-model="form.position"
|
||||
:error="form.errors.position"
|
||||
/>
|
||||
<Input
|
||||
id="notes"
|
||||
type="textarea"
|
||||
label="Notatki (opcjonalne)"
|
||||
placeholder="Notatka dla administratora"
|
||||
v-model="form.notes"
|
||||
:error="form.errors.notes"
|
||||
/>
|
||||
<Input
|
||||
id="mission"
|
||||
type="textarea"
|
||||
label="Misja - wstęp"
|
||||
placeholder="Krótki opis, list motywacyjny."
|
||||
v-model="form.mission"
|
||||
:error="form.errors.mission"
|
||||
/>
|
||||
<Input
|
||||
id="rodo"
|
||||
type="textarea"
|
||||
label="RODO"
|
||||
placeholder="Klauzula informacyjna RODO"
|
||||
v-model="form.rodo"
|
||||
:error="form.errors.rodo"
|
||||
/>
|
||||
<Input
|
||||
id="sended"
|
||||
label="Wysłany"
|
||||
type="checkbox"
|
||||
v-model="form.sended"
|
||||
/>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 sm:gap-2 items-center">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/cv/${cv.token}`"
|
||||
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||
<button class="col-span-1 md:col-span-2 px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Edytuj CV</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
93
resources/js/Pages/CV/Index.vue
Normal file
93
resources/js/Pages/CV/Index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script setup>
|
||||
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||
|
||||
defineProps({
|
||||
cvs: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
function copySlug(slug) {
|
||||
const input = document.createElement('input');
|
||||
input.value = slug;
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
navigator
|
||||
.clipboard
|
||||
.writeText(input.value)
|
||||
.then((value, reason) => { });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Lista CV" />
|
||||
<div class="p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Lista CV</h1>
|
||||
</div>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/cv/create"
|
||||
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"
|
||||
title="Dodaj nowe dane dla CV"
|
||||
><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||
</header>
|
||||
<div class="overflow-x-auto">
|
||||
<table v-if="cvs.data.length" class="w-full min-w-[600px] border-separate border-spacing-y-2 cursor-pointer">
|
||||
<colgroup>
|
||||
<col class="w-min" />
|
||||
</colgroup>
|
||||
<thead class="text-left bg-gray-100">
|
||||
<th class="p-2 text-center">ID</th>
|
||||
<th class="p-2">Token</th>
|
||||
<th class="w-[100px] p-2 whitespace-nowrap">Firma</th>
|
||||
<th class="hidden md:table-cell p-2">Lokalizacje</th>
|
||||
<th class="p-2">Telefon</th>
|
||||
<th class="p-2"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<InertiaLink
|
||||
as="tr"
|
||||
v-for="(cv, key) in cvs.data"
|
||||
:key="key"
|
||||
class="px-3 py-2 bg-white hover:bg-neutral-200 rounded-md z-10"
|
||||
:href="`/dashboard/cv/${cv.token}`">
|
||||
<td class="p-2 text-center">#{{ cv.id }}</td>
|
||||
<td class="p-2"
|
||||
><button
|
||||
class="bg-gray-50 p-1 rounded-md hover:bg-gray-100"
|
||||
@click.prevent="copySlug(cv.token)"
|
||||
:title="cv.token">{{ cv.token.slice(0, 7) }}</button></td>
|
||||
<td class="max-w-[150px] md:max-w-[250px] p-2 whitespace-nowrap overflow-hidden overflow-ellipsis" :title="cv.recipient">{{ cv.recipient }}</td>
|
||||
<td class="hidden md:table-cell p-2">{{ cv.locations.join(' / ') }}</td>
|
||||
<td class="p-2">{{ cv.phone.formattedNumber }}</td>
|
||||
<td class="flex items-center justify-center gap-2 p-3 z-50">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-3 py-3 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
||||
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||
title="Edytuj CV"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-3 py-3 text-red-600 hover:text-red-800"
|
||||
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||
title="Usuń CV z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||
</td>
|
||||
</InertiaLink>
|
||||
</tbody>
|
||||
</table>
|
||||
<EmptyState v-else :icon="['fas', 'file']">
|
||||
<template #title>Nie znaleziono</template>
|
||||
<template #text>Nie dodano jeszcze CV do listy.</template>
|
||||
</EmptyState>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
169
resources/js/Pages/CV/Show.vue
Normal file
169
resources/js/Pages/CV/Show.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { router } from '@inertiajs/inertia';
|
||||
|
||||
const props = defineProps({
|
||||
cv: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
cvInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const CV_URL = import.meta.env.VITE_CV_APP_URL;
|
||||
const cvNotes = computed(() => {
|
||||
const notes = props.cv.notes;
|
||||
return notes ? props.cv.notes.split("\n") : null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Szczegóły CV" />
|
||||
<div class="px-3 py-2">
|
||||
<InertiaLink
|
||||
v-if="!cv.sended.status"
|
||||
as="button"
|
||||
method="post"
|
||||
:href="`/dashboard/cv/${cv.token}/sended`"
|
||||
class="w-full px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]"
|
||||
title="Ustaw jako wysłane">Ustaw jako wysłane do odbiorcy.</InertiaLink>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/cv"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Szczegóły CV</h1>
|
||||
</div>
|
||||
<div class="flex gap-3 sm:gap-2">
|
||||
<a
|
||||
class="flex items-center gap-2 px-2 py-1 text-blue-600 hover:text-blue-800"
|
||||
:href="`${CV_URL}/show/${cv.token}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Przekieruj do CV"><FontAwesomeIcon :icon="['fas', 'arrow-up-right-from-square']" /><span class="hidden sm:inline-block">Przejdź do CV</span></a>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-lime-600 hover:text-white hover:bg-lime-600 rounded-md"
|
||||
title="Usuń CV"
|
||||
><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /><span class="hidden sm:inline-block">Edytuj</span></InertiaLink>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||
title="Usuń CV"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="hidden sm:inline-block">Usuń</span></InertiaLink>
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="cv.sended.status" class="max-w-screen-lg my-2 lg:mx-auto px-2 py-3 rounded-md bg-yellow-100 text-yellow-600 text-center">
|
||||
CV jest oznaczone jako wysłane - {{ cv.sended.datetime }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Token</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white whitespace-nowrap overflow-hidden overflow-ellipsis">{{ cv.token }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Firma</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.recipient }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Stanowisko</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.position ?? '[Wartość domyślna]' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">E-mail</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.email }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Numer telefonu</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.phone.formattedNumber }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Lokalizacje</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.locations.join(' / ') }}</p>
|
||||
</div>
|
||||
<div v-if="cvNotes" class="md:col-span-2">
|
||||
<div class="text-gray-500 pb-0.5">Notatki</div>
|
||||
<div class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||
<p
|
||||
v-for="(noteLine, key) in cvNotes"
|
||||
:key="key"
|
||||
>{{ noteLine }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Statystyka</h2>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Utworzono</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.created }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Zmodyfikowano</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.updated }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Zachowane wyświetlenia</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.views }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Łączne wyświetlenia</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.registeredViews }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Opisy</h2>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Misja</div>
|
||||
<div v-if="cv.mission.length > 0 && cv.mission[0] !== ''" class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||
<p
|
||||
v-for="(mission, key) in cv.mission"
|
||||
:key="key">{{ mission }}</p>
|
||||
</div>
|
||||
<p v-else class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">[Wartość domyślna]</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">RODO</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.rodo ?? '[Wartość domyślna]' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Lista wejść</h2>
|
||||
</header>
|
||||
<ul v-if="cvInfo.data.length" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||
<li
|
||||
v-for="(info, key) in cvInfo.data"
|
||||
:key="key"
|
||||
class="flex flex-col px-3 py-2 bg-white rounded-md">
|
||||
<span>#{{ info.id }} {{ info.ip }}</span>
|
||||
<span class="text-xs">{{ info.created_at }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else>
|
||||
Brak wyświetleń
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
37
resources/js/Pages/Categories/ConfirmDelete.vue
Normal file
37
resources/js/Pages/Categories/ConfirmDelete.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
category: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function confirmDelete() {
|
||||
router.delete(`/dashboard/category/${props.category.id}/delete`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Usuwanie kategorii" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<h1 class="text-3xl font-roboto font-light">Usuwanie kategorii</h1>
|
||||
</header>
|
||||
<div class="max-w-[600px]">
|
||||
<p class="mb-4">Na pewno usunąć kategorię o nazwie {{ category.name }}?</p>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||
<button
|
||||
@click.prevent="confirmDelete"
|
||||
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń kategorię {{ category.name }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
71
resources/js/Pages/Categories/Create.vue
Normal file
71
resources/js/Pages/Categories/Create.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const form = useForm({
|
||||
name: null,
|
||||
slug: null,
|
||||
priority: Number(0),
|
||||
default: Boolean(false),
|
||||
visible: Boolean(false),
|
||||
});
|
||||
|
||||
function createCategory() {
|
||||
form.post('/dashboard/category');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Nowa kategoria" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Nowa kategoria</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="createCategory">
|
||||
<Input
|
||||
id="name"
|
||||
label="Nazwa"
|
||||
placeholder="Nazwa kategorii"
|
||||
v-model="form.name"
|
||||
:error="form.errors.name"
|
||||
/>
|
||||
<Input
|
||||
id="slug"
|
||||
label="Slug"
|
||||
placeholder="Slug dla kategorii"
|
||||
v-model="form.slug"
|
||||
:error="form.errors.slug"
|
||||
/>
|
||||
<Input
|
||||
id="priority"
|
||||
label="Priorytet"
|
||||
type="number"
|
||||
placeholder="Priorytet dla danej kategorii"
|
||||
v-model="form.priority"
|
||||
:error="form.errors.priority"
|
||||
/>
|
||||
<Input
|
||||
id="visible"
|
||||
label="Widoczny"
|
||||
type="checkbox"
|
||||
v-model="form.visible"
|
||||
/>
|
||||
<Input
|
||||
id="default"
|
||||
label="Domyślny"
|
||||
type="checkbox"
|
||||
v-model="form.default"
|
||||
/>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Dodaj kategorię</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
91
resources/js/Pages/Categories/Edit.vue
Normal file
91
resources/js/Pages/Categories/Edit.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
category: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const visibleCheckbox = ref(props.category.visible);
|
||||
const defaultCheckbox = ref(props.category.default);
|
||||
|
||||
const form = useForm({
|
||||
name: props.category.name,
|
||||
slug: props.category.slug,
|
||||
priority: props.category.priority,
|
||||
visible: visibleCheckbox,
|
||||
default: defaultCheckbox,
|
||||
});
|
||||
|
||||
function updateProject() {
|
||||
form.clearErrors();
|
||||
form.put(`/dashboard/category/${props.category.id}`);
|
||||
if (defaultCheckbox.value)
|
||||
visibleCheckbox.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Edytuj projekt" />
|
||||
<div class="p-4">
|
||||
<header class="flex items-center justify-between pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Edytuj {{ category.name }}</h1>
|
||||
</div>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/category/${category.id}/delete`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||
title="Usuń kategorię"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="updateProject">
|
||||
<Input
|
||||
id="name"
|
||||
label="Nazwa"
|
||||
placeholder="Nazwa kategorii"
|
||||
v-model="form.name"
|
||||
:error="form.errors.name"
|
||||
/>
|
||||
<Input
|
||||
id="slug"
|
||||
label="Slug"
|
||||
placeholder="Slug dla kategorii"
|
||||
v-model="form.slug"
|
||||
:error="form.errors.slug"
|
||||
/>
|
||||
<Input
|
||||
id="priority"
|
||||
label="Priorytet"
|
||||
type="number"
|
||||
placeholder="Priorytet dla danej kategorii"
|
||||
v-model="form.priority"
|
||||
:error="form.errors.priority"
|
||||
/>
|
||||
<Input
|
||||
id="visible"
|
||||
label="Widoczny"
|
||||
type="checkbox"
|
||||
v-model="form.visible"
|
||||
/>
|
||||
<Input
|
||||
id="default"
|
||||
label="Domyślny"
|
||||
type="checkbox"
|
||||
v-model="form.default"
|
||||
/>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Aktualizuj kategorię</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
28
resources/js/Pages/Dashboard/Index.vue
Normal file
28
resources/js/Pages/Dashboard/Index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
import ProjectsList from '../../Share/ProjectsList.vue';
|
||||
import CategoriesList from '../../Share/CategoriesList.vue';
|
||||
|
||||
defineProps({
|
||||
categories: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Dashboard" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<h1 class="text-3xl font-roboto font-light">Dashboard</h1>
|
||||
</header>
|
||||
<div class="grid md:grid-cols-3 gap-3">
|
||||
<ProjectsList class="md:col-span-2" :projects="projects" />
|
||||
<CategoriesList class="col-span-1" :categories="categories" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
45
resources/js/Pages/Login.vue
Normal file
45
resources/js/Pages/Login.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import GuestLayout from '../Share/Layout/Guest.vue';
|
||||
|
||||
defineOptions({ layout: GuestLayout });
|
||||
|
||||
const form = useForm({
|
||||
'email': null,
|
||||
'password': null,
|
||||
});
|
||||
|
||||
function login() {
|
||||
form.post('/dashboard/login');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Logowanie" />
|
||||
<div class="max-w-screen-sm mx-auto p-4 bg-neutral-100 rounded-md border border-gray-200 shadow">
|
||||
<h1 class="pb-4 text-3xl font-robot font-light">Logowanie</h1>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="form.post('/dashboard/login')">
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<label for="email"
|
||||
class="text-gray-500">E-mail</label>
|
||||
<input id="email"
|
||||
:class="['w-full px-2.5 py-2 border-b-2 rounded-md', form.errors.email ? 'border-red-300 focus:border-red-400 hover:border-red-500 outline-none text-red-900 placeholder-red-400' : 'border-neutral-300 focus:border-neutral-400 hover:border-neutral-500 outline-none text-gray-900 placeholder-gray-400']"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
placeholder="Podaj swój e-mail" />
|
||||
<span class="text-red-400" v-if="form.errors.email">{{ form.errors.email }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label for="password"
|
||||
class="text-gray-500">Hasło</label>
|
||||
<input
|
||||
id="password"
|
||||
:class="['w-full px-2.5 py-2 border-b-2 rounded-md', form.errors.email ? 'border-red-300 focus:border-red-400 hover:border-red-500 outline-none text-red-900 placeholder-red-400' : 'border-neutral-300 focus:border-neutral-400 hover:border-neutral-500 outline-none text-gray-900 placeholder-gray-400']"
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
placeholder="Podaj swoje hasło" />
|
||||
</div>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Zaloguj</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
37
resources/js/Pages/Messages/ConfirmDelete.vue
Normal file
37
resources/js/Pages/Messages/ConfirmDelete.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function confirmDelete() {
|
||||
router.delete(`/dashboard/message/${props.message.id}/delete`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Usuwanie wiadomości" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<h1 class="text-3xl font-roboto font-light">Usuwanie wiadomości</h1>
|
||||
</header>
|
||||
<div class="max-w-[600px]">
|
||||
<p class="mb-4">Na pewno usunąć wiadomość od {{ message.sender }}?</p>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/message/${message.id}`"
|
||||
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||
<button
|
||||
@click.prevent="confirmDelete"
|
||||
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="whitespace-nowrap overflow-hidden overflow-ellipsis">Usuń wiadomość od {{ message.sender }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
65
resources/js/Pages/Messages/Index.vue
Normal file
65
resources/js/Pages/Messages/Index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||
|
||||
defineProps({
|
||||
messages: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Wiadomości" />
|
||||
<div class="p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Wiadomości</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="overflow-x-auto">
|
||||
<table v-if="messages.data.length" class="table-fixed w-full min-w-[600px] border-separate border-spacing-y-2 cursor-pointer">
|
||||
<colgroup>
|
||||
<col class="w-[40px] max-w-[60px]" />
|
||||
<col class="w-[250px]" />
|
||||
<col class="w-auto" />
|
||||
<col class="w-[50px]" />
|
||||
</colgroup>
|
||||
<thead class="text-left bg-gray-100">
|
||||
<th class="w-[40px] max-w-[60px] p-2 text-center">ID</th>
|
||||
<th class="w-[250px] p-2">Wysyłający</th>
|
||||
<th class="p-2">E-mail</th>
|
||||
<th class="w-[50px] p-2"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<InertiaLink
|
||||
as="tr"
|
||||
v-for="(message, key) in messages.data"
|
||||
:key="key"
|
||||
class="px-3 py-2 bg-white hover:bg-neutral-200 rounded-md z-10"
|
||||
:href="`/dashboard/message/${message.id}`">
|
||||
<td class="p-2 w-[60px] text-center">#{{ message.id }}</td>
|
||||
<td class="p-2 whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message.sender }}</td>
|
||||
<td class="p-2">{{ message.email }}</td>
|
||||
<td class="flex items-center justify-end gap-2 p-3 z-50">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-3 py-3 text-red-600 hover:text-red-800"
|
||||
:href="`/dashboard/message/${message.id}/delete`"
|
||||
title="Usuń wiadomość z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||
</td>
|
||||
</InertiaLink>
|
||||
</tbody>
|
||||
</table>
|
||||
<EmptyState v-else :icon="['fas', 'message']">
|
||||
<template #title>Brak wiadomości</template>
|
||||
<template #text>Nie przesłano jeszcze żadnej wiadomości.</template>
|
||||
</EmptyState>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
71
resources/js/Pages/Messages/Show.vue
Normal file
71
resources/js/Pages/Messages/Show.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const splitMessage = computed(() => props.message.message.split("\n"));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Szczegóły wiadomości" />
|
||||
<div class="p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/message"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróć do listy wiadomości"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Szczegóły wiadomości</h1>
|
||||
</div>
|
||||
<div class="flex gap-3 sm:gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/message/${message.id}/delete`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||
title="Usuń wiadomość"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="hidden sm:inline-block">Usuń</span></InertiaLink>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mb-4">
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">ID</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message.id }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">Nadawca</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ message.sender }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500 pb-0.5">E-mail</div>
|
||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ message.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<header>
|
||||
<h2 class="text-2xl font-roboto font-light pb-3">Treść wiadomości</h2>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="col-span-1 sm:col-span-2">
|
||||
<div class="text-gray-500 pb-0.5">Wiadomość</div>
|
||||
<div class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||
<p
|
||||
v-for="(messageLine, key) in splitMessage"
|
||||
:key="key"
|
||||
>{{ messageLine }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
37
resources/js/Pages/Projects/ConfirmDelete.vue
Normal file
37
resources/js/Pages/Projects/ConfirmDelete.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function confirmDelete() {
|
||||
router.delete(`/dashboard/project/${props.project.id}/delete`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Nowy projekt" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<h1 class="text-3xl font-roboto font-light">Usuwanie projektu</h1>
|
||||
</header>
|
||||
<div class="max-w-[600px]">
|
||||
<p class="mb-4">Na pewno usunąć projekt o nazwie {{ project.title }}?</p>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||
<button
|
||||
@click.prevent="confirmDelete"
|
||||
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń projekt {{ project.title }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
160
resources/js/Pages/Projects/Create.vue
Normal file
160
resources/js/Pages/Projects/Create.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const categories = ref([]);
|
||||
|
||||
const categoryToString = computed({
|
||||
get: () => categories.value.join(', '),
|
||||
set: (val) => {
|
||||
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||
val = val.split(',');
|
||||
val.forEach((element, key) => {
|
||||
element = element.trim();
|
||||
val[key] = slug(element);
|
||||
});
|
||||
categories.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
title: null,
|
||||
author: null,
|
||||
categories: categoryToString,
|
||||
release_date: null,
|
||||
update_date: null,
|
||||
image_small: null,
|
||||
image_medium: null,
|
||||
image_large: null,
|
||||
project_url: null,
|
||||
project_version: null,
|
||||
description: null,
|
||||
visible: Boolean(false),
|
||||
});
|
||||
|
||||
function createProject() {
|
||||
form.post('/dashboard/project');
|
||||
}
|
||||
|
||||
function slug(str) {
|
||||
str = str.replace(/^\s+|\s+$/g, ''); // trim
|
||||
str = str.toLowerCase();
|
||||
|
||||
// remove accents, swap ñ for n, etc
|
||||
var from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;";
|
||||
var to = "aaaaaeeeeeiiiiooooouuuunc------";
|
||||
for (var i = 0, l = from.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
|
||||
}
|
||||
|
||||
str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
|
||||
.replace(/\s+/g, '-') // collapse whitespace and replace by -
|
||||
.replace(/-+/g, '-'); // collapse dashes
|
||||
|
||||
return str;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Nowy projekt" />
|
||||
<div class="p-4">
|
||||
<header class="pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Nowy projekt</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="createProject">
|
||||
<Input
|
||||
id="title"
|
||||
label="Tytuł"
|
||||
placeholder="Nazwa projektu"
|
||||
v-model="form.title"
|
||||
:error="form.errors.title"
|
||||
/>
|
||||
<Input
|
||||
id="author"
|
||||
label="Autor"
|
||||
placeholder="Imię i nazwisko"
|
||||
v-model="form.author"
|
||||
:error="form.errors.author"
|
||||
/>
|
||||
<Input
|
||||
id="categories"
|
||||
label="Kategorie"
|
||||
placeholder="Kategorie projektu"
|
||||
v-model="form.categories"
|
||||
:error="form.errors.categories"
|
||||
/>
|
||||
<Input
|
||||
id="release_date"
|
||||
label="Data pierwszego wydania"
|
||||
type="date"
|
||||
v-model="form.release_date"
|
||||
:error="form.errors.release_date"
|
||||
/>
|
||||
<Input
|
||||
id="update_date"
|
||||
label="Data aktualizacji"
|
||||
type="date"
|
||||
v-model="form.update_date"
|
||||
:error="form.errors.update_date"
|
||||
/>
|
||||
<Input
|
||||
id="image_small"
|
||||
label="Zdjęcie projekty - małe"
|
||||
v-model="form.image_small"
|
||||
:error="form.errors.image_small"
|
||||
/>
|
||||
<Input
|
||||
id="image_medium"
|
||||
label="Zdjęcie projekty - średnie"
|
||||
v-model="form.image_medium"
|
||||
:error="form.errors.image_medium"
|
||||
/>
|
||||
<Input
|
||||
id="image_large"
|
||||
label="Zdjęcie projekty - duże"
|
||||
v-model="form.image_large"
|
||||
:error="form.errors.image_large"
|
||||
/>
|
||||
<Input
|
||||
id="project_url"
|
||||
label="Adres projektu"
|
||||
placeholder="Adres www strony projektu"
|
||||
v-model="form.project_url"
|
||||
:error="form.errors.project_url"
|
||||
/>
|
||||
<Input
|
||||
id="project_version"
|
||||
label="Wersja projektu"
|
||||
placeholder="v1.0.0"
|
||||
v-model="form.project_version"
|
||||
:error="form.errors.project_version"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Opis"
|
||||
type="textarea"
|
||||
placeholder="Ładny opis"
|
||||
v-model="form.description"
|
||||
:error="form.errors.description"
|
||||
textareaHeight="200px"
|
||||
/>
|
||||
<Input
|
||||
id="visible"
|
||||
label="Widoczny"
|
||||
type="checkbox"
|
||||
v-model="form.visible"
|
||||
/>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Dodaj projekt</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
173
resources/js/Pages/Projects/Edit.vue
Normal file
173
resources/js/Pages/Projects/Edit.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import Input from '../../Share/Components/Input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const categories = ref(props.project?.categories ?? []);
|
||||
|
||||
const categoryToString = computed({
|
||||
get: () => categories.value.join(', '),
|
||||
set: (val) => {
|
||||
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||
val = val.split(',');
|
||||
val.forEach((element, key) => {
|
||||
element = element.trim();
|
||||
val[key] = slug(element);
|
||||
});
|
||||
categories.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
title: props.project.title,
|
||||
author: props.project.author,
|
||||
categories: categoryToString,
|
||||
release_date: props.project.release_date,
|
||||
update_date: props.project.update_date,
|
||||
image_small: props.project?.images['small'] ?? '',
|
||||
image_medium: props.project?.images['medium'] ?? '',
|
||||
image_large: props.project?.images['large'] ?? '',
|
||||
project_url: props.project.project_url,
|
||||
project_version: props.project.project_version,
|
||||
description: props.project.description,
|
||||
visible: Boolean(props.project.visible),
|
||||
});
|
||||
|
||||
function updateProject() {
|
||||
form.put(`/dashboard/project/${props.project.id}`);
|
||||
}
|
||||
|
||||
function slug(str) {
|
||||
str = str.replace(/^\s+|\s+$/g, ''); // trim
|
||||
str = str.toLowerCase();
|
||||
|
||||
// remove accents, swap ñ for n, etc
|
||||
var from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;";
|
||||
var to = "aaaaaeeeeeiiiiooooouuuunc------";
|
||||
for (var i = 0, l = from.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
|
||||
}
|
||||
|
||||
str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
|
||||
.replace(/\s+/g, '-') // collapse whitespace and replace by -
|
||||
.replace(/-+/g, '-'); // collapse dashes
|
||||
|
||||
return str;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InertiaHead title="Nowy projekt" />
|
||||
<div class="p-4">
|
||||
<header class="flex items-center justify-between pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard"
|
||||
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||
<h1 class="text-3xl font-roboto font-light">Edytuj {{ project.title }}</h1>
|
||||
</div>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
:href="`/dashboard/project/${project.id}/delete`"
|
||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||
title="Usuń projekt"
|
||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
||||
</header>
|
||||
<div>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="updateProject">
|
||||
<Input
|
||||
id="title"
|
||||
label="Tytuł"
|
||||
placeholder="Nazwa projektu"
|
||||
v-model="form.title"
|
||||
:error="form.errors.title"
|
||||
/>
|
||||
<Input
|
||||
id="author"
|
||||
label="Autor"
|
||||
placeholder="Imię i nazwisko"
|
||||
v-model="form.author"
|
||||
:error="form.errors.author"
|
||||
/>
|
||||
<Input
|
||||
id="categories"
|
||||
label="Kategorie"
|
||||
placeholder="Kategorie projektu"
|
||||
v-model="form.categories"
|
||||
:error="form.errors.categories"
|
||||
/>
|
||||
<Input
|
||||
id="release_date"
|
||||
label="Data pierwszego wydania"
|
||||
type="date"
|
||||
v-model="form.release_date"
|
||||
:error="form.errors.release_date"
|
||||
/>
|
||||
<Input
|
||||
id="update_date"
|
||||
label="Data aktualizacji"
|
||||
type="date"
|
||||
v-model="form.update_date"
|
||||
:error="form.errors.update_date"
|
||||
/>
|
||||
<Input
|
||||
id="image_small"
|
||||
label="Zdjęcie projekty - małe"
|
||||
v-model="form.image_small"
|
||||
:error="form.errors.image_small"
|
||||
/>
|
||||
<Input
|
||||
id="image_medium"
|
||||
label="Zdjęcie projekty - średnie"
|
||||
v-model="form.image_medium"
|
||||
:error="form.errors.image_medium"
|
||||
/>
|
||||
<Input
|
||||
id="image_large"
|
||||
label="Zdjęcie projekty - duże"
|
||||
v-model="form.image_large"
|
||||
:error="form.errors.image_large"
|
||||
/>
|
||||
<Input
|
||||
id="project_url"
|
||||
label="Adres projektu"
|
||||
placeholder="Adres www strony projektu"
|
||||
v-model="form.project_url"
|
||||
:error="form.errors.project_url"
|
||||
/>
|
||||
<Input
|
||||
id="project_version"
|
||||
label="Wersja projektu"
|
||||
placeholder="v1.0.0"
|
||||
v-model="form.project_version"
|
||||
:error="form.errors.project_version"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Opis"
|
||||
type="textarea"
|
||||
placeholder="Ładny opis"
|
||||
v-model="form.description"
|
||||
:error="form.errors.description"
|
||||
textareaHeight="200px"
|
||||
/>
|
||||
<Input
|
||||
id="visible"
|
||||
label="Widoczny"
|
||||
type="checkbox"
|
||||
v-model="form.visible"
|
||||
/>
|
||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Aktualizuj projekt</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
48
resources/js/Share/CategoriesList.vue
Normal file
48
resources/js/Share/CategoriesList.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||
|
||||
defineProps({
|
||||
categories: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-gray-100 rounded-md p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<h2 class="text-2xl font-roboto font-light">Kategorie</h2>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/category/create"
|
||||
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||
</header>
|
||||
<ul v-if="categories.length" class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="(category, key) in categories"
|
||||
:key="key"
|
||||
class="flex items-center justify-between px-3 py-2 bg-white hover:bg-neutral-200"
|
||||
>
|
||||
<InertiaLink :href="`/dashboard/category/${category.id}`">{{ category.name }}</InertiaLink>
|
||||
<div class="flex items-center gap-1">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-1 py-0.5 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
||||
:href="`/dashboard/category/${category.id}`"
|
||||
title="Edytuj kategorię"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
||||
<InertiaLink
|
||||
v-if="!category.default"
|
||||
as="button"
|
||||
class="px-1 py-0.5 text-red-600 hover:text-red-800"
|
||||
:href="`/dashboard/category/${category.id}/delete`"
|
||||
title="Usuń kategorię z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<EmptyState v-else :icon="['fas', 'list']">
|
||||
<template #title>Brak kategorii</template>
|
||||
<template #text>Nie dodano jeszcze żadnej kategorii.</template>
|
||||
</EmptyState>
|
||||
</section>
|
||||
</template>
|
31
resources/js/Share/Components/EmptyState.vue
Normal file
31
resources/js/Share/Components/EmptyState.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
defaut: ['far', 'folder-open'],
|
||||
},
|
||||
showDescription: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-center my-5 text-gray-500">
|
||||
<slot name="head">
|
||||
<FontAwesomeIcon :icon="icon" class="mx-auto w-12 h-12" />
|
||||
</slot>
|
||||
<h3 class="mt-2 text-sm font-medium">
|
||||
<slot name="title">Brak danych</slot>
|
||||
</h3>
|
||||
<p
|
||||
v-if="showDescription"
|
||||
class="text-sm"
|
||||
>
|
||||
<slot name="text">
|
||||
Nie znaleziono danych.
|
||||
</slot>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
53
resources/js/Share/Components/Input.vue
Normal file
53
resources/js/Share/Components/Input.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
modelValue: [String, Boolean, Number],
|
||||
id: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
label: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
textareaHeight: {
|
||||
type: String,
|
||||
default: null,
|
||||
}
|
||||
});
|
||||
defineEmits(['update:model-value']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['flex w-full', (type === 'checkbox' ? 'flex-row gap-2 items-center bg-gray-200 px-2 py-2 rounded-md' : 'flex-col gap-1')]">
|
||||
<label v-if="id"
|
||||
:for="id"
|
||||
class="text-gray-500">{{ label }}</label>
|
||||
<textarea v-if="type === 'textarea'"
|
||||
:id="id"
|
||||
:class="['w-full min-w-full max-w-full h-[200px] min-h-[200px] px-2.5 py-2 border-b-2 rounded-md', error ? 'border-red-300 focus:border-red-400 hover:border-red-500 outline-none text-red-900 placeholder-red-400' : 'border-neutral-300 focus:border-neutral-400 hover:border-neutral-500 outline-none text-gray-900 placeholder-gray-400']"
|
||||
:value="modelValue"
|
||||
@input="$emit('update:model-value', $event.target.value)"
|
||||
:placeholder="placeholder"></textarea>
|
||||
<input v-else-if="type === 'checkbox'"
|
||||
:id="id"
|
||||
:checked="modelValue"
|
||||
@input="$emit('update:model-value', $event.target.checked)"
|
||||
:true-value="true"
|
||||
:false-value="false"
|
||||
type="checkbox" />
|
||||
<input v-else
|
||||
:id="id"
|
||||
:class="['w-full px-2.5 py-2 border-b-2 rounded-md', error ? 'border-red-300 focus:border-red-400 hover:border-red-500 outline-none text-red-900 placeholder-red-400' : 'border-neutral-300 focus:border-neutral-400 hover:border-neutral-500 outline-none text-gray-900 placeholder-gray-400']"
|
||||
:type="type"
|
||||
:value="modelValue"
|
||||
@input="$emit('update:model-value', $event.target.value)"
|
||||
:placeholder="placeholder" />
|
||||
<span class="text-red-400" v-if="error">{{ error }}</span>
|
||||
</div>
|
||||
</template>
|
49
resources/js/Share/Layout/App.vue
Normal file
49
resources/js/Share/Layout/App.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
messages: {
|
||||
type: Object,
|
||||
default: {},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.header {
|
||||
background: linear-gradient(237.74deg,#1199a5,#436da7 83%);
|
||||
}
|
||||
.logo-green {
|
||||
color: rgb(162, 207, 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header class="header">
|
||||
<div class="flex gap-5 items-center justify-start max-w-screen-lg mx-auto font-thasadith">
|
||||
<InertiaLink href="/dashboard">
|
||||
<div
|
||||
class="bg-white text-gray-800 pl-10 pr-5 py-2.5 text-4xl"
|
||||
>Kamil<span class="logo-green">Craft</span></div>
|
||||
</InertiaLink>
|
||||
<nav>
|
||||
<ul class="flex gap-3 items-center font-bold">
|
||||
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/cv">CV</InertiaLink></li>
|
||||
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/message">Msg</InertiaLink></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="messages?.info" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-yellow-100 text-yellow-600 text-center">
|
||||
{{ messages.info }}
|
||||
</div>
|
||||
<div v-if="messages?.error" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-red-100 text-red-600 text-center">
|
||||
{{ messages.error }}
|
||||
</div>
|
||||
<div v-if="messages?.success" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-green-100 text-green-600 text-center">
|
||||
{{ messages?.success }}
|
||||
</div>
|
||||
<main class="max-w-screen-lg mx-2 lg:mx-auto mt-2 rounded-md bg-gray-50">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
30
resources/js/Share/Layout/Guest.vue
Normal file
30
resources/js/Share/Layout/Guest.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.header {
|
||||
@apply min-h-[220px];
|
||||
background: linear-gradient(237.74deg,#1199a5,#436da7 83%);
|
||||
}
|
||||
.logo-green {
|
||||
color: rgb(162, 207, 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header class="header">
|
||||
<div class="flex gap-5 items-center justify-between md:justify-start max-w-screen-xl mx-auto font-thasadith">
|
||||
<InertiaLink href="/dashboard">
|
||||
<div
|
||||
class="bg-white text-gray-800 pl-10 pr-5 py-2.5 text-4xl"
|
||||
>Kamil<span class="logo-green">Craft</span></div>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
</header>
|
||||
<main class="-my-24 px-2 sm:px-0">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
48
resources/js/Share/ProjectsList.vue
Normal file
48
resources/js/Share/ProjectsList.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||
|
||||
defineProps({
|
||||
projects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-gray-100 rounded-md p-4">
|
||||
<header class="flex justify-between items-center pb-4">
|
||||
<h2 class="text-2xl font-roboto font-light">Projekty</h2>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
href="/dashboard/project/create"
|
||||
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"
|
||||
><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||
</header>
|
||||
<ul v-if="projects.length" class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="(project, key) in projects"
|
||||
:key="key"
|
||||
class="flex items-center justify-between px-3 py-2 bg-white hover:bg-neutral-200"
|
||||
>
|
||||
<InertiaLink :href="`/dashboard/project/${project.id}`">{{ project.title }}</InertiaLink>
|
||||
<div class="flex items-center gap-2">
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-2 py-1 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
||||
:href="`/dashboard/project/${project.id}`"
|
||||
title="Edytuj projekt"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
class="px-2 py-1 text-red-600 hover:text-red-800"
|
||||
:href="`/dashboard/project/${project.id}/delete`"
|
||||
title="Usuń projekt z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<EmptyState v-else :icon="['fas', 'bars-progress']">
|
||||
<template #title>Brak projektów</template>
|
||||
<template #text>Nie dodano jeszcze żadnego projektu.</template>
|
||||
</EmptyState>
|
||||
</section>
|
||||
</template>
|
44
resources/js/app.js
vendored
44
resources/js/app.js
vendored
@@ -1 +1,43 @@
|
||||
require('./bootstrap');
|
||||
import './bootstrap';
|
||||
|
||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||
|
||||
import { createApp, h } from 'vue';
|
||||
import { createInertiaApp, Head, Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import { far } from '@fortawesome/free-regular-svg-icons';
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
library.add(fas);
|
||||
library.add(far);
|
||||
library.add(fab);
|
||||
|
||||
import './css/app.css';
|
||||
import App from '@/Share/Layout/App.vue';
|
||||
|
||||
createInertiaApp({
|
||||
resolve: async (name) => {
|
||||
const page = resolvePageComponent(
|
||||
`./Pages/${name}.vue`,
|
||||
import.meta.glob('./Pages/**/*.vue'),
|
||||
);
|
||||
|
||||
page.then((module) => {
|
||||
module.default.layout = module.default.layout || App
|
||||
});
|
||||
|
||||
return page;
|
||||
},
|
||||
setup({el, App, props, plugin}) {
|
||||
createApp({render: () => h(App, props)})
|
||||
.use(plugin)
|
||||
.component('InertiaLink', Link)
|
||||
.component('InertiaHead', Head)
|
||||
.component('FontAwesomeIcon', FontAwesomeIcon)
|
||||
.mount(el)
|
||||
},
|
||||
title: title => `${title} | KamilCraft API`
|
||||
});
|
||||
|
29
resources/js/bootstrap.js
vendored
29
resources/js/bootstrap.js
vendored
@@ -1,28 +1,7 @@
|
||||
window._ = require('lodash');
|
||||
import _ from 'lodash';
|
||||
window._ = _;
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
window.axios = require('axios');
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// window.Pusher = require('pusher-js');
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: process.env.MIX_PUSHER_APP_KEY,
|
||||
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
|
||||
// forceTLS: true
|
||||
// });
|
||||
|
9
resources/js/css/app.css
vendored
Normal file
9
resources/js/css/app.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--color-logo-green: 162, 207, 0;
|
||||
}
|
||||
}
|
31
resources/js/ssr.js
vendored
Normal file
31
resources/js/ssr.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createSSRApp, h } from 'vue';
|
||||
import { renderToString } from '@vue/server-renderer';
|
||||
import createServer from '@inertiajs/server'
|
||||
import { createInertiaApp, Head } from '@inertiajs/inertia-vue3';
|
||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||
import App from '@/Share/Layout/App.vue';
|
||||
import '../css/app.css';
|
||||
|
||||
createServer(page =>
|
||||
createInertiaApp({
|
||||
page,
|
||||
render: renderToString,
|
||||
resolve: async (name) => {
|
||||
const page = resolvePageComponent(
|
||||
`./Pages/${name}.vue`,
|
||||
import.meta.glob('./Pages/**/*.vue'),
|
||||
);
|
||||
|
||||
page.then((module) => {
|
||||
module.default.layout = module.default.layout || App
|
||||
});
|
||||
|
||||
return page;
|
||||
},
|
||||
setup({ app, props, plugin }) {
|
||||
return createSSRApp({
|
||||
render: () => h(app, props)
|
||||
}).use(plugin).component('InertiaHead', Head);
|
||||
},
|
||||
})
|
||||
);
|
14
resources/views/app.blade.php
Normal file
14
resources/views/app.blade.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Thasadith&display=swap" rel="stylesheet">
|
||||
@vite('resources/js/app.js')
|
||||
@inertiaHead
|
||||
</head>
|
||||
<body>
|
||||
@inertia
|
||||
</body>
|
||||
</html>
|
@@ -1,132 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Laravel</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.dark\:text-gray-500{--tw-text-opacity:1;color:#6b7280;color:rgba(107,114,128,var(--tw-text-opacity))}}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="antialiased">
|
||||
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
|
||||
@if (Route::has('login'))
|
||||
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
|
||||
@auth
|
||||
<a href="{{ url('/home') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Home</a>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Log in</a>
|
||||
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-gray-500 underline">Register</a>
|
||||
@endif
|
||||
@endauth
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
|
||||
<svg viewBox="0 0 651 192" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-16 w-auto text-gray-700 sm:h-20">
|
||||
<g clip-path="url(#clip0)" fill="#EF3B2D">
|
||||
<path d="M248.032 44.676h-16.466v100.23h47.394v-14.748h-30.928V44.676zM337.091 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.431 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162-.001 2.863-.479 5.584-1.432 8.161zM463.954 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.432 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162 0 2.863-.479 5.584-1.432 8.161zM650.772 44.676h-15.606v100.23h15.606V44.676zM365.013 144.906h15.607V93.538h26.776V78.182h-42.383v66.724zM542.133 78.182l-19.616 51.096-19.616-51.096h-15.808l25.617 66.724h19.614l25.617-66.724h-15.808zM591.98 76.466c-19.112 0-34.239 15.706-34.239 35.079 0 21.416 14.641 35.079 36.239 35.079 12.088 0 19.806-4.622 29.234-14.688l-10.544-8.158c-.006.008-7.958 10.449-19.832 10.449-13.802 0-19.612-11.127-19.612-16.884h51.777c2.72-22.043-11.772-40.877-33.023-40.877zm-18.713 29.28c.12-1.284 1.917-16.884 18.589-16.884 16.671 0 18.697 15.598 18.813 16.884h-37.402zM184.068 43.892c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002-35.648-20.524a2.971 2.971 0 00-2.964 0l-35.647 20.522-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v38.979l-29.706 17.103V24.493a3 3 0 00-.103-.776c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002L40.098 1.396a2.971 2.971 0 00-2.964 0L1.487 21.919l-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v122.09c0 1.063.568 2.044 1.489 2.575l71.293 41.045c.156.089.324.143.49.202.078.028.15.074.23.095a2.98 2.98 0 001.524 0c.069-.018.132-.059.2-.083.176-.061.354-.119.519-.214l71.293-41.045a2.971 2.971 0 001.489-2.575v-38.979l34.158-19.666a2.971 2.971 0 001.489-2.575V44.666a3.075 3.075 0 00-.106-.774zM74.255 143.167l-29.648-16.779 31.136-17.926.001-.001 34.164-19.669 29.674 17.084-21.772 12.428-43.555 24.863zm68.329-76.259v33.841l-12.475-7.182-17.231-9.92V49.806l12.475 7.182 17.231 9.92zm2.97-39.335l29.693 17.095-29.693 17.095-29.693-17.095 29.693-17.095zM54.06 114.089l-12.475 7.182V46.733l17.231-9.92 12.475-7.182v74.537l-17.231 9.921zM38.614 7.398l29.693 17.095-29.693 17.095L8.921 24.493 38.614 7.398zM5.938 29.632l12.475 7.182 17.231 9.92v79.676l.001.005-.001.006c0 .114.032.221.045.333.017.146.021.294.059.434l.002.007c.032.117.094.222.14.334.051.124.088.255.156.371a.036.036 0 00.004.009c.061.105.149.191.222.288.081.105.149.22.244.314l.008.01c.084.083.19.142.284.215.106.083.202.178.32.247l.013.005.011.008 34.139 19.321v34.175L5.939 144.867V29.632h-.001zm136.646 115.235l-65.352 37.625V148.31l48.399-27.628 16.953-9.677v33.862zm35.646-61.22l-29.706 17.102V66.908l17.231-9.92 12.475-7.182v33.841z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center">
|
||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
|
||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
||||
Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-t-0 md:border-l">
|
||||
<div class="flex items-center">
|
||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laracasts.com" class="underline text-gray-900 dark:text-white">Laracasts</a></div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center">
|
||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"></path></svg>
|
||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel-news.com/" class="underline text-gray-900 dark:text-white">Laravel News</a></div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
||||
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-l">
|
||||
<div class="flex items-center">
|
||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<div class="ml-4 text-lg leading-7 font-semibold text-gray-900 dark:text-white">Vibrant Ecosystem</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-12">
|
||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
||||
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="underline">Forge</a>, <a href="https://vapor.laravel.com" class="underline">Vapor</a>, <a href="https://nova.laravel.com" class="underline">Nova</a>, and <a href="https://envoyer.io" class="underline">Envoyer</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="underline">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="underline">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="underline">Echo</a>, <a href="https://laravel.com/docs/horizon" class="underline">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="underline">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="underline">Telescope</a>, and more.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-4 sm:items-center sm:justify-between">
|
||||
<div class="text-center text-sm text-gray-500 sm:text-left">
|
||||
<div class="flex items-center">
|
||||
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="-mt-px w-5 h-5 text-gray-400">
|
||||
<path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
||||
</svg>
|
||||
|
||||
<a href="https://laravel.bigcartel.com" class="ml-1 underline">
|
||||
Shop
|
||||
</a>
|
||||
|
||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="ml-4 -mt-px w-5 h-5 text-gray-400">
|
||||
<path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path>
|
||||
</svg>
|
||||
|
||||
<a href="https://github.com/sponsors/taylorotwell" class="ml-1 underline">
|
||||
Sponsor
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
|
||||
Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -17,3 +17,7 @@ Route::get('projects', 'ProjectController@index');
|
||||
Route::prefix('project')->group(function() {
|
||||
Route::get('{project}', 'ProjectController@show');
|
||||
});
|
||||
|
||||
Route::get('cv/{cv}', 'CVController@show');
|
||||
|
||||
Route::post('message', 'MessageController@store');
|
||||
|
@@ -5,9 +5,41 @@ declare(strict_types=1);
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::name('admin.')->group(function () {
|
||||
Route::namespace('Dashboard')->middleware('auth')->group(function () {
|
||||
Route::namespace('Dashboard')->middleware('auth')->group(function (): void {
|
||||
Route::get('', 'AdminPanelController')->name('home');
|
||||
Route::name('category.')->prefix('category')->group(function () {
|
||||
Route::name('message.')->prefix('message')->group(function (): void {
|
||||
Route::get('', 'MessageController@index')
|
||||
->name('index');
|
||||
Route::get('{message}', 'MessageController@show')
|
||||
->name('show');
|
||||
Route::get('{message}/delete', 'MessageController@delete')
|
||||
->name('delete');
|
||||
Route::delete('{message}/delete', 'MessageController@destroy')
|
||||
->name('destroy');
|
||||
});
|
||||
Route::name('cv.')->prefix('cv')->group(function (): void {
|
||||
Route::get('', 'CVController@index')
|
||||
->name('index');
|
||||
Route::get('create', 'CVController@create')
|
||||
->name('create');
|
||||
Route::post('', 'CVController@store')
|
||||
->name('store');
|
||||
Route::get('{cv}', 'CVController@show')
|
||||
->name('show');
|
||||
Route::post('{cv}/sended', 'CVController@updateSendStatus')
|
||||
->name('sended');
|
||||
Route::post('', 'CVController@store')
|
||||
->name('store');
|
||||
Route::get('{cv}/edit', 'CVController@edit')
|
||||
->name('edit');
|
||||
Route::put('{cv}', 'CVController@update')
|
||||
->name('update');
|
||||
Route::get('{cv}/delete', 'CVController@delete')
|
||||
->name('delete');
|
||||
Route::delete('{cv}/delete', 'CVController@destroy')
|
||||
->name('destroy');
|
||||
});
|
||||
Route::name('category.')->prefix('category')->group(function (): void {
|
||||
Route::get('create', 'CategoryController@create')
|
||||
->name('create');
|
||||
Route::post('', 'CategoryController@store')
|
||||
@@ -24,7 +56,7 @@ Route::name('admin.')->group(function () {
|
||||
->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('project.')->prefix('project')->group(function () {
|
||||
Route::name('project.')->prefix('project')->group(function (): void {
|
||||
Route::get('create', 'ProjectController@create')
|
||||
->name('create');
|
||||
Route::post('', 'ProjectController@store')
|
||||
@@ -42,7 +74,7 @@ Route::name('admin.')->group(function () {
|
||||
});
|
||||
});
|
||||
|
||||
Route::name('auth.')->namespace('Auth')->group(function () {
|
||||
Route::name('auth.')->namespace('Auth')->group(function (): void {
|
||||
Route::get('login', 'LoginController@login')
|
||||
->name('login');
|
||||
Route::post('login', 'LoginController@authenticate')
|
||||
|
22
tailwind.config.js
vendored
Normal file
22
tailwind.config.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./public/index.html",
|
||||
"./resources/**/*.{vue,js}",
|
||||
"./resources/views/**/*.blade.php"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'arial': ['Arial', 'sans-serif'],
|
||||
'roboto': ['Roboto', 'sans-serif'],
|
||||
'thasadith': ['Thasadith', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
'logo-green': 'rgb(var(--color-logo-green) / <alpha-value>)',
|
||||
'kamilcraft-green': 'rgb(var(--color-logo-green) / <alpha-value>)',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
43
vite.config.js
vendored
Normal file
43
vite.config.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import { networkInterfaces } from 'os';
|
||||
|
||||
export default defineConfig((mode) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
return {
|
||||
server: {
|
||||
host: Object.values(networkInterfaces()).flat().find(i => i.family === 'IPv4' && !i.internal).address,
|
||||
port: parseInt(env.VITE_PORT ?? 3001),
|
||||
hmr: {
|
||||
host: 'localhost',
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/resources/js',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
transformAssetUrls: {
|
||||
base: null,
|
||||
includeAbsolute: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
laravel({
|
||||
input: 'resources/js/app.js',
|
||||
ssr: 'resources/js/ssr.js',
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
ssr: {
|
||||
noExternal: [
|
||||
'@inertiajs/server',
|
||||
'@vue/runtime-dom'
|
||||
],
|
||||
},
|
||||
}
|
||||
});
|
6
webpack.mix.js
vendored
6
webpack.mix.js
vendored
@@ -1,6 +0,0 @@
|
||||
const mix = require('laravel-mix');
|
||||
|
||||
mix.js('resources/js/app.js', 'public/js');
|
||||
|
||||
mix.sass('resources/sass/app.scss', 'public/css');
|
||||
mix.sass('resources/sass/dashboard.scss', 'public/css');
|
Reference in New Issue
Block a user