penambahan nidn pada user yang sudah login dan notifikasi

This commit is contained in:
baghizadizn 2025-11-29 11:53:46 +07:00
parent 7553fc022a
commit a6efc67883
5 changed files with 381 additions and 23 deletions

View File

@ -121,6 +121,10 @@ class RegisteredUserController extends Controller
// Log the user in after successful registration
Auth::login($user);
//send notification to user
$application = Application::max('id') + 1;
$user->notify(new \App\Notifications\InstructorApplicant($application));
return redirect(RouteServiceProvider::HOME);
} catch (\Exception $e) {
@ -181,7 +185,7 @@ class RegisteredUserController extends Controller
'id_sdm' => $matched_dosen['id'] ?? null,
'id_sms' => $matched_dosen['nama_prodi'] ?? null,
'id_pt' => $matched_dosen['nama_pt'] ?? null,
'status' => 0
'status' => 0
];
Log::info('Instructor data to be saved:', $instructor);

View File

@ -5,10 +5,17 @@ namespace App\Http\Controllers\student;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\FileUploader;
use App\Models\Instructors;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class BecomeInstructorController extends Controller
{
@ -20,40 +27,154 @@ class BecomeInstructorController extends Controller
public function store(Request $request)
{
// check application exists or not
// Check if application already exists
if (Application::where('user_id', auth()->user()->id)->exists()) {
Session::flash('error', get_phrase('Your request is in process. Please wait for admin to response.'));
return redirect()->route('become.instructor');
}
$rules = [
'nidn' => ['required', 'string', 'max:11'],
'phone' => 'required',
'document' => 'required|file|mimes:doc,docx,pdf,txt,png,jpg,jpeg|max:5120',
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:2048'],
'description' => 'required',
];
// validate data
$validator = Validator::make($request->all(), $rules);
// Validate data
$validator = Validator::make($request->all(), $rules, [
'nidn.required' => get_phrase('NIDN is required'),
'nidn.string' => get_phrase('NIDN must be a valid number'),
'nidn.max' => get_phrase('NIDN may not be greater than 11 characters'),
'phone.required' => get_phrase('Phone number is required'),
'document.required' => get_phrase('Document is required'),
'document.file' => get_phrase('Document must be a valid file'),
'document.mimes' => get_phrase('Document must be PDF, DOC, or DOCX'),
'document.max' => get_phrase('Document size must be less than 2MB'),
'description.required' => get_phrase('Description is required'),
]);
if ($validator->fails()) {
$firstError = $validator->errors()->first();
Session::flash('error', $firstError);
return redirect()->back()->withErrors($validator)->withInput();
}
$user = auth()->user();
// process data
$application['user_id'] = auth()->user()->id;
$application['phone'] = $request->phone;
$application['description'] = $request->description;
try {
DB::beginTransaction();
$doc = $request->document;
$application['document'] = 'uploads/applications/' . auth()->user()->id . Str::random(20) .'.'. $doc->extension();
$result = $this->processInstructorApplication($request, $user);
// upload document
FileUploader::upload($doc, $application['document'], null, null, 300);
if ($result instanceof RedirectResponse && $result->getSession()->get('error')) {
DB::rollBack();
return $result;
}
// store application
Application::insert($application);
DB::commit();
Session::flash('success', get_phrase('Your application has been submitted.'));
return redirect()->back();
// Get the latest application and send notification
$application = Application::max('id') + 1;
if ($application) {
$user->notify(new \App\Notifications\InstructorApplicant($application));
}
Session::flash('success', get_phrase('Your application has been submitted successfully.'));
return redirect(RouteServiceProvider::HOME);
} catch (\Exception $e) {
DB::rollBack();
Log::error('Instructor application error:', ['error' => $e->getMessage()]);
Session::flash('error', get_phrase('Error during application submission. Please try again.'));
return redirect()->back()->withInput();
}
}
private function processInstructorApplication(Request $request, User $user): RedirectResponse
{
// Check if application already exists (redundant but safe)
if (Application::where('user_id', $user->id)->exists()) {
Session::flash('error', get_phrase('Your request is in process. Please wait for admin to respond.'));
return redirect()->route('become.instructor');
}
// Check if NIDN already exists in Instructors table with status = 1 (active)
$nidn = $request->nidn;
$existingInstructor = Instructors::where('nidn', $nidn)
->where('status', 1)
->first();
if ($existingInstructor) {
Session::flash('error', get_phrase('This NIDN is already registered as an active instructor.'));
return redirect()->back()->withInput();
}
// Check NIDN with the API
$api_url = "https://sindig.unesa.ac.id/apipddikti/api?nidn={$nidn}&auto=1";
$response = Http::timeout(30)->get($api_url);
$data = $response->json();
Log::info('API Response for NIDN: ' . $nidn, ['response' => $data]);
// Extract matched dosen data
$matched_dosen = $data['matched_dosen'][0];
Log::info('Instructor data to be saved:', $matched_dosen);
if (!isset($data['ok']) || !isset($data['matched_dosen']) || count($data['matched_dosen']) == 0) {
Session::flash('error', get_phrase('NIDN not found in the system. Please check your NIDN.'));
return redirect()->back()->withInput();
} else if (strtolower($matched_dosen['nama']) != strtolower($user->name)) {
Session::flash('error', get_phrase('Name does not match PDDikti records. Please check your name.'));
return redirect()->back()->withInput();
}
// Upload document
$fileName = null;
if ($request->hasFile('document') && $request->file('document')->isValid()) {
$doc = $request->file('document');
$fileName = 'uploads/applications/' . $user->id . Str::random(20) . '.' . $doc->getClientOriginalExtension();
$uploadResult = FileUploader::upload($doc, $fileName, null, null, 300);
if (!$uploadResult) {
Session::flash('error', get_phrase('Document upload failed. Please try again.'));
return redirect()->back()->withInput();
}
} else {
Session::flash('error', get_phrase('Document upload failed or no document selected.'));
return redirect()->back()->withInput();
}
// Prepare instructor data
$instructorData = [
'user_id' => $user->id,
'nidn' => $nidn,
'name' => $matched_dosen['nama'] ?? $user->name,
'id_sdm' => $matched_dosen['id'] ?? null,
'id_sms' => $matched_dosen['nama_prodi'] ?? null,
'id_pt' => $matched_dosen['nama_pt'] ?? null,
'status' => 0
];
Log::info('Instructor data to be saved:', $instructorData);
// Prepare application data - INCLUDING DOCUMENT PATH
$applicationData = [
'user_id' => $user->id,
'nidn' => $nidn,
'phone' => $request->phone,
'description' => $request->description,
'document' => $fileName,
'status' => 0
];
Log::info('Application data to be saved:', $applicationData);
// Create both application and instructor records
Application::create($applicationData);
Instructors::create($instructorData);
return redirect()->back(); // This return won't be used due to the try-catch structure
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class InstructorApplicant extends Notification
{
use Queueable;
protected $application;
/**
* Create a new notification instance.
*/
public function __construct($application)
{
$this->application = $application;
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(get_phrase('Instructor Application Approved') . ' - ' . config('app.name'))
->view('vendor.notifications.instructor_applicant', [
'user' => $notifiable,
'application' => $this->application
]);
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}

View File

@ -17,21 +17,39 @@
<div class="row">
<div class="col-lg-12 mb-20">
<div class="form-group">
<label for="phone" class="form-label">{{ get_phrase('Phone Number') }}</label>
<input type="tel" class="form-control @error('phone') border border-danger @enderror " name="phone" id="phone" placeholder="{{ get_phrase('+0 (123) 456 - 7890') }}">
<label for="nidn" class="form-label">{{ get_phrase('NIDN') }}</label>
<input class="form-control @error('nidn') is-invalid @enderror" id="nidn" type="number" name="nidn" placeholder="{{ get_phrase('Enter your NIDN number') }}" value="{{ old('nidn') }}">
@error('nidn')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
</div>
<div class="col-lg-12 mb-20">
<div class="form-group">
<label for="document" class="form-label">{{ get_phrase('Document') }}</label>
<input type="file" class="form-control @error('document') border border-danger @enderror" name="document" id="document">
<small class="ps-3 text-secondary">{{ get_phrase('Documents of qualification. Max-size : 5MB (DOC, DOCX, PDF, TXT, PNG, JPG, JPEG)') }}</small>
<label for="phone" class="form-label">{{ get_phrase('Phone Number') }}</label>
<input class="form-control @error('phone') is-invalid @enderror" id="phone" type="number" name="phone" placeholder="{{ get_phrase('Enter your phone number') }}" value="{{ old('phone') }}">
@error('phone')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
</div>
<div class="col-lg-12 mb-20">
<div class="form-group">
<label for="document" class="form-label">{{ get_phrase('Document') }} <small>(doc, docs, pdf)</small></label>
<input class="form-control @error('document') is-invalid @enderror" id="document" type="file" name="document" accept=".doc,.docx,.pdf" onchange="validateFileSize(this)">
@error('document')
<small class="text-danger">{{ $message }}</small>
@enderror
<small class="ps-3 text-secondary">{{ get_phrase('Provide some documents about your qualifications') }}</small>
</div>
</div>
<div class="col-lg-12 mb-20">
<div class="form-group">
<label for="description" class="form-label">{{ get_phrase('Description') }}</label>
<textarea name="description" class="form-control @error('description') border border-danger @enderror" id="description" cols="30" rows="5" placeholder="{{ get_phrase('Your description here...') }}"></textarea>
<textarea name="description" class="form-control @error('description') border border-danger @enderror" id="description" cols="30" rows="5" placeholder="{{ get_phrase('Your personal summary here...') }}"></textarea>
@error('description')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
</div>
</div>
@ -45,5 +63,37 @@
<!------------ My profile area end ------------>
@endsection
@push('js')
<script>
"use strict";
function onLoginSubmit(token) {
document.getElementById("login-form").submit();
}
$(document).ready(function() {
$('#instructor').on('change', function() {
if ($(this).is(':checked')) {
$('#become-instructor-fields').removeClass('d-none');
} else {
$('#become-instructor-fields').addClass('d-none');
}
});
});
// File size validation
function validateFileSize(input) {
const file = input.files[0];
if (file) {
const fileSizeInMB = file.size / (1024 * 1024); // Convert to MB
if (fileSizeInMB > 2) {
// Show error message using existing toaster system
error('{{ get_phrase("File size exceeds 2MB limit.") }}');
input.value = "";
}
}
}
</script>
@endpush

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ get_phrase('Instructor Application Submitted') }}</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
margin: 0;
padding: 0;
}
.email-container {
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.email-header {
background-color: #2f57ef;
color: #ffffff;
text-align: center;
padding: 20px;
font-size: 24px;
font-weight: bold;
}
.email-body {
padding: 20px;
color: #333333;
font-size: 16px;
line-height: 1.6;
}
.email-footer {
background-color: #f0f3ff;
color: #2f57ef;
text-align: center;
padding: 10px;
font-size: 14px;
border-top: 1px solid #dddddd;
}
.application-details {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
border-left: 4px solid #2f57ef;
}
.thank-you {
color: #2f57ef;
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
}
.status-pending {
display: inline-block;
background-color: #fff3cd;
color: #856404;
padding: 5px 10px;
border-radius: 15px;
font-size: 14px;
font-weight: bold;
margin-left: 10px;
}
.notification-info {
background-color: #e7f3ff;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
border-left: 4px solid #2f57ef;
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
{{ get_phrase('Instructor Application Submitted') }}
</div>
<div class="email-body">
<div class="thank-you">
{{ get_phrase('Thank You') }}, {{ $user->name }}! 🙂
</div>
<p>{{ get_phrase('We have successfully received your instructor application and it is now under review.') }}</p>
<div class="application-details">
<p><strong>{{ get_phrase('Application Details') }}:</strong></p>
<p><strong>{{ get_phrase('Application ID') }}:</strong> {{ $application->id }}</p>
<p><strong>{{ get_phrase('Submission Date') }}:</strong> {{ $application->created_at->format('F d, Y') }}</p>
<p><strong>{{ get_phrase('Current Status') }}:</strong>
<span class="status-pending">{{ get_phrase('Under Review') }}</span>
</p>
</div>
<div class="notification-info">
<p><strong>{{ get_phrase('Important Information') }}:</strong></p>
<p>{{ get_phrase('You will be notified via email once your application has been processed.') }}</p>
<p>{{ get_phrase('If your application is accepted, you will receive an approval email with instructions on how to access the instructor dashboard.') }}</p>
<p>{{ get_phrase('If your application is rejected, you will receive a notification email with further information.') }}</p>
</div>
<p><strong>{{ get_phrase('What happens next') }}?</strong></p>
<ul>
<li>{{ get_phrase('Our team will review your application carefully') }}</li>
<li>{{ get_phrase('Review process typically takes 2-3 business days') }}</li>
<li>{{ get_phrase('You will receive an email notification for the final decision') }}</li>
<li>{{ get_phrase('We may contact you if additional information is needed') }}</li>
</ul>
<p><strong>{{ get_phrase('In the meantime') }}:</strong></p>
<p>{{ get_phrase('You can continue to explore our platform and prepare your course materials while waiting for the review results.') }}</p>
<p>{{ get_phrase('If you have any questions about your application or need to provide additional documents, please contact our support team.') }}</p>
<p>{{ get_phrase('We appreciate your interest in becoming an instructor with us!') }}</p>
</div>
<div class="email-footer">
<p><strong>{{ config('app.name') }}</strong></p>
<p>{{ get_phrase('Instructor Recruitment Team') }}</p>
</div>
</div>
</body>
</html>