Compare commits

...

2 Commits

19 changed files with 987 additions and 120 deletions

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\InternshipResource\Pages;
use App\Filament\Resources\InternshipResource\RelationManagers;
use App\Models\Internship;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
class InternshipResource extends Resource
{
protected static ?string $model = Internship::class;
protected static ?string $navigationGroup = 'Internships';
protected static ?string $navigationIcon = 'heroicon-o-academic-cap';
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('title')
->required()
->maxLength(255),
TextInput::make('location')
->required()
->maxLength(255),
Textarea::make('title_description')
->label('Description (show on modal)')
->required()
->maxLength(65535)
->columnSpan('full'),
TextInput::make('salary_per_month')
->required()
->numeric()
->label('Salary per month')
->maxLength(255),
Forms\Components\Select::make('salary_currency')
->options(getCurrencies())
->required()
->label('Salary currency')
->searchable()
->default('USD'),
Repeater::make('bullets')
->schema([
TextInput::make('bullet')
->required()
->maxLength(255),
])
->minItems(1)
->defaultItems(1)
->columnSpan('full'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title')
->searchable(),
Tables\Columns\TextColumn::make('title_description')
->searchable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('salary_per_month')
->searchable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('salary_currency')
->searchable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('location')
->searchable(),
Tables\Columns\TextColumn::make('salary')
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
RelationManagers\ApplicationsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListInternships::route('/'),
'create' => Pages\CreateInternship::route('/create'),
'edit' => Pages\EditInternship::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\InternshipResource\Pages;
use App\Filament\Resources\InternshipResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateInternship extends CreateRecord
{
protected static string $resource = InternshipResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\InternshipResource\Pages;
use App\Filament\Resources\InternshipResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditInternship extends EditRecord
{
protected static string $resource = InternshipResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\InternshipResource\Pages;
use App\Filament\Resources\InternshipResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListInternships extends ListRecords
{
protected static string $resource = InternshipResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Filament\Resources\InternshipResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use App\Models\InternshipApplication; // Added this use statement
class ApplicationsRelationManager extends RelationManager
{
protected static string $relationship = 'applications';
// Add this line to define the inverse relationship
protected static ?string $model = InternshipApplication::class;
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\DatePicker::make('birthdate')
->required(),
Forms\Components\FileUpload::make('resume_file')
->required()
->acceptedFileTypes(['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])
->disk('public') // or your preferred disk
->directory('resumes'),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\TextInput::make('phone_number')
->required()
->maxLength(20),
Forms\Components\Textarea::make('cover_letter')
->maxLength(65535)
->nullable()
->columnSpan('full'),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('email')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('phone_number')
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}

View File

@@ -6,7 +6,7 @@ use App\Filament\Resources\NewsResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListNews extends ListRecords
class ManageNews extends ListRecords
{
protected static string $resource = NewsResource::class;

View File

@@ -4,17 +4,44 @@ namespace App\Http\Controllers;
use App\Models\Internship;
use Illuminate\Http\Request;
use App\Models\InternshipApplication; // Changed from App\Models\Application
class InternshipsPageController extends Controller
{
public function index()
{
return view('web.pages.internships.index');
$internships = Internship::query()->get();
return view('web.pages.internships.index', compact('internships'));
}
public function store(Request $request)
{
dd($request->all());
$validatedData = $request->validate([
'internship_id' => 'required|exists:internships,id',
'name' => 'required|string|max:255',
'birthdate' => 'required|date',
'resume_file' => 'required|file|mimes:pdf,doc,docx|max:2048',
'email' => 'required|email|max:255',
'phone_number' => 'required|string',
'cover_letter' => 'nullable|string',
]);
$resumePath = $request->file('resume_file')->store('resumes');
InternshipApplication::create([ // Changed to InternshipApplication::create
'internship_id' => $validatedData['internship_id'],
'name' => $validatedData['name'],
'birthdate' => $validatedData['birthdate'],
'resume_file' => $resumePath,
'email' => $validatedData['email'],
'phone_number' => $validatedData['phone_number'],
'cover_letter' => $validatedData['cover_letter'] ?? null,
]);
return response()->json([
'message' => 'Your application has been submitted successfully!',
]);
}
public function show(Internship $internship)

View File

@@ -3,8 +3,26 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Models\InternshipApplication;
class Internship extends Model
{
//
protected $fillable = [
'title',
'title_description',
'salary_per_month',
'bullets',
'location',
'salary_currency',
];
protected $casts = [
'bullets' => 'array',
];
public function applications(): HasMany
{
return $this->hasMany(InternshipApplication::class);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InternshipApplication extends Model
{
protected $fillable = [
'internship_id',
'name',
'birthdate',
'resume_file',
'email',
'phone_number',
'cover_letter',
];
public function internship(): BelongsTo
{
return $this->belongsTo(Internship::class);
}
}

View File

@@ -3,33 +3,158 @@
return [
/*
|--------------------------------------------------------------------------
| Livewire Temporary File Uploads
|--------------------------------------------------------------------------
|---------------------------------------------------------------------------
| Class Namespace
|---------------------------------------------------------------------------
|
| By default, Livewire will upload temporary files to the local disk
| and clear them after a maximum of 5 minutes. You may customize
| this process by changing the "disk" and "max_upload_time" properties.
| This value sets the root class namespace for Livewire component classes in
| your application. This value will change where component auto-discovery
| finds components. It's also referenced by the file creation commands.
|
*/
'class_namespace' => 'App\\Livewire',
/*
|---------------------------------------------------------------------------
| View Path
|---------------------------------------------------------------------------
|
| This value is used to specify where Livewire component Blade templates are
| stored when running file creation commands like `artisan make:livewire`.
| It is also used if you choose to omit a component's render() method.
|
*/
'view_path' => resource_path('views/livewire'),
/*
|---------------------------------------------------------------------------
| Layout
|---------------------------------------------------------------------------
| The view that will be used as the layout when rendering a single component
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
| In this case, the view returned by CreatePost will render into $slot.
|
*/
'layout' => 'components.layouts.app',
/*
|---------------------------------------------------------------------------
| Lazy Loading Placeholder
|---------------------------------------------------------------------------
| Livewire allows you to lazy load components that would otherwise slow down
| the initial page load. Every component can have a custom placeholder or
| you can define the default placeholder view for all components below.
|
*/
'lazy_placeholder' => null,
/*
|---------------------------------------------------------------------------
| Temporary File Uploads
|---------------------------------------------------------------------------
|
| Livewire handles file uploads by storing uploads in a temporary directory
| before the file is stored permanently. All file uploads are directed to
| a global endpoint for temporary storage. You may configure this below:
|
*/
'temporary_file_upload' => [
'disk' => 'public', // This is the default disk for temporary uploads.
'rules' => null, // Set this to null to use default upload rules (e.g., file size, mime types).
'middleware' => null, // Set this to null to use default middleware for temporary uploads.
'max_upload_time' => 30, // The maximum number of minutes a temporary file can be stored.
'disk' => null, // Example: 'local', 's3' | Default: 'default'
'rules' => ['required', 'file', 'max:100000'], // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 20, // Max duration (in minutes) before an upload is invalidated...
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
],
/*
|--------------------------------------------------------------------------
| Livewire App URL
|--------------------------------------------------------------------------
|---------------------------------------------------------------------------
| Render On Redirect
|---------------------------------------------------------------------------
|
| This is the app URL that Livewire will use to make requests to.
| If you're using a custom domain, you may need to change this.
| This value determines if Livewire will run a component's `render()` method
| after a redirect has been triggered using something like `redirect(...)`
| Setting this to true will render the view once more before redirecting
|
*/
'app_url' => env('APP_URL'),
'render_on_redirect' => false,
];
/*
|---------------------------------------------------------------------------
| Eloquent Model Binding
|---------------------------------------------------------------------------
|
| Previous versions of Livewire supported binding directly to eloquent model
| properties using wire:model by default. However, this behavior has been
| deemed too "magical" and has therefore been put under a feature flag.
|
*/
'legacy_model_binding' => false,
/*
|---------------------------------------------------------------------------
| Auto-inject Frontend Assets
|---------------------------------------------------------------------------
|
| By default, Livewire automatically injects its JavaScript and CSS into the
| <head> and <body> of pages containing Livewire components. By disabling
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
*/
'inject_assets' => true,
/*
|---------------------------------------------------------------------------
| Navigate (SPA mode)
|---------------------------------------------------------------------------
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
| will prevent the default link handling and instead request those pages
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
*/
'navigate' => [
'show_progress_bar' => true,
'progress_bar_color' => '#2299dd',
],
/*
|---------------------------------------------------------------------------
| HTML Morph Markers
|---------------------------------------------------------------------------
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
| after each update. To make this process more reliable, Livewire injects
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
*/
'inject_morph_markers' => true,
/*
|---------------------------------------------------------------------------
| Pagination Theme
|---------------------------------------------------------------------------
|
| When enabling Livewire's pagination feature by using the `WithPagination`
| trait, Livewire will use Tailwind templates to render pagination views
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
*/
'pagination_theme' => 'tailwind',
];

View File

@@ -0,0 +1,28 @@
<?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('applications', function (Blueprint $table) {
$table->foreignId('internship_id')->nullable()->constrained()->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropConstrainedForeignId('internship_id');
});
}
};

View File

@@ -0,0 +1,40 @@
<?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('internships', function (Blueprint $table) {
$table->string('title');
$table->text('title_description');
$table->string('salary_per_month');
$table->json('bullets');
$table->string('location');
$table->string('salary_currency');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('internships', function (Blueprint $table) {
$table->dropColumn([
'title',
'title_description',
'salary_per_month',
'bullets',
'location',
'salary_currency',
]);
});
}
};

View File

@@ -0,0 +1,34 @@
<?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::create('internship_applications', function (Blueprint $table) {
$table->id();
$table->foreignId('internship_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->date('birthdate');
$table->string('resume_file');
$table->string('email');
$table->string('phone_number');
$table->text('cover_letter')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('internship_applications');
}
};

View File

@@ -0,0 +1,29 @@
<?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('applications', function (Blueprint $table) {
$table->dropForeign(['internship_id']);
$table->dropColumn('internship_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->foreignId('internship_id')->nullable()->constrained()->onDelete('set null');
});
}
};

View File

@@ -0,0 +1,122 @@
<div class="modal fade" id="applyInternshipModal" tabindex="-1" aria-labelledby="applyInternshipModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3">
<div class="modal-header">
<h5 class="modal-title" id="applicationModalLabel">Apply for <span id="jobTitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6 id="jobLocation"></h6>
<p id="jobSalary"></p>
<p id="jobDescription"></p>
<form id="applicationForm" enctype="multipart/form-data" action="{{ $jobType === 'internship' ? route('internship.store') : route('career.store') }}" method="POST">
@csrf
@if($jobType === 'internship')
<input type="hidden" name="internship_id" id="jobIdInput" value="{{ $jobId }}">
@else
<input type="hidden" name="career_id" id="jobIdInput" value="{{ $jobId }}">
@endif
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Name <span> *</span></label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-6 mb-3">
<label for="birthdate" class="form-label">Birthdate <span> *</span></label>
<input type="date" class="form-control" id="birthdate" name="birthdate" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email <span> *</span></label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="col-md-6 mb-3">
<label for="phone_number" class="form-label">Phone Number <span> *</span></label>
<input type="text" class="form-control" id="phone_number" name="phone_number" required>
</div>
</div>
<div class="mb-3">
<label for="resume_file" class="form-label">Resume (PDF, DOC, DOCX) <span> *</span></label>
<input type="file" class="form-control" id="resume_file" name="resume_file" accept=".pdf,.doc,.docx" required>
</div>
<div class="mb-3">
<label for="cover_letter" class="form-label">Cover Letter (Optional)</label>
<textarea class="form-control" id="cover_letter" name="cover_letter" rows="5"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit Application</button>
</form>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
var applicationModal = document.getElementById('applyInternshipModal'); // Changed ID here
if (applicationModal) { // Add a check for modal existence
applicationModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget; // Button that triggered the modal
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
var modalTitle = applicationModal.querySelector('#jobTitle');
var modalLocation = applicationModal.querySelector('#jobLocation');
var modalSalary = applicationModal.querySelector('#jobSalary');
var modalDescription = applicationModal.querySelector('#jobDescription');
var jobIdInput = applicationModal.querySelector('#jobIdInput');
var applicationForm = applicationModal.querySelector('#applicationForm');
// Set dynamic values
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
jobIdInput.value = jobId;
// Set form action dynamically
if (jobType === 'internship') {
applicationForm.action = '{{ route('internship.store') }}';
jobIdInput.name = 'internship_id';
} else if (jobType === 'career') {
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
jobIdInput.name = 'career_id';
}
});
// Handle form submission with AJAX
document.getElementById('applicationForm').addEventListener('submit', function (e) {
e.preventDefault();
let form = e.target;
let formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
var modal = bootstrap.Modal.getInstance(applicationModal);
modal.hide();
form.reset();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
});
});
}
});
</script>
@endpush

View File

@@ -47,7 +47,7 @@
</ul>
</div>
<div class="price__area-item-btn">
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applicationModal" data-career-title="{{ $career->title }}" data-career-location="{{ $career->location }}" data-career-salary="{{ $career->salary_per_month }}" data-career-description="{{ $career->title_description }}" data-career-id="{{ $career->id }}">Apply Now<i class="flaticon-right-up"></i></button>
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applyInternshipModal" data-job-id="{{ $career->id }}" data-job-type="career" data-career-title="{{ $career->title }}" data-career-location="{{ $career->location }}" data-career-salary="{{ $career->salary_per_month }}" data-career-description="{{ $career->title_description }}">Apply Now<i class="flaticon-right-up"></i></button>
</div>
</div>
@empty
@@ -59,114 +59,77 @@
</div>
<!-- Pricing Plan Area End -->
<!-- Application Modal -->
<div class="modal fade" id="applicationModal" tabindex="-1" aria-labelledby="applicationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3">
<div class="modal-header">
<h5 class="modal-title" id="applicationModalLabel">Apply for <span id="jobTitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6 id="jobLocation"></h6>
<p id="jobSalary"></p>
<p id="jobDescription"></p>
<form id="applicationForm" enctype="multipart/form-data" action="{{ route('applications.store') }}" method="POST">
@csrf
<input type="hidden" name="career_id" id="careerId">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Name <span> *</span></label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-6 mb-3">
<label for="birthdate" class="form-label">Birthdate <span> *</span></label>
<input type="date" class="form-control" id="birthdate" name="birthdate" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email <span> *</span></label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="col-md-6 mb-3">
<label for="phone_number" class="form-label">Phone Number <span> *</span></label>
<input type="text" class="form-control" id="phone_number" name="phone_number" required>
</div>
</div>
<div class="mb-3">
<label for="resume_file" class="form-label">Resume (PDF, DOC, DOCX) <span> *</span></label>
<input type="file" class="form-control" id="resume_file" name="resume_file" accept=".pdf,.doc,.docx" required>
</div>
<div class="mb-3">
<label for="cover_letter" class="form-label">Cover Letter (Optional)</label>
<textarea class="form-control" id="cover_letter" name="cover_letter" rows="5"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit Application</button>
</form>
</div>
</div>
</div>
</div>
@endsection
{{-- Application Modal --}}
@include('web.components.application_modal', ['jobId' => null, 'jobType' => 'career']) {{-- jobId will be set by JS from button data attributes --}}
@push('footer-js')
<script>
document.addEventListener('DOMContentLoaded', function () {
var applicationModal = document.getElementById('applicationModal');
applicationModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var careerTitle = button.getAttribute('data-career-title');
var careerLocation = button.getAttribute('data-career-location');
var careerSalary = button.getAttribute('data-career-salary');
var careerDescription = button.getAttribute('data-career-description');
var careerId = button.getAttribute('data-career-id');
var applicationModal = document.getElementById('applyInternshipModal');
if (applicationModal) {
applicationModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget; // Button that triggered the modal
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
var modalTitle = applicationModal.querySelector('#jobTitle');
var modalLocation = applicationModal.querySelector('#jobLocation');
var modalSalary = applicationModal.querySelector('#jobSalary');
var modalDescription = applicationModal.querySelector('#jobDescription');
var modalCareerId = applicationModal.querySelector('#careerId');
var modalTitle = applicationModal.querySelector('#jobTitle');
var modalLocation = applicationModal.querySelector('#jobLocation');
var modalSalary = applicationModal.querySelector('#jobSalary');
var modalDescription = applicationModal.querySelector('#jobDescription');
var jobIdInput = applicationModal.querySelector('#jobIdInput');
var applicationForm = applicationModal.querySelector('#applicationForm');
modalTitle.textContent = careerTitle;
modalLocation.textContent = 'Location: ' + careerLocation;
modalSalary.textContent = 'Salary: ' + careerSalary + ' / Per monthly';
modalDescription.textContent = careerDescription;
modalCareerId.value = careerId;
});
// Set dynamic values
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
jobIdInput.value = jobId;
// Handle form submission with AJAX
document.getElementById('applicationForm').addEventListener('submit', function (e) {
e.preventDefault();
let form = e.target;
let formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
// Set form action dynamically
if (jobType === 'internship') {
applicationForm.action = '{{ route('internship.store') }}';
jobIdInput.name = 'internship_id';
} else if (jobType === 'career') {
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
jobIdInput.name = 'career_id';
}
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
var modal = bootstrap.Modal.getInstance(applicationModal);
modal.hide();
form.reset();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
});
});
// Handle form submission with AJAX
document.getElementById('applicationForm').addEventListener('submit', function (e) {
e.preventDefault();
let form = e.target;
let formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
var modal = bootstrap.Modal.getInstance(applicationModal);
modal.hide();
form.reset();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
});
});
}
});
</script>
@endpush

View File

@@ -0,0 +1,32 @@
@extends('web.layouts.app')
@section('title', $career->title)
@section('content')
<!-- Main Content for Single Career Page -->
<div class="container">
<h1>{{ $career->title }}</h1>
<p><strong>{{ __('Location:') }}</strong> {{ $career->location }}</p>
<p><strong>{{ __('Salary:') }}</strong> {{ $career->salary_per_month }} {{ $career->salary_currency }}</p>
<h2>{{ __('Description') }}</h2>
<p>{{ $career->title_description }}</p>
@if ($career->bullets)
<h3>{{ __('Responsibilities and Qualifications') }}</h3>
<ul>
@foreach ($career->bullets as $bullet)
<li>{{ $bullet['bullet'] }}</li>
@endforeach
</ul>
@endif
<!-- Apply Button - Triggers Modal -->
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#applyInternshipModal">
{{ __('Apply for this Career') }}
</button>
</div>
<!-- Application Modal -->
@include('web.components.application_modal', ['jobId' => $career->id, 'jobType' => 'career'])
@endsection

View File

@@ -0,0 +1,135 @@
@extends('web.layouts.app')
@push('header-css')
<style>
#applicationForm > label > span {
color: var(--gujurly-primary);
}
</style>
@endpush
@section('content')
<!-- Breadcrumb Area Start -->
<div class="breadcrumb__area" style="background-image: url('/web/assets/img/page/breadcrumb.jpg');">
<div class="container">
<div class="row">
<div class="col-xl-12">
<div class="breadcrumb__area-content">
<h2>Internships</h2>
<ul>
<li><a href="/">Home</a><i class="fa-regular fa-angle-right"></i></li>
<li>Internships</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Breadcrumb Area End -->
<!-- Pricing Plan Area Start -->
<div class="price__area section-padding">
<div class="container">
<div class="row">
<div class="col-xl-4 col-md-6 xl-mb-25 wow fadeInUp" data-wow-delay=".4s">
@forelse ($internships as $internship)
<div class="price__area-item">
<div class="price__area-item-price">
<span>{{ $internship->title }}</span>
<h3>{{ $internship->location }}</h3>
<h2>{{ $internship->salary_per_month }} {{ $internship->salary_currency }}</h2>
<span>Per monthly</span>
</div>
<div class="price__area-item-list">
<ul>
@foreach($internship->bullets as $bullet)
<li><i class="flaticon-checked"></i>{{ $bullet['bullet'] ?? '' }}</li>
@endforeach
</ul>
</div>
<div class="price__area-item-btn">
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applyInternshipModal" data-job-id="{{ $internship->id }}" data-job-type="internship" data-internship-title="{{ $internship->title }}" data-internship-location="{{ $internship->location }}" data-internship-salary="{{ $internship->salary_per_month }}" data-internship-description="{{ $internship->title_description }}">Apply Now<i class="flaticon-right-up"></i></button>
</div>
</div>
@empty
<span>No internships found...</span>
@endforelse
</div>
</div>
</div>
</div>
<!-- Pricing Plan Area End -->
{{-- Application Modal --}}
@include('web.components.application_modal', ['jobId' => null, 'jobType' => 'internship']) {{-- jobId will be set by JS from button data attributes --}}
@endsection
@push('footer-js')
<script>
document.addEventListener('DOMContentLoaded', function () {
var applicationModal = document.getElementById('applyInternshipModal');
if (applicationModal) {
applicationModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget; // Button that triggered the modal
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
var modalTitle = applicationModal.querySelector('#jobTitle');
var modalLocation = applicationModal.querySelector('#jobLocation');
var modalSalary = applicationModal.querySelector('#jobSalary');
var modalDescription = applicationModal.querySelector('#jobDescription');
var jobIdInput = applicationModal.querySelector('#jobIdInput');
var applicationForm = applicationModal.querySelector('#applicationForm');
// Set dynamic values
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
jobIdInput.value = jobId;
// Set form action dynamically
if (jobType === 'internship') {
applicationForm.action = '{{ route('internship.store') }}';
jobIdInput.name = 'internship_id';
} else if (jobType === 'career') {
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
jobIdInput.name = 'career_id';
}
});
// Handle form submission with AJAX
document.getElementById('applicationForm').addEventListener('submit', function (e) {
e.preventDefault();
let form = e.target;
let formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
var modal = bootstrap.Modal.getInstance(applicationModal);
modal.hide();
form.reset();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
});
});
}
});
</script>
@endpush

View File

@@ -0,0 +1,32 @@
@extends('web.layouts.app')
@section('title', $internship->title)
@section('content')
<!-- Main Content for Single Internship Page -->
<div class="container">
<h1>{{ $internship->title }}</h1>
<p><strong>{{ __('Location:') }}</strong> {{ $internship->location }}</p>
<p><strong>{{ __('Salary:') }}</strong> {{ $internship->salary_per_month }} {{ $internship->salary_currency }}</p>
<h2>{{ __('Description') }}</h2>
<p>{{ $internship->title_description }}</p>
@if ($internship->bullets)
<h3>{{ __('Responsibilities and Qualifications') }}</h3>
<ul>
@foreach ($internship->bullets as $bullet)
<li>{{ $bullet['bullet'] }}</li>
@endforeach
</ul>
@endif
<!-- Apply Button - Triggers Modal -->
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#applyInternshipModal">
{{ __('Apply for this Internship') }}
</button>
</div>
<!-- Application Modal -->
@include('web.components.application_modal', ['jobId' => $internship->id, 'jobType' => 'internship'])
@endsection