DONE: fix password bypass in production, use dropdown for admin to edit membership application #13

Merged
ismailmasseran merged 1 commits from testcase/fix-feature into main 2026-07-16 11:02:14 +08:00
5 changed files with 70 additions and 17 deletions
+23 -11
View File
@@ -94,19 +94,31 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::authenticateUsing(function (Request $request) {
$user = User::where('email', $request->email)->first();
// Bypass password check in local or development environments
$shouldBypassPassword = config('app.env', 'local');
if ($user && ($shouldBypassPassword || Hash::check($request->password, $user->password))) {
if (! $user->canAuthenticate()) {
throw ValidationException::withMessages([
'email' => [$user->getLoginRestrictionMessage()],
]);
}
$invalidCredentialsMessage = 'Emel atau kata laluan tidak sah.';
return $user;
$failLogin = function () use ($invalidCredentialsMessage): never {
throw ValidationException::withMessages([
'email' => [$invalidCredentialsMessage],
]);
};
if (! $user) {
$failLogin();
}
$shouldBypassPassword = config('app.env', 'local');
if (! $shouldBypassPassword && ! Hash::check($request->password, $user->password)) {
$failLogin();
}
if (! $user->canAuthenticate()) {
throw ValidationException::withMessages([
'email' => [$user->getLoginRestrictionMessage()],
]);
}
return $user;
});
}
}
+5 -2
View File
@@ -59,10 +59,13 @@ return Application::configure(basePath: dirname(__DIR__))
// Handle Validation exceptions (422 errors)
$exceptions->render(function (Illuminate\Validation\ValidationException $e, $request) {
if ($request->expectsJson()) {
$errors = $e->errors();
$firstMessage = collect($errors)->flatten()->first() ?? 'Data tidak sah.';
return response()->json([
'success' => false,
'message' => 'Data tidak sah.',
'errors' => $e->errors(),
'message' => $firstMessage,
'errors' => $errors,
], 422);
}
});
+1 -1
View File
@@ -23,7 +23,7 @@ export const EMPLOYERS = [
},
{
name: 'Xtra Biz Sdn Bhd (XBSB)',
address: "Lot 241, Jalan Dato' Cik Arif, Taman Mutiara 1, 15000 Kota Bharu, Kelantan.",
address: "-",
},
{
name: 'Pesara',
@@ -58,6 +58,7 @@ import { ADMIN_ATTACHMENT_DOCUMENT_TYPE } from '../types/membership-application.
import {
APPLICANT_DOCUMENT_UPLOAD_TYPES,
DOCUMENT_TYPE_LABELS,
EMPLOYER_OPTIONS,
GENDER_OPTIONS,
MARRIAGE_STATUS_OPTIONS,
RELATIONSHIP_OPTIONS,
@@ -67,6 +68,7 @@ import {
createEmptyFormState,
createSelectCollection,
detailToFormState,
getEmployerAddress,
labelToApiValue,
} from '../utils/membership-application-form.utils'
@@ -112,6 +114,7 @@ const referenceLookupLoading = reactive({
const genderCollection = createSelectCollection(GENDER_OPTIONS)
const marriageStatusCollection = createSelectCollection(MARRIAGE_STATUS_OPTIONS)
const relationshipCollection = createSelectCollection(RELATIONSHIP_OPTIONS)
const employerCollection = createSelectCollection(EMPLOYER_OPTIONS)
const canEdit = computed(
() =>
@@ -123,6 +126,7 @@ const genderInitial = computed(() => apiValueToLabel(GENDER_OPTIONS, form.applic
const marriageStatusInitial = computed(() =>
apiValueToLabel(MARRIAGE_STATUS_OPTIONS, form.applicant.marriage_status),
)
const employerInitial = computed(() => apiValueToLabel(EMPLOYER_OPTIONS, form.applicant.employer_name))
const workflowProgress = computed(() =>
application.value ? getWorkflowProgress(application.value.status) : null,
@@ -309,6 +313,14 @@ function setMarriageStatusValue(details: { value: string[] }) {
delete fieldErrors['applicant.marriage_status']
}
function setEmployerValue(details: { value: string[] }) {
const employerName = labelToApiValue(EMPLOYER_OPTIONS, details.value[0])
form.applicant.employer_name = employerName
form.applicant.employer_address = getEmployerAddress(employerName)
delete fieldErrors['applicant.employer_name']
delete fieldErrors['applicant.employer_address']
}
function setHeirRelationshipValue(index: number, details: { value: string[] }) {
const heir = form.heirs[index]
if (!heir) return
@@ -859,8 +871,24 @@ onUnmounted(() => {
<TabsContent value="employment" class="mt-6">
<div class="grid grid-cols-12 gap-4 gap-y-5">
<Field class="col-span-12 sm:col-span-6">
<FieldLabel for="employer_name">Nama Majikan</FieldLabel>
<Input id="employer_name" v-model="form.applicant.employer_name" type="text" :disabled="!canEdit" />
<FieldLabel>Nama Majikan</FieldLabel>
<SelectRoot :key="`employer-${form.applicant.employer_name}`" class="w-full"
:collection="employerCollection" :default-value="employerInitial" :disabled="!canEdit"
@value-change="setEmployerValue">
<SelectControl>
<SelectTrigger :aria-invalid="!!fieldErrors['applicant.employer_name']">
<SelectValueText placeholder="Pilih majikan" />
</SelectTrigger>
</SelectControl>
<SelectContent>
<SelectItemGroup>
<SelectItemGroupLabel>Nama Majikan</SelectItemGroupLabel>
<SelectItem v-for="item in employerCollection.items" :key="item.label" :item="item">
<SelectItemText>{{ item.label }}</SelectItemText>
</SelectItem>
</SelectItemGroup>
</SelectContent>
</SelectRoot>
<FieldError v-if="fieldErrors['applicant.employer_name']">{{ fieldErrors['applicant.employer_name'] }}
</FieldError>
</Field>
@@ -872,7 +900,7 @@ onUnmounted(() => {
</Field>
<Field class="col-span-12">
<FieldLabel for="employer_address">Alamat Majikan</FieldLabel>
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" :disabled="!canEdit" />
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" disabled />
<FieldError v-if="fieldErrors['applicant.employer_address']">{{ fieldErrors['applicant.employer_address']
}}</FieldError>
</Field>
@@ -1,5 +1,6 @@
import dayjs from 'dayjs'
import * as select from '@zag-js/select'
import { EMPLOYERS } from '@/constants/employers'
import type {
MembershipApplicationDetail,
MembershipApplicationDocumentsForm,
@@ -35,6 +36,15 @@ export const RELATIONSHIP_OPTIONS: SelectOption[] = [
{ label: 'Lain-lain', value: 'Lain-lain' },
]
export const EMPLOYER_OPTIONS: SelectOption[] = EMPLOYERS.map((employer) => ({
label: employer.name,
value: employer.name,
}))
export function getEmployerAddress(name: string): string {
return EMPLOYERS.find((employer) => employer.name === name)?.address ?? ''
}
export const DOCUMENT_TYPE_LABELS: Record<string, string> = {
ic_copy: 'Salinan Kad Pengenalan',
photo: 'Gambar Passport',