feat: implement intern authentication and secure document dashboard for internship applications
This commit is contained in:
@@ -6,7 +6,9 @@ use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class CareerApplicationForm
|
||||
{
|
||||
@@ -34,7 +36,8 @@ class CareerApplicationForm
|
||||
->disk('public')
|
||||
->directory('cvs')
|
||||
->required()
|
||||
->disabled(),
|
||||
->disabled()
|
||||
->downloadable(),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('career.message'))
|
||||
@@ -50,6 +53,32 @@ class CareerApplicationForm
|
||||
'accepted' => 'Accepted',
|
||||
])
|
||||
->required(),
|
||||
|
||||
Section::make('Stajyer Giriş Bilgileri & Belgeleri')
|
||||
->description('Stajyerin sisteme giriş yapabilmesi için kullanıcı adı, şifre belirleyin ve imzalı staj formunu yükleyin.')
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->label('Kullanıcı Adı')
|
||||
->unique(ignoreRecord: true)
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('password')
|
||||
->label('Şifre')
|
||||
->password()
|
||||
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın')
|
||||
->nullable(),
|
||||
|
||||
FileUpload::make('signed_internship_form_path')
|
||||
->label('İmzalı Staj Formu')
|
||||
->disk('public')
|
||||
->directory('signed_interns')
|
||||
->downloadable()
|
||||
->nullable(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,12 @@ class CareerApplicationsTable
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
->url(fn ($record) => Storage::disk('public')->url($record->cv_path))
|
||||
->openUrlInNewTab(),
|
||||
Action::make('download_signed_form')
|
||||
->label('İmzalı Form İndir')
|
||||
->icon('heroicon-o-document-check')
|
||||
->url(fn ($record) => $record->signed_internship_form_path ? Storage::disk('public')->url($record->signed_internship_form_path) : null)
|
||||
->visible(fn ($record) => !empty($record->signed_internship_form_path))
|
||||
->openUrlInNewTab(),
|
||||
Action::make('download_nda')
|
||||
->label(__('career.nda'))
|
||||
->icon('heroicon-o-shield-check')
|
||||
|
||||
@@ -74,4 +74,63 @@ class CareerController extends Controller
|
||||
|
||||
return redirect()->back()->with('success', __('career.success_message'));
|
||||
}
|
||||
|
||||
public function internLoginForm()
|
||||
{
|
||||
if (session()->has('intern_id')) {
|
||||
return redirect()->route('intern.dashboard');
|
||||
}
|
||||
return view('front.career.intern_login', [
|
||||
'meta' => [
|
||||
'title' => 'Stajyer Girişi',
|
||||
'description' => 'İmzalı staj belgelerinize erişmek için lütfen giriş yapın.',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function internLogin(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$intern = CareerApplication::where('username', $request->username)
|
||||
->where('type', 'internship')
|
||||
->first();
|
||||
|
||||
if (!$intern || !\Illuminate\Support\Facades\Hash::check($request->password, $intern->password)) {
|
||||
return redirect()->back()
|
||||
->withInput($request->only('username'))
|
||||
->withErrors([
|
||||
'username' => 'Girdiğiniz kullanıcı adı veya şifre hatalı.',
|
||||
]);
|
||||
}
|
||||
|
||||
session(['intern_id' => $intern->id]);
|
||||
|
||||
return redirect()->route('intern.dashboard')->with('success', 'Başarıyla giriş yapıldı.');
|
||||
}
|
||||
|
||||
public function internDashboard()
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen giriş yapın.');
|
||||
}
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
return view('front.career.intern_dashboard', [
|
||||
'intern' => $intern,
|
||||
'meta' => [
|
||||
'title' => 'Stajyer Paneli',
|
||||
'description' => 'Belgelerinizi buradan indirebilirsiniz.',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function internLogout()
|
||||
{
|
||||
session()->forget('intern_id');
|
||||
return redirect()->route('intern.login')->with('success', 'Başarıyla çıkış yapıldı.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,20 @@ class CareerApplication extends Model
|
||||
'ai_usage',
|
||||
'message',
|
||||
'status',
|
||||
'username',
|
||||
'password',
|
||||
'signed_internship_form_path',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('username')->nullable()->unique()->after('status');
|
||||
$table->string('password')->nullable()->after('username');
|
||||
$table->string('signed_internship_form_path')->nullable()->after('password');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn(['username', 'password', 'signed_internship_form_path']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
@extends('layouts.site')
|
||||
|
||||
@section('content')
|
||||
<section class="wrapper bg-[#f0f7ff] py-12">
|
||||
<div class="container px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
|
||||
<!-- Top header / Welcome card -->
|
||||
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50 flex flex-col md:flex-row justify-between items-start md:items-center gap-6 mb-8">
|
||||
<div>
|
||||
<span class="text-xs uppercase font-extrabold tracking-widest text-[#e31e24] bg-red-50 px-3 py-1.5 rounded-full">Stajyer Paneli</span>
|
||||
<h1 class="text-3xl font-extrabold text-slate-900 tracking-tight mt-3">Hoş Geldiniz, {{ $intern->name }}</h1>
|
||||
<p class="text-sm text-slate-500 mt-1">Staj belgelerinizi ve başvuru detaylarınızı buradan yönetebilirsiniz.</p>
|
||||
</div>
|
||||
<form action="{{ route('intern.logout') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="px-5 py-3 rounded-xl bg-slate-100 hover:bg-red-50 text-slate-600 hover:text-[#e31e24] font-bold text-sm transition-all flex items-center gap-2 border border-slate-200/50 hover:border-red-100">
|
||||
<i class="uil uil-sign-out-alt text-lg"></i>
|
||||
<span>Çıkış Yap</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Application Info & Document Download Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
||||
|
||||
<!-- Sidebar stats / statuses -->
|
||||
<div class="md:col-span-4 space-y-6">
|
||||
<div class="bg-white rounded-3xl p-6 shadow-xl border border-slate-100/50">
|
||||
<h3 class="font-bold text-slate-800 text-lg mb-4 pb-2 border-b border-slate-100">Başvuru Detayları</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<span class="text-xs text-slate-400 font-semibold block">E-posta</span>
|
||||
<span class="text-sm font-semibold text-slate-700 block mt-0.5">{{ $intern->email }}</span>
|
||||
</div>
|
||||
|
||||
@if($intern->phone)
|
||||
<div>
|
||||
<span class="text-xs text-slate-400 font-semibold block">Telefon</span>
|
||||
<span class="text-sm font-semibold text-slate-700 block mt-0.5">{{ $intern->phone }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<span class="text-xs text-slate-400 font-semibold block">Staj Durumu</span>
|
||||
<span class="inline-flex mt-1.5 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider
|
||||
@if($intern->status === 'accepted') bg-green-50 text-green-700 border border-green-200
|
||||
@elseif($intern->status === 'rejected') bg-red-50 text-red-700 border border-red-200
|
||||
@elseif($intern->status === 'reviewed') bg-blue-50 text-blue-700 border border-blue-200
|
||||
@else bg-slate-50 text-slate-500 border border-slate-200 @endif">
|
||||
@if($intern->status === 'accepted') Kabul Edildi
|
||||
@elseif($intern->status === 'rejected') Reddedildi
|
||||
@elseif($intern->status === 'reviewed') İncelendi
|
||||
@else Beklemede @endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document download cards -->
|
||||
<div class="md:col-span-8 space-y-6">
|
||||
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
|
||||
<h3 class="font-bold text-slate-800 text-lg mb-6 pb-2 border-b border-slate-100">Belgeleriniz</h3>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- CV Card -->
|
||||
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 hover:border-slate-200 transition-all">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-50 text-[#e31e24] flex items-center justify-center text-2xl flex-shrink-0">
|
||||
<i class="uil uil-file-alt"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-slate-800 text-base">Özgeçmiş (CV)</h4>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Başvuru sırasında yüklemiş olduğunuz özgeçmiş belgeniz.</p>
|
||||
</div>
|
||||
</div>
|
||||
@if($intern->cv_path)
|
||||
<a href="{{ Storage::disk('public')->url($intern->cv_path) }}" target="_blank" class="w-full sm:w-auto px-5 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-sm transition-all flex items-center justify-center gap-2 shadow-md">
|
||||
<i class="uil uil-arrow-down-tray"></i>
|
||||
<span>İndir</span>
|
||||
</a>
|
||||
@else
|
||||
<span class="text-xs text-slate-400 italic">Yüklenmemiş</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Signed Internship Form Card -->
|
||||
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 hover:border-slate-200 transition-all">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl @if($intern->signed_internship_form_path) bg-green-50 text-green-600 @else bg-amber-50 text-amber-600 @endif flex items-center justify-center text-2xl flex-shrink-0">
|
||||
<i class="uil uil-file-check-alt"></i>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<h4 class="font-bold text-slate-800 text-base">İmzalı Staj Formu</h4>
|
||||
@if($intern->signed_internship_form_path)
|
||||
<p class="text-xs text-slate-400 mt-0.5">Şirketimiz tarafından imzalanmış ve onaylanmış staj belgeniz.</p>
|
||||
@else
|
||||
<p class="text-xs text-amber-600/90 font-medium mt-0.5">Belgeniz imza aşamasındadır. Yöneticilerimiz tarafından onaylandığında buradan indirebilirsiniz.</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($intern->signed_internship_form_path)
|
||||
<a href="{{ Storage::disk('public')->url($intern->signed_internship_form_path) }}" target="_blank" class="w-full sm:w-auto px-5 py-2.5 bg-[#e31e24] hover:bg-[#c4191f] text-white font-bold rounded-xl text-sm transition-all flex items-center justify-center gap-2 shadow-md shadow-red-500/10">
|
||||
<i class="uil uil-arrow-down-tray"></i>
|
||||
<span>İndir</span>
|
||||
</a>
|
||||
@else
|
||||
<button disabled class="w-full sm:w-auto px-5 py-2.5 bg-slate-100 text-slate-400 font-bold rounded-xl text-sm cursor-not-allowed flex items-center justify-center gap-2 border border-slate-200/50">
|
||||
<i class="uil uil-hourglass"></i>
|
||||
<span>Bekleniyor</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@push('styles')
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
.shadow-xl {
|
||||
box-shadow: 0 1rem 3rem rgba(30, 41, 59, 0.04) !important;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@endsection
|
||||
@@ -0,0 +1,87 @@
|
||||
@extends('layouts.site')
|
||||
|
||||
@section('content')
|
||||
<section class="wrapper bg-[#f0f7ff] min-h-[70vh] flex items-center py-12">
|
||||
<div class="container px-4">
|
||||
<div class="max-w-md mx-auto bg-white/80 backdrop-blur-md rounded-3xl shadow-2xl border border-slate-100/50 overflow-hidden">
|
||||
<div class="p-8 md:p-10">
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex w-16 h-16 rounded-full bg-red-50 text-[#e31e24] items-center justify-center mb-4">
|
||||
<i class="uil uil-lock-alt text-3xl"></i>
|
||||
</div>
|
||||
<h1 class="text-3xl font-extrabold text-slate-900 tracking-tight">Stajyer Girişi</h1>
|
||||
<p class="text-sm text-slate-500 mt-2">İmzalı belgelerinize erişmek için kullanıcı adı ve şifrenizle giriş yapın.</p>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="bg-red-50 border-l-4 border-[#e31e24] p-4 rounded-xl mb-6">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="uil uil-exclamation-triangle text-[#e31e24] text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-red-700 font-medium">
|
||||
{{ $errors->first() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-50 border-l-4 border-green-500 p-4 rounded-xl mb-6">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="uil uil-check-circle text-green-500 text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-green-700 font-medium">
|
||||
{{ session('success') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('intern.login.submit') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="username" class="text-sm font-bold text-slate-700 block">Kullanıcı Adı</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 text-slate-400">
|
||||
<i class="uil uil-user"></i>
|
||||
</span>
|
||||
<input type="text" name="username" id="username" class="w-full pl-11 pr-5 py-3.5 rounded-xl border border-slate-200 focus:ring-4 focus:ring-red-500/10 focus:border-[#e31e24] outline-none transition-all placeholder-slate-300" placeholder="Kullanıcı adınızı girin" value="{{ old('username') }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="password" class="text-sm font-bold text-slate-700 block">Şifre</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 text-slate-400">
|
||||
<i class="uil uil-key-skeleton-alt"></i>
|
||||
</span>
|
||||
<input type="password" name="password" id="password" class="w-full pl-11 pr-5 py-3.5 rounded-xl border border-slate-200 focus:ring-4 focus:ring-red-500/10 focus:border-[#e31e24] outline-none transition-all placeholder-slate-300" placeholder="Şifrenizi girin" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full py-4 bg-[#e31e24] hover:bg-[#c4191f] text-white font-bold rounded-xl transition-all shadow-lg shadow-red-500/20 flex items-center justify-center gap-2 mt-4 hover:-translate-y-0.5">
|
||||
<span>Giriş Yap</span>
|
||||
<i class="uil uil-arrow-right text-xl"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@push('styles')
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
.shadow-2xl {
|
||||
box-shadow: 0 1.5rem 4rem rgba(30, 41, 59, 0.08) !important;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@endsection
|
||||
@@ -115,6 +115,12 @@ Route::get('/kariyer', [\App\Http\Controllers\CareerController::class, 'index'])
|
||||
Route::get('/staj-basvurusu', [\App\Http\Controllers\CareerController::class, 'internship'])->name('career.internship');
|
||||
Route::post('/kariyer', [\App\Http\Controllers\CareerController::class, 'store'])->name('career.store');
|
||||
|
||||
// Intern Portal
|
||||
Route::get('/stajyer/giris', [\App\Http\Controllers\CareerController::class, 'internLoginForm'])->name('intern.login');
|
||||
Route::post('/stajyer/giris', [\App\Http\Controllers\CareerController::class, 'internLogin'])->name('intern.login.submit');
|
||||
Route::get('/stajyer/panel', [\App\Http\Controllers\CareerController::class, 'internDashboard'])->name('intern.dashboard');
|
||||
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
|
||||
|
||||
// Payment Process
|
||||
Route::post('/payment/process', [\App\Http\Controllers\PaymentController::class, 'process'])->name('payment.process');
|
||||
Route::any('/payment/success', [\App\Http\Controllers\PaymentController::class, 'success'])->name('payment.success');
|
||||
|
||||
Reference in New Issue
Block a user