readme.md
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# GIS Portal & Sinergi Umat Dashboard
|
||||
|
||||
Proyek ini adalah portal Sistem Informasi Geografis (GIS) terintegrasi yang menggabungkan visualisasi peta spasial Pontianak dengan sistem dasbor manajemen kesejahteraan masyarakat (Sinergi Umat).
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ Komponen Proyek
|
||||
|
||||
1. **Portal Utama GIS (PHP + Apache)**:
|
||||
- Visualisasi Lokasi SPBU (`spbu-map`)
|
||||
- Visualisasi Parsil Tanah (`parsil-tanah`)
|
||||
- Peta Kloroplet (`chloropleth`)
|
||||
2. **Dasbor Sinergi Umat (`sistem-km-next`)**:
|
||||
- Aplikasi berbasis Next.js untuk pemetaan rumah ibadah (Mosque, Church, Vihara, Temple) dan survei kesejahteraan Keluarga Penerima Manfaat (KPM).
|
||||
3. **Database MySQL**:
|
||||
- Penyimpanan data SPBU, parsil, dan kloroplet.
|
||||
4. **Supabase**:
|
||||
- Backend auth dan database real-time untuk data rumah ibadah, data keluarga (households), transaksi donasi, dan approval queue di Sinergi Umat.
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Opsi 1: Menjalankan Menggunakan Docker (Direkomendasikan)
|
||||
|
||||
Dengan Docker Compose, seluruh aplikasi portal PHP, dasbor Next.js, MySQL database, dan phpMyAdmin akan dijalankan otomatis dalam kontainer yang saling terhubung.
|
||||
|
||||
### Prasyarat
|
||||
- Sudah menginstal **Docker Desktop** di komputer Anda.
|
||||
|
||||
### Langkah-langkah
|
||||
1. Buka terminal/command prompt pada **direktori root** proyek (`gis-portal` / folder tempat `docker-compose.yaml` berada).
|
||||
2. Jalankan perintah berikut untuk mengunduh, membuat build image, dan menjalankan kontainer secara background:
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
3. Setelah proses build selesai dan container berjalan (`healthy`), Anda dapat mengakses layanan berikut di browser Anda:
|
||||
- **Portal Utama GIS (PHP, SPBU, Parsil, Kloroplet)**: [http://localhost:8080](http://localhost:8080)
|
||||
- **Dasbor Sinergi Umat (Next.js)**: [http://localhost:3000](http://localhost:3000)
|
||||
- **phpMyAdmin (Manajemen Database MySQL)**: [http://localhost:8085](http://localhost:8085)
|
||||
- **MySQL Database**: Berjalan di port `3306` (host: `localhost`, user: `root`, password: `rootpassword`, database: `spbu_db`).
|
||||
|
||||
### Catatan Penting Docker:
|
||||
- Data MySQL disimpan secara persisten di volume docker `db-data`.
|
||||
- Skema awal database otomatis di-import saat database pertama kali di-boot dari berkas `./docker/db/init.sql`.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Opsi 2: Menjalankan Tanpa Docker (Manual)
|
||||
|
||||
Jika ingin menjalankan aplikasi secara langsung di sistem lokal Anda tanpa menggunakan kontainer Docker.
|
||||
|
||||
### Prasyarat
|
||||
- **Web Server Lokal** (seperti Laragon atau XAMPP) yang mendukung PHP (min v8.1) dan MySQL.
|
||||
- **Node.js** (min v18 atau v20) terinstal pada komputer Anda.
|
||||
|
||||
### Langkah-langkah Setup Manual:
|
||||
|
||||
#### 1. Setup Database MySQL Lokal
|
||||
1. Pastikan server MySQL lokal Anda (Laragon/XAMPP) sudah menyala.
|
||||
2. Buat database baru bernama `spbu_db` melalui phpMyAdmin lokal Anda.
|
||||
3. Import berkas SQL skema awal yang terletak di `docker/db/init.sql` ke dalam database `spbu_db` tersebut.
|
||||
|
||||
#### 2. Jalankan Portal GIS Utama (PHP App)
|
||||
1. Pindahkan atau hubungkan (symlink) seluruh folder proyek `gis-portal` ke direktori root web server Anda:
|
||||
- Untuk **Laragon**: taruh di `C:\laragon\www\gis-portal`
|
||||
- Untuk **XAMPP**: taruh di `C:\xampp\htdocs\gis-portal`
|
||||
2. Konfigurasi koneksi database PHP berada di berkas:
|
||||
- `spbu-map/db.php`
|
||||
- `parsil-tanah/db.php`
|
||||
*(Secara default, skrip PHP akan otomatis terhubung ke localhost dengan database `spbu_db`, user `root`, dan password kosong `""`)*.
|
||||
3. Akses portal utama melalui browser Anda di:
|
||||
- Laragon: [http://gis-portal.test](http://gis-portal.test)
|
||||
- XAMPP: [http://localhost/gis-portal](http://localhost/gis-portal)
|
||||
|
||||
#### 3. Jalankan Dasbor Sinergi Umat (Next.js App)
|
||||
1. Buka terminal Anda dan masuk ke direktori Next.js:
|
||||
```bash
|
||||
cd sistem-km-next
|
||||
```
|
||||
2. Instal pustaka ketergantungan (dependencies) dengan perintah:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Pastikan berkas `.env.local` sudah ada di dalam folder `sistem-km-next` dan berisi kredensial Supabase berikut (sudah otomatis disertakan dalam berkas zip):
|
||||
```env
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://gzpvygrsfdtzykyqbnes.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
4. Jalankan server pengembangan Next.js lokal:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
5. Buka browser Anda dan akses dasbor Sinergi Umat di [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
---
|
||||
|
||||
## 💾 Struktur Folder Proyek
|
||||
|
||||
```text
|
||||
gis-portal/
|
||||
├── README.md # Dokumentasi utama (berkas ini)
|
||||
├── chloropleth/ # Fitur peta kloroplet Pontianak (PHP/HTML/JS)
|
||||
├── docker/ # Konfigurasi Dockerfile untuk php, nextjs, & db
|
||||
│ ├── db/
|
||||
│ │ └── init.sql # Database schema SQL init
|
||||
│ ├── nextjs/
|
||||
│ └── php/
|
||||
├── parsil-tanah/ # Fitur peta parsil tanah (PHP/HTML/JS)
|
||||
├── sistem-km-next/ # Dasbor Sinergi Umat (Next.js App)
|
||||
│ ├── src/
|
||||
│ └── README.md # Dokumentasi subsistem Next.js
|
||||
├── spbu-map/ # Fitur peta sebaran SPBU (PHP/HTML/JS)
|
||||
├── docker-compose.yaml # Main configuration file Docker
|
||||
├── docker-compose.override.yaml # Local override config (Port mapping & bind mounts)
|
||||
└── index.html # Halaman Beranda / Gateway Portal GIS
|
||||
```
|
||||
+112
-32
@@ -1,36 +1,116 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# GIS Portal & Sinergi Umat Dashboard
|
||||
|
||||
## Getting Started
|
||||
Proyek ini adalah portal Sistem Informasi Geografis (GIS) terintegrasi yang menggabungkan visualisasi peta spasial Pontianak dengan sistem dasbor manajemen kesejahteraan masyarakat (Sinergi Umat).
|
||||
|
||||
First, run the development server:
|
||||
---
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
## 🏛️ Komponen Proyek
|
||||
|
||||
1. **Portal Utama GIS (PHP + Apache)**:
|
||||
- Visualisasi Lokasi SPBU (`spbu-map`)
|
||||
- Visualisasi Parsil Tanah (`parsil-tanah`)
|
||||
- Peta Kloroplet (`chloropleth`)
|
||||
2. **Dasbor Sinergi Umat (`sistem-km-next`)**:
|
||||
- Aplikasi berbasis Next.js untuk pemetaan rumah ibadah (Mosque, Church, Vihara, Temple) dan survei kesejahteraan Keluarga Penerima Manfaat (KPM).
|
||||
3. **Database MySQL**:
|
||||
- Penyimpanan data SPBU, parsil, dan kloroplet.
|
||||
4. **Supabase**:
|
||||
- Backend auth dan database real-time untuk data rumah ibadah, data keluarga (households), transaksi donasi, dan approval queue di Sinergi Umat.
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Opsi 1: Menjalankan Menggunakan Docker (Direkomendasikan)
|
||||
|
||||
Dengan Docker Compose, seluruh aplikasi portal PHP, dasbor Next.js, MySQL database, dan phpMyAdmin akan dijalankan otomatis dalam kontainer yang saling terhubung.
|
||||
|
||||
### Prasyarat
|
||||
- Sudah menginstal **Docker Desktop** di komputer Anda.
|
||||
|
||||
### Langkah-langkah
|
||||
1. Buka terminal/command prompt pada **direktori root** proyek (`gis-portal` / folder tempat `docker-compose.yaml` berada).
|
||||
2. Jalankan perintah berikut untuk mengunduh, membuat build image, dan menjalankan kontainer secara background:
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
3. Setelah proses build selesai dan container berjalan (`healthy`), Anda dapat mengakses layanan berikut di browser Anda:
|
||||
- **Portal Utama GIS (PHP, SPBU, Parsil, Kloroplet)**: [http://localhost:8080](http://localhost:8080)
|
||||
- **Dasbor Sinergi Umat (Next.js)**: [http://localhost:3000](http://localhost:3000)
|
||||
- **phpMyAdmin (Manajemen Database MySQL)**: [http://localhost:8085](http://localhost:8085)
|
||||
- **MySQL Database**: Berjalan di port `3306` (host: `localhost`, user: `root`, password: `rootpassword`, database: `spbu_db`).
|
||||
|
||||
### Catatan Penting Docker:
|
||||
- Data MySQL disimpan secara persisten di volume docker `db-data`.
|
||||
- Skema awal database otomatis di-import saat database pertama kali di-boot dari berkas `./docker/db/init.sql`.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Opsi 2: Menjalankan Tanpa Docker (Manual)
|
||||
|
||||
Jika ingin menjalankan aplikasi secara langsung di sistem lokal Anda tanpa menggunakan kontainer Docker.
|
||||
|
||||
### Prasyarat
|
||||
- **Web Server Lokal** (seperti Laragon atau XAMPP) yang mendukung PHP (min v8.1) dan MySQL.
|
||||
- **Node.js** (min v18 atau v20) terinstal pada komputer Anda.
|
||||
|
||||
### Langkah-langkah Setup Manual:
|
||||
|
||||
#### 1. Setup Database MySQL Lokal
|
||||
1. Pastikan server MySQL lokal Anda (Laragon/XAMPP) sudah menyala.
|
||||
2. Buat database baru bernama `spbu_db` melalui phpMyAdmin lokal Anda.
|
||||
3. Import berkas SQL skema awal yang terletak di `docker/db/init.sql` ke dalam database `spbu_db` tersebut.
|
||||
|
||||
#### 2. Jalankan Portal GIS Utama (PHP App)
|
||||
1. Pindahkan atau hubungkan (symlink) seluruh folder proyek `gis-portal` ke direktori root web server Anda:
|
||||
- Untuk **Laragon**: taruh di `C:\laragon\www\gis-portal`
|
||||
- Untuk **XAMPP**: taruh di `C:\xampp\htdocs\gis-portal`
|
||||
2. Konfigurasi koneksi database PHP berada di berkas:
|
||||
- `spbu-map/db.php`
|
||||
- `parsil-tanah/db.php`
|
||||
*(Secara default, skrip PHP akan otomatis terhubung ke localhost dengan database `spbu_db`, user `root`, dan password kosong `""`)*.
|
||||
3. Akses portal utama melalui browser Anda di:
|
||||
- Laragon: [http://gis-portal.test](http://gis-portal.test)
|
||||
- XAMPP: [http://localhost/gis-portal](http://localhost/gis-portal)
|
||||
|
||||
#### 3. Jalankan Dasbor Sinergi Umat (Next.js App)
|
||||
1. Buka terminal Anda dan masuk ke direktori Next.js:
|
||||
```bash
|
||||
cd sistem-km-next
|
||||
```
|
||||
2. Instal pustaka ketergantungan (dependencies) dengan perintah:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Pastikan berkas `.env.local` sudah ada di dalam folder `sistem-km-next` dan berisi kredensial Supabase berikut (sudah otomatis disertakan dalam berkas zip):
|
||||
```env
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://gzpvygrsfdtzykyqbnes.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
4. Jalankan server pengembangan Next.js lokal:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
5. Buka browser Anda dan akses dasbor Sinergi Umat di [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
---
|
||||
|
||||
## 💾 Struktur Folder Proyek
|
||||
|
||||
```text
|
||||
gis-portal/
|
||||
├── chloropleth/ # Fitur peta kloroplet Pontianak (PHP/HTML/JS)
|
||||
├── docker/ # Konfigurasi Dockerfile untuk php, nextjs, & db
|
||||
│ ├── db/
|
||||
│ │ └── init.sql # Database schema SQL init
|
||||
│ ├── nextjs/
|
||||
│ └── php/
|
||||
├── parsil-tanah/ # Fitur peta parsil tanah (PHP/HTML/JS)
|
||||
├── sistem-km-next/ # Dasbor Sinergi Umat (Next.js App)
|
||||
│ ├── src/
|
||||
│ └── README.md # Dokumentasi ini
|
||||
├── spbu-map/ # Fitur peta sebaran SPBU (PHP/HTML/JS)
|
||||
├── docker-compose.yaml # Main configuration file Docker
|
||||
├── docker-compose.override.yaml # Local override config (Port mapping & bind mounts)
|
||||
├── index.html # Halaman Beranda / Gateway Portal GIS
|
||||
└── README.md -> sistem-km-next/README.md
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
|
||||
@@ -1,29 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
export default function ChangePassword() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [user, setUser] = useState(null);
|
||||
const [profile, setProfile] = useState(null);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const router = useRouter();
|
||||
const mapRef = useRef(null);
|
||||
const mapInstanceRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth.getUser().then(({ data }) => {
|
||||
if (!data?.user) router.push('/login');
|
||||
else setUser(data.user);
|
||||
if (!data?.user) {
|
||||
router.push('/login');
|
||||
} else {
|
||||
setUser(data.user);
|
||||
supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', data.user.id)
|
||||
.single()
|
||||
.then(({ data: profileData }) => {
|
||||
setProfile(profileData);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
// Dynamically load Leaflet for the background map
|
||||
import('leaflet').then((L) => {
|
||||
if (!active) return;
|
||||
|
||||
if (!mapInstanceRef.current && mapRef.current) {
|
||||
// Initialize map centered on Pontianak
|
||||
const map = L.map(mapRef.current, {
|
||||
center: [-0.0227, 109.3340],
|
||||
zoom: 13,
|
||||
zoomControl: false,
|
||||
dragging: false,
|
||||
scrollWheelZoom: false,
|
||||
doubleClickZoom: false,
|
||||
boxZoom: false,
|
||||
keyboard: false,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
// Use CartoDB Positron for a clean, light background
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
maxZoom: 19,
|
||||
subdomains: 'abcd'
|
||||
}).addTo(map);
|
||||
|
||||
mapInstanceRef.current = map;
|
||||
|
||||
// Force resize recalculation to ensure tiles render immediately
|
||||
setTimeout(() => {
|
||||
if (active && mapInstanceRef.current) {
|
||||
mapInstanceRef.current.invalidateSize();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
if (mapInstanceRef.current) {
|
||||
mapInstanceRef.current.remove();
|
||||
mapInstanceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError('Konfirmasi kata sandi baru tidak cocok.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
setError('Kata sandi baru minimal harus 6 karakter.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { error: authError } = await supabase.auth.updateUser({ password });
|
||||
|
||||
if (authError) {
|
||||
@@ -48,50 +124,131 @@ export default function ChangePassword() {
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#f8fafc',
|
||||
fontFamily: '"Inter", sans-serif'
|
||||
fontFamily: '"Inter", sans-serif',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* Return to Dashboard Button */}
|
||||
<button
|
||||
onClick={() => router.push('/')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '24px',
|
||||
left: '24px',
|
||||
zIndex: 10,
|
||||
background: 'rgba(255, 255, 255, 0.85)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
WebkitBackdropFilter: 'blur(8px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.6)',
|
||||
borderRadius: '14px',
|
||||
padding: '10px 18px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
color: '#1e293b',
|
||||
fontWeight: '600',
|
||||
fontSize: '0.9rem',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.15)',
|
||||
transition: 'all 0.2s',
|
||||
outline: 'none'
|
||||
}}
|
||||
onMouseOver={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.95)';
|
||||
}}
|
||||
onMouseOut={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.85)';
|
||||
}}
|
||||
>
|
||||
<i className="fas fa-arrow-left" style={{ color: 'var(--primary, #3b82f6)' }}></i>
|
||||
<span>Kembali ke Dasbor</span>
|
||||
</button>
|
||||
|
||||
{/* Dynamic Leaflet Background */}
|
||||
<div
|
||||
ref={mapRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0, left: 0, right: 0, bottom: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 1,
|
||||
filter: 'brightness(0.95)'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Glassmorphism Dark/Blur Overlay */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
position: 'absolute',
|
||||
top: 0, left: 0, right: 0, bottom: 0,
|
||||
zIndex: 2,
|
||||
background: 'rgba(15, 23, 42, 0.45)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
WebkitBackdropFilter: 'blur(4px)'
|
||||
}} />
|
||||
|
||||
{/* Change Password Card */}
|
||||
<div style={{
|
||||
background: 'rgba(255, 255, 255, 0.85)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
WebkitBackdropFilter: 'blur(8px)',
|
||||
padding: '40px',
|
||||
borderRadius: '24px',
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.05)',
|
||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)',
|
||||
width: '100%',
|
||||
maxWidth: '400px',
|
||||
textAlign: 'center',
|
||||
border: '1px solid #e2e8f0'
|
||||
border: '1px solid rgba(255, 255, 255, 0.6)',
|
||||
zIndex: 10
|
||||
}}>
|
||||
<h2 style={{ margin: '0 0 8px 0', color: '#1e293b', fontSize: '1.5rem' }}>Rotasi Keamanan</h2>
|
||||
<p style={{ margin: '0 0 24px 0', color: '#ef4444', fontSize: '0.85rem', fontWeight: '500', background: '#fee2e2', padding: '10px', borderRadius: '8px' }}>
|
||||
<i className="fas fa-lock"></i> Untuk alasan keamanan, Anda wajib mengubah kata sandi default sebelum melanjutkan.
|
||||
<div style={{
|
||||
background: 'var(--primary, #3b82f6)',
|
||||
width: '60px', height: '60px',
|
||||
borderRadius: '50%',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: 'white', fontSize: '24px',
|
||||
margin: '0 auto 20px auto',
|
||||
boxShadow: '0 10px 15px -3px rgba(59, 130, 246, 0.5)'
|
||||
}}>
|
||||
<i className="fas fa-key"></i>
|
||||
</div>
|
||||
<h2 style={{ margin: '0 0 8px 0', color: '#1e293b', fontSize: '1.8rem', fontWeight: '800' }}>Ubah Kata Sandi</h2>
|
||||
<p style={{ margin: '0 0 24px 0', color: '#64748b', fontSize: '0.95rem' }}>
|
||||
{profile?.password_changed === false
|
||||
? 'Anda wajib mengubah kata sandi bawaan demi keamanan akun.'
|
||||
: 'Ubah kata sandi Anda untuk meningkatkan keamanan akun.'}
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div style={{ background: '#fee2e2', color: '#ef4444', padding: '10px', borderRadius: '8px', marginBottom: '16px', fontSize: '0.85rem' }}>
|
||||
{error}
|
||||
<div style={{ background: '#fee2e2', color: '#ef4444', padding: '10px', borderRadius: '8px', marginBottom: '16px', fontSize: '0.85rem', fontWeight: '500' }}>
|
||||
<i className="fas fa-exclamation-circle" style={{ marginRight: '5px' }}></i> {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleUpdate} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', color: '#475569', marginBottom: '6px', fontWeight: '500' }}>Kata Sandi Baru</label>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', color: '#475569', marginBottom: '6px', fontWeight: '600' }}>Kata Sandi Baru</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
minLength={6}
|
||||
style={{
|
||||
width: '100%', padding: '12px 44px 12px 16px', borderRadius: '12px',
|
||||
border: '1px solid #cbd5e1', background: 'white',
|
||||
outline: 'none', transition: 'border-color 0.2s', color: 'black',
|
||||
outline: 'none', transition: 'all 0.2s', color: '#1e293b',
|
||||
boxShadow: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--primary, #3b82f6)'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#cbd5e1'}
|
||||
required
|
||||
minLength={6}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -118,19 +275,67 @@ export default function ChangePassword() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', color: '#475569', marginBottom: '6px', fontWeight: '600' }}>Konfirmasi Kata Sandi Baru</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
style={{
|
||||
width: '100%', padding: '12px 44px 12px 16px', borderRadius: '12px',
|
||||
border: '1px solid #cbd5e1', background: 'white',
|
||||
outline: 'none', transition: 'all 0.2s', color: '#1e293b',
|
||||
boxShadow: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)'
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--primary, #3b82f6)'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#cbd5e1'}
|
||||
required
|
||||
minLength={6}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '14px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#94a3b8',
|
||||
cursor: 'pointer',
|
||||
padding: '4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '1rem',
|
||||
outline: 'none'
|
||||
}}
|
||||
title={showConfirmPassword ? 'Sembunyikan Kata Sandi' : 'Tampilkan Kata Sandi'}
|
||||
>
|
||||
<i className={showConfirmPassword ? 'fas fa-eye-slash' : 'fas fa-eye'}></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%', padding: '14px', borderRadius: '12px',
|
||||
background: '#10b981', color: 'white',
|
||||
background: 'var(--primary, #3b82f6)', color: 'white',
|
||||
border: 'none', fontWeight: '600', fontSize: '1rem',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
marginTop: '8px', transition: 'background 0.2s',
|
||||
opacity: loading ? 0.7 : 1
|
||||
marginTop: '8px', transition: 'all 0.2s',
|
||||
opacity: loading ? 0.8 : 1,
|
||||
boxShadow: loading ? 'none' : '0 4px 6px -1px rgba(59, 130, 246, 0.5)'
|
||||
}}
|
||||
onMouseOver={(e) => { if(!loading) e.target.style.transform = 'translateY(-2px)' }}
|
||||
onMouseOut={(e) => { if(!loading) e.target.style.transform = 'translateY(0)' }}
|
||||
>
|
||||
{loading ? 'Menyimpan...' : 'Simpan & Lanjutkan'}
|
||||
{loading ? <><i className="fas fa-spinner fa-spin"></i> Menyimpan...</> : 'Simpan & Lanjutkan'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function ProfileModal({
|
||||
showProfileModal,
|
||||
@@ -8,6 +9,7 @@ export default function ProfileModal({
|
||||
handleLogout,
|
||||
isLoggingOut
|
||||
}) {
|
||||
const router = useRouter();
|
||||
if (!showProfileModal) return null;
|
||||
|
||||
const getRoleBadge = (role) => {
|
||||
@@ -138,6 +140,33 @@ export default function ProfileModal({
|
||||
>
|
||||
<i className="fas fa-sign-out-alt"></i> {isLoggingOut ? 'Keluar...' : 'Keluar dari Akun (Logout)'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowProfileModal(false);
|
||||
router.push('/change-password');
|
||||
}}
|
||||
className="btn"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, var(--primary, #3b82f6), #1d4ed8)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
padding: '12px',
|
||||
borderRadius: '12px',
|
||||
fontWeight: '600',
|
||||
fontSize: '0.95rem',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px',
|
||||
transition: 'all 0.2s'
|
||||
}}
|
||||
onMouseOver={(e) => e.currentTarget.style.transform = 'translateY(-2px)'}
|
||||
onMouseOut={(e) => e.currentTarget.style.transform = 'translateY(0)'}
|
||||
>
|
||||
<i className="fas fa-key"></i> Ubah Kata Sandi
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowProfileModal(false)}
|
||||
className="btn btn-outline"
|
||||
|
||||
Reference in New Issue
Block a user