251 lines
5.3 KiB
Vue
251 lines
5.3 KiB
Vue
<template>
|
|
<div class="login-shell">
|
|
<div class="login-card">
|
|
<div class="login-header">
|
|
<img class="login-logo" src="/images/MyKoPKB-logo.png" alt="MyKoPKB" />
|
|
<h1 class="login-title">Selamat Datang</h1>
|
|
<p class="login-subtitle">Log masuk untuk meneruskan ke Portal MyKoPKB</p>
|
|
</div>
|
|
|
|
<form @submit.prevent="login" id="login_form" class="login-form" novalidate>
|
|
<div class="form-group mb-3">
|
|
<label class="login-label" for="no_kp">No. Kad Pengenalan</label>
|
|
<div class="login-field">
|
|
<span class="login-field-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24" focusable="false">
|
|
<path
|
|
d="M4 7.5A3.5 3.5 0 0 1 7.5 4h9A3.5 3.5 0 0 1 20 7.5v9A3.5 3.5 0 0 1 16.5 20h-9A3.5 3.5 0 0 1 4 16.5v-9Zm3.5-1.5A1.5 1.5 0 0 0 6 7.5v9A1.5 1.5 0 0 0 7.5 18h9a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 16.5 6h-9Zm1.5 3h6v2H9V9Zm0 4h10v2H9v-2Z" />
|
|
</svg>
|
|
</span>
|
|
<input v-model.trim="no_kp" @input="onNoKpInput" :disabled="loading" type="text" id="no_kp"
|
|
name="no_kp" class="form-control login-input" placeholder="Contoh: 901010101010"
|
|
autocomplete="off" inputmode="numeric" enterkeyhint="go" required />
|
|
</div>
|
|
<small class="login-help">Masukkan tanpa dash (-) atau jarak.</small>
|
|
</div>
|
|
|
|
<button type="submit" class="btn login-btn" :disabled="loading || !no_kp">
|
|
<span v-if="loading" class="spinner-border spinner-border-sm mr-2" role="status"
|
|
aria-hidden="true"></span>
|
|
<span>{{ loading ? 'Sedang log masuk…' : 'Masuk' }}</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
no_kp: ''
|
|
};
|
|
},
|
|
|
|
created() {
|
|
if (this.util.isAdminPortalSession()) {
|
|
this.$router.replace(this.util.getAdminEntryRoute());
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
onNoKpInput() {
|
|
// keep input forgiving: strip dash/spaces and non-digits
|
|
this.no_kp = (this.no_kp || '').replace(/[\s-]+/g, '').replace(/[^\d]/g, '');
|
|
},
|
|
login() {
|
|
this.startLoading();
|
|
const noKp = (this.no_kp || '').trim();
|
|
axios
|
|
.post(config.API + 'voter/login', {
|
|
no_kp: noKp,
|
|
send_otp: false
|
|
})
|
|
.then(response => {
|
|
this.stopLoading();
|
|
if (this.util.showResult(response, 'success')) {
|
|
this.$router.push({
|
|
name: 'Voter Verify',
|
|
params: {
|
|
'nokp': response.data.nokp,
|
|
'notel': response.data.notel,
|
|
'debug_otp': response.data.debug_otp || null
|
|
}
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
this.stopLoading();
|
|
this.util.showResult(error, 'error');
|
|
});
|
|
},
|
|
startLoading() {
|
|
this.loading = true;
|
|
},
|
|
stopLoading() {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-shell {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 32px 16px;
|
|
background: #f6f7fb;
|
|
}
|
|
|
|
.login-card {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
background: #ffffff;
|
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
border-radius: 20px;
|
|
box-shadow:
|
|
0 18px 55px rgba(2, 6, 23, 0.12),
|
|
0 2px 10px rgba(2, 6, 23, 0.06);
|
|
padding: 28px 24px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.login-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0 0 auto 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, #22c55e 0%, #3b82f6 55%, #a855f7 100%);
|
|
}
|
|
|
|
.login-header {
|
|
text-align: center;
|
|
margin-bottom: 16px;
|
|
padding-top: 6px;
|
|
}
|
|
|
|
.login-logo {
|
|
display: block;
|
|
margin: 0 auto 10px;
|
|
max-width: 240px;
|
|
height: auto;
|
|
}
|
|
|
|
.login-title {
|
|
margin: 0;
|
|
font-size: 22px;
|
|
font-weight: 900;
|
|
letter-spacing: -0.02em;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.login-subtitle {
|
|
margin: 6px 0 0;
|
|
font-size: 14px;
|
|
color: rgba(15, 23, 42, 0.72);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.login-form {
|
|
margin-top: 14px;
|
|
}
|
|
|
|
.login-label {
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
color: rgba(15, 23, 42, 0.85);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.login-field {
|
|
position: relative;
|
|
}
|
|
|
|
.login-field-icon {
|
|
position: absolute;
|
|
left: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 18px;
|
|
height: 18px;
|
|
color: rgba(15, 23, 42, 0.55);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.login-field-icon svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
fill: currentColor;
|
|
display: block;
|
|
}
|
|
|
|
.login-input {
|
|
border-radius: 14px;
|
|
padding: 12px 14px 12px 42px;
|
|
font-size: 16px;
|
|
border: 1px solid rgba(15, 23, 42, 0.14);
|
|
transition: box-shadow 160ms ease, border-color 160ms ease, transform 160ms ease;
|
|
}
|
|
|
|
.login-input:focus {
|
|
border-color: rgba(59, 130, 246, 0.55);
|
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.14);
|
|
}
|
|
|
|
.login-help {
|
|
display: block;
|
|
margin-top: 8px;
|
|
color: rgba(15, 23, 42, 0.6);
|
|
}
|
|
|
|
.login-btn {
|
|
width: 100%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 14px;
|
|
padding: 13px 14px;
|
|
font-weight: 900;
|
|
letter-spacing: 0.01em;
|
|
color: #ffffff;
|
|
background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
|
|
border: 0;
|
|
box-shadow: 0 12px 24px rgba(29, 78, 216, 0.22);
|
|
transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease;
|
|
}
|
|
|
|
.login-btn:hover {
|
|
filter: brightness(1.03);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 16px 34px rgba(29, 78, 216, 0.26);
|
|
}
|
|
|
|
.login-btn:disabled {
|
|
opacity: 0.72;
|
|
filter: saturate(0.9);
|
|
transform: none;
|
|
box-shadow: none;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.login-shell {
|
|
align-items: center;
|
|
padding: 20px 14px;
|
|
}
|
|
|
|
.login-card {
|
|
max-width: none;
|
|
border-radius: 20px;
|
|
padding: 22px 18px 18px;
|
|
}
|
|
|
|
.login-logo {
|
|
max-width: 200px;
|
|
}
|
|
}
|
|
</style> |