feat: implement intern authentication and secure document dashboard for internship applications

This commit is contained in:
Ümit Tunç
2026-05-26 17:21:30 +03:00
parent 910e912b05
commit a4bc64527c
8 changed files with 370 additions and 1 deletions
@@ -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')
+59
View File
@@ -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ı.');
}
}
+15
View File
@@ -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',
];
}
}
@@ -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
+6
View File
@@ -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');