Skip to main content

rustbasic_breeze/
lib.rs

1use std::fs::{self, OpenOptions};
2use std::io::{Read, Write};
3use colored::*;
4use regex::Regex;
5
6pub fn update_controller_mod_rs(mod_name: &str) {
7    let mod_path = "src/app/http/controllers/mod.rs";
8    let mut content = String::new();
9    if let Ok(mut file) = fs::File::open(mod_path) {
10        file.read_to_string(&mut content).ok();
11    }
12
13    let line = format!("pub mod {};", mod_name);
14    if content.contains(&line) {
15        return;
16    }
17
18    let mut file = OpenOptions::new()
19        .append(true)
20        .open(mod_path)
21        .expect("Gagal membuka controllers/mod.rs");
22
23    writeln!(file, "{}", line).ok();
24    println!("{} {}", "📝".blue(), "controllers/mod.rs diperbarui.".dimmed());
25}
26
27pub fn update_migration_mod_rs(mod_name: &str) {
28    let mod_path = "database/migrations/mod.rs";
29    let mut content = String::new();
30    if let Ok(mut file) = fs::File::open(mod_path) {
31        file.read_to_string(&mut content).ok();
32    }
33
34    // Tambahkan mod declaration
35    if !content.contains(&format!("pub mod {};", mod_name)) {
36        if !content.ends_with('\n') {
37            content.push('\n');
38        }
39        content.push_str(&format!("pub mod {};\n", mod_name));
40    }
41
42    // Tambahkan ke list migrations
43    let search_pattern = "fn migrations() -> Vec<Box<dyn sea_orm_migration::prelude::MigrationTrait>> {";
44    let search_pattern_alt = "fn migrations() -> Vec<Box<dyn MigrationTrait>> {";
45    
46    let mut pos = content.find(search_pattern);
47    if pos.is_none() {
48        pos = content.find(search_pattern_alt);
49    }
50    
51    if let Some(_pos) = pos {
52        let insert_pos = content.find("        ]").unwrap_or(content.len());
53        content.insert_str(insert_pos, &format!("            Box::new({}::Migration),\n", mod_name));
54    }
55
56    fs::write(mod_path, content).expect("Gagal memperbarui database/migrations/mod.rs");
57    println!("{} {}", "📝".blue(), "database/migrations/mod.rs diperbarui.".dimmed());
58}
59
60pub async fn make_auth() {
61    println!("\n{}", "🔐 Scaffolding Authentication...".magenta().bold());
62
63    // 1. Create src/routes/auth.rs
64    let auth_route_path = "src/routes/auth.rs";
65    let auth_route_template = r#"use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};
66use crate::app::http::controllers::auth;
67use crate::app::http::middleware::auth::guest_middleware;
68use rustbasic_core::server::AppState;
69
70pub fn router() -> Router<AppState> {
71    Router::new()
72        .route("/login", get(auth::auth_controller::AuthController::login_page))
73        .route("/login", post(auth::auth_controller::AuthController::login))
74        .route("/register", get(auth::auth_controller::AuthController::register_page))
75        .route("/register", post(auth::auth_controller::AuthController::register))
76        .route("/forgot-password", get(auth::auth_controller::AuthController::forgot_password_page))
77        .route("/forgot-password", post(auth::auth_controller::AuthController::send_reset_link))
78        .route("/reset-password", get(auth::auth_controller::AuthController::reset_password_page))
79        .route("/reset-password", post(auth::auth_controller::AuthController::update_password))
80        .layer(from_fn(guest_middleware))
81}
82"#;
83    if !std::path::Path::new(auth_route_path).exists() {
84        fs::write(auth_route_path, auth_route_template).ok();
85        println!("   {} {}", "✅ Created:".green(), auth_route_path.cyan());
86    } else {
87        println!("   {} {}", "⚠️  Exists:".yellow(), auth_route_path.cyan());
88    }
89
90    // 2. Update src/routes/mod.rs
91    let routes_mod_path = "src/routes/mod.rs";
92    if let Ok(mut content) = fs::read_to_string(routes_mod_path)
93        && !content.contains("pub mod auth;") {
94            content.push_str("pub mod auth;\n");
95            fs::write(routes_mod_path, content).ok();
96            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
97        }
98
99    // 3. Update src/routes/web.rs
100    let web_route_path = "src/routes/web.rs";
101    if let Ok(mut content) = fs::read_to_string(web_route_path)
102        && !content.contains("use crate::routes::auth as auth_routes;") {
103            content = content.replace("use rustbasic_core::axum::{Router, routing::get};", "use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};");
104            content = content.replace("use rustbasic_core::server::AppState;", "use crate::app::http::controllers::{auth, dashboard_controller};\nuse crate::app::http::middleware::auth::auth_middleware;\nuse rustbasic_core::server::AppState;\nuse crate::routes::auth as auth_routes;");
105
106            let merge_logic = r#"let auth_protected_routes = Router::new()
107        .route("/dashboard", get(dashboard_controller::DashboardController::index))
108        .route("/logout", post(auth::auth_controller::AuthController::logout))
109        .layer(from_fn(auth_middleware));
110
111    Router::new()
112        .route("/", get(welcome_controller::index))
113        .route("/about", get(welcome_controller::about))
114        .route("/dev", get(welcome_controller::dev_info))
115        .merge(auth_routes::router())
116        .merge(auth_protected_routes)"#;
117
118            // Use regex for more robust replacement (includes leading spaces)
119            let re = Regex::new(r#"(?s)Router::new\(\s*\n\s*\.route\("/", get\(welcome_controller::index\)\)\s*\n\s*\.route\("/about", get\(welcome_controller::about\)\)\s*\n\s*\.route\("/dev", get\(welcome_controller::dev_info\)\)"#).unwrap();
120            if re.is_match(&content) {
121                content = re.replace(&content, merge_logic).to_string();
122            } else {
123                // Fallback for simple replacement
124                content = content.replace("Router::new()\n        .route(\"/\", get(welcome_controller::index))\n        .route(\"/about\", get(welcome_controller::about))\n        .route(\"/dev\", get(welcome_controller::dev_info))", merge_logic);
125            }
126            
127            fs::write(web_route_path, content).ok();
128            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
129        }
130
131    // 3.1 Create Password Resets Migration
132    let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
133    let migration_name = format!("m{}_create_password_resets_table", timestamp);
134    let migration_path = format!("database/migrations/{}.rs", migration_name);
135    
136    // Check if any password reset migration already exists
137    let mut exists = false;
138    if let Ok(entries) = std::fs::read_dir("database/migrations") {
139        for entry in entries.flatten() {
140            if let Some(name) = entry.file_name().to_str()
141                && name.ends_with("_create_password_resets_table.rs") {
142                    exists = true;
143                    println!("   {} {}", "⚠️  Exists:".yellow(), name.cyan());
144                    break;
145                }
146        }
147    }
148
149    if !exists {
150        let migration_template = r#"use sea_orm_migration::prelude::*;
151use async_trait::async_trait;
152
153#[derive(Iden)]
154enum PasswordResets {
155    Table,
156    Email,
157    Token,
158    CreatedAt,
159}
160
161#[derive(DeriveMigrationName)]
162pub struct Migration;
163
164#[async_trait]
165impl MigrationTrait for Migration {
166    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
167        manager
168            .create_table(
169                Table::create()
170                    .table(PasswordResets::Table)
171                    .if_not_exists()
172                    .col(ColumnDef::new(PasswordResets::Email).string().not_null().primary_key())
173                    .col(ColumnDef::new(PasswordResets::Token).string().not_null())
174                    .col(
175                        ColumnDef::new(PasswordResets::CreatedAt)
176                            .timestamp()
177                            .default(Expr::current_timestamp())
178                            .not_null(),
179                    )
180                    .to_owned(),
181            )
182            .await
183    }
184
185    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
186        manager
187            .drop_table(Table::drop().table(PasswordResets::Table).to_owned())
188            .await
189    }
190}
191"#.to_string();
192        fs::write(&migration_path, migration_template).ok();
193        
194        update_migration_mod_rs(&migration_name);
195        println!("   {} {}", "✅ Created:".green(), format!("Migration {}", migration_name).cyan());
196    }
197
198    // 4. Create Controller Folder & mod.rs
199    let auth_controller_dir = "src/app/http/controllers/auth";
200    fs::create_dir_all(auth_controller_dir).ok();
201    let auth_controller_mod = "src/app/http/controllers/auth/mod.rs";
202    if !std::path::Path::new(auth_controller_mod).exists() {
203        fs::write(auth_controller_mod, "pub mod auth_controller;").ok();
204    }
205    update_controller_mod_rs("auth");
206
207    // 4.1 Create Auth Middleware
208    let auth_middleware_dir = "src/app/http/middleware";
209    fs::create_dir_all(auth_middleware_dir).ok();
210    let auth_middleware_path = "src/app/http/middleware/auth.rs";
211    if !std::path::Path::new(auth_middleware_path).exists() {
212        let middleware_template = r#"use rustbasic_core::axum::{
213    middleware::Next,
214    response::{IntoResponse, Redirect},
215    extract::Request,
216};
217use rustbasic_core::session_manager::RustBasicSessionStore;
218use rustbasic_core::axum_session::Session;
219
220pub async fn auth_middleware(req: Request, next: Next) -> impl IntoResponse {
221    let session = req.extensions().get::<Session<RustBasicSessionStore>>().unwrap();
222    if session.get::<i32>("user_id").is_none() {
223        session.set("error", "Silakan login terlebih dahulu");
224        return Redirect::to("/login").into_response();
225    }
226    next.run(req).await
227}
228
229pub async fn guest_middleware(req: Request, next: Next) -> impl IntoResponse {
230    let session = req.extensions().get::<Session<RustBasicSessionStore>>().unwrap();
231    if session.get::<i32>("user_id").is_some() {
232        return Redirect::to("/dashboard").into_response();
233    }
234    next.run(req).await
235}
236"#;
237        fs::write(auth_middleware_path, middleware_template).ok();
238        
239        // Update src/app/http/middleware/mod.rs
240        let middleware_mod_path = "src/app/http/middleware/mod.rs";
241        if let Ok(mut content) = fs::read_to_string(middleware_mod_path)
242            && !content.contains("pub mod auth;") {
243                content.push_str("pub mod auth;\n");
244                fs::write(middleware_mod_path, content).ok();
245            }
246        println!("   {} {}", "✅ Created:".green(), auth_middleware_path.cyan());
247    }
248
249    // 4.1 Create Password Resets Model
250    let model_path = "src/app/models/password_resets.rs";
251    if !std::path::Path::new(model_path).exists() {
252        let model_template = r#"use rustbasic_core::sea_orm::entity::prelude::*;
253use rustbasic_core::serde::{Deserialize, Serialize};
254
255#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
256#[sea_orm(table_name = "password_resets")]
257pub struct Model {
258    #[sea_orm(primary_key, auto_increment = false)]
259    pub email: String,
260    pub token: String,
261    pub created_at: DateTime,
262}
263
264#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
265pub enum Relation {}
266
267impl ActiveModelBehavior for ActiveModel {}
268"#;
269        fs::write(model_path, model_template).ok();
270        
271        // Update src/app/models/mod.rs
272        let models_mod_path = "src/app/models/mod.rs";
273        if let Ok(mut content) = fs::read_to_string(models_mod_path)
274            && !content.contains("pub mod password_resets;") {
275                content.push_str("pub mod password_resets;\n");
276                fs::write(models_mod_path, content).ok();
277            }
278        println!("   {} {}", "✅ Created:".green(), "Model password_resets".cyan());
279    }
280
281    // 4.2 Create Auth Controller
282    let auth_controller_path = "src/app/http/controllers/auth/auth_controller.rs";
283    if !std::path::Path::new(auth_controller_path).exists() {
284        let controller_template = r#"/* ---------------------------------------------------------
285 * 📑 LABEL: AUTH CONTROLLER (auth/auth_controller.rs)
286 * Menangani pendaftaran, login, dan logout user.
287 * --------------------------------------------------------- */
288
289use crate::app::inertia::inertia;
290use crate::app::models::users;
291use rustbasic_core::requests::Request;
292use rustbasic_core::server::AppState;
293use rustbasic_core::axum::{response::{IntoResponse, Response, Redirect}, extract::State};
294use rustbasic_core::bcrypt::{hash, verify, DEFAULT_COST};
295use rustbasic_core::uuid::Uuid;
296use rustbasic_core::serde::Deserialize;
297use rustbasic_core::validator::Validate;
298use rustbasic_core::mail::MailService;
299use rustbasic_core::sea_orm::{EntityTrait, ColumnTrait, QueryFilter, Set};
300use rustbasic_core::serde_json::json;
301
302#[derive(Deserialize, Validate)]
303pub struct RegisterRequest {
304    #[validate(length(min = 3, message = "Nama minimal 3 karakter"))]
305    pub name: String,
306    
307    #[validate(email(message = "Format email tidak valid"))]
308    pub email: String,
309    
310    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
311    pub password: String,
312}
313
314#[derive(Deserialize, Validate)]
315pub struct LoginRequest {
316    #[validate(email(message = "Format email tidak valid"))]
317    pub email: String,
318    pub password: String,
319    pub remember: Option<bool>,
320}
321
322#[derive(Deserialize, Validate)]
323pub struct ForgotPasswordRequest {
324    #[validate(email(message = "Format email tidak valid"))]
325    pub email: String,
326}
327
328#[derive(Deserialize, Validate)]
329pub struct ResetPasswordRequest {
330    pub token: String,
331    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
332    pub password: String,
333}
334
335pub struct AuthController;
336
337impl AuthController {
338    /// Menampilkan halaman login
339    pub async fn login_page(req: Request) -> Response {
340        inertia(&req, "Auth/Login", json!({ "title": "Login" }))
341    }
342
343    /// Menampilkan halaman register
344    pub async fn register_page(req: Request) -> Response {
345        inertia(&req, "Auth/Register", json!({ "title": "Daftar Akun" }))
346    }
347
348    /// Proses Pendaftaran
349    pub async fn register(State(state): State<AppState>, req: Request) -> impl IntoResponse {
350        // 1. Validasi Input
351        let data = match req.validate::<RegisterRequest>() {
352            Ok(d) => d,
353            Err(_) => return Redirect::to("/register").into_response(),
354        };
355
356        // 2. Cek apakah email sudah terdaftar
357        let existing = users::Entity::find()
358            .filter(users::Column::Email.eq(&data.email))
359            .one(&state.db)
360            .await
361            .ok()
362            .flatten();
363
364        if existing.is_some() {
365            req.session.set("error", "Email sudah terdaftar");
366            return Redirect::to("/register").into_response();
367        }
368
369        // 3. Hash Password
370        let hashed = hash(data.password, DEFAULT_COST).unwrap();
371
372        // 4. Simpan ke Database
373        let new_user = users::ActiveModel {
374            name: Set(data.name),
375            email: Set(data.email),
376            password: Set(hashed),
377            ..Default::default()
378        };
379
380        if let Err(e) = users::Entity::insert(new_user).exec(&state.db).await {
381            rustbasic_core::tracing::error!("Gagal menyimpan user: {}", e);
382            req.session.set("error", "Gagal mendaftar, coba lagi.");
383            return Redirect::to("/register").into_response();
384        }
385
386        req.session.set("success", "Pendaftaran berhasil! Silakan login.");
387        Redirect::to("/login").into_response()
388    }
389
390    /// Proses Login
391    pub async fn login(State(state): State<AppState>, req: Request) -> impl IntoResponse {
392        // 1. Validasi Input
393        let data = match req.validate::<LoginRequest>() {
394            Ok(d) => d,
395            Err(_) => return Redirect::to("/login").into_response(),
396        };
397
398        // 2. Ambil User dari DB
399        let user = users::Entity::find()
400            .filter(users::Column::Email.eq(&data.email))
401            .one(&state.db)
402            .await
403            .ok()
404            .flatten();
405
406        if let Some(u) = user {
407            // 3. Verifikasi Password
408            if verify(data.password, &u.password).unwrap_or(false) {
409                // 4. Set Session
410                req.session.set("user_id", u.id);
411                req.session.set("success", "Selamat datang kembali!");
412                return Redirect::to("/dashboard").into_response();
413            }
414        }
415
416        req.session.set("error", "Email atau password salah");
417        Redirect::to("/login").into_response()
418    }
419
420    /// Menampilkan halaman lupa password
421    pub async fn forgot_password_page(req: Request) -> Response {
422        inertia(&req, "Auth/ForgotPassword", json!({ "title": "Lupa Password" }))
423    }
424
425    /// Kirim link reset password
426    pub async fn send_reset_link(State(state): State<AppState>, req: Request) -> impl IntoResponse {
427        let data = match req.validate::<ForgotPasswordRequest>() {
428            Ok(d) => d,
429            Err(_) => return Redirect::to("/forgot-password").into_response(),
430        };
431
432        // 1. Cek apakah user ada
433        let user = users::Entity::find()
434            .filter(users::Column::Email.eq(&data.email))
435            .one(&state.db)
436            .await
437            .ok()
438            .flatten();
439
440        if let Some(u) = user {
441            // 2. Generate Token
442            let token = Uuid::new_v4().to_string();
443
444            // 3. Simpan Token
445            let reset = crate::app::models::password_resets::ActiveModel {
446                email: Set(u.email.clone()),
447                token: Set(token.clone()),
448                created_at: Set(rustbasic_core::chrono::Utc::now().naive_utc()),
449            };
450
451            let _ = crate::app::models::password_resets::Entity::insert(reset)
452                .on_conflict(
453                    rustbasic_core::sea_orm::sea_query::OnConflict::column(crate::app::models::password_resets::Column::Email)
454                        .update_column(crate::app::models::password_resets::Column::Token)
455                        .update_column(crate::app::models::password_resets::Column::CreatedAt)
456                        .to_owned()
457                )
458                .exec(&state.db)
459                .await;
460
461            // 4. Kirim Email (Gunakan Config::load().mail_*)
462            let config = rustbasic_core::Config::load();
463            let app_name = std::env::var("APP_NAME").unwrap_or_else(|_| "RustBasic".to_string());
464            let reset_url = format!("{}/reset-password?token={}", config.app_url, token);
465
466            let subject = format!("Reset Password - {}", app_name);
467            let body = rustbasic_core::view::render_to_string("emails/reset.rb.html", rustbasic_core::minijinja::context! {
468                app_name => app_name,
469                reset_url => reset_url,
470            });
471
472            if let Err(e) = MailService::send_email(&u.email, &subject, &body).await {
473                rustbasic_core::tracing::error!("Gagal mengirim email reset: {}", e);
474            }
475
476            rustbasic_core::tracing::info!("Reset link for {}: {}", u.email, reset_url);
477        }
478
479        req.session.set("success", "Jika email terdaftar, link reset password akan dikirim.");
480        Redirect::to("/login").into_response()
481    }
482
483    /// Menampilkan halaman reset password
484    pub async fn reset_password_page(req: Request) -> Response {
485        let token = req.input_as_str("token").unwrap_or_default();
486        inertia(&req, "Auth/ResetPassword", json!({ "title": "Reset Password", "token": token }))
487    }
488
489    /// Proses update password baru
490    pub async fn update_password(State(state): State<AppState>, req: Request) -> impl IntoResponse {
491        let data = match req.validate::<ResetPasswordRequest>() {
492            Ok(d) => d,
493            Err(_) => return Redirect::to("/login").into_response(),
494        };
495
496        // 1. Cari Token
497        let reset = crate::app::models::password_resets::Entity::find()
498            .filter(crate::app::models::password_resets::Column::Token.eq(&data.token))
499            .one(&state.db)
500            .await
501            .ok()
502            .flatten();
503
504        if let Some(r) = reset {
505            // 2. Cek Kadaluarsa (60 Menit)
506            let now = rustbasic_core::chrono::Utc::now().naive_utc();
507            let duration = now.signed_duration_since(r.created_at);
508            
509            if duration.num_minutes() > 60 {
510                // Hapus token yang sudah kadaluarsa
511                let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email.clone())
512                    .exec(&state.db)
513                    .await;
514                    
515                req.session.set("error", "Tautan reset password sudah kadaluarsa (melebihi 60 menit).");
516                return Redirect::to("/login").into_response();
517            }
518
519            // 3. Hash Password Baru
520            let hashed = rustbasic_core::bcrypt::hash(data.password, rustbasic_core::bcrypt::DEFAULT_COST).unwrap();
521
522            // 4. Update User
523            let _ = users::Entity::update_many()
524                .col_expr(users::Column::Password, rustbasic_core::sea_orm::sea_query::Expr::value(hashed))
525                .filter(users::Column::Email.eq(&r.email))
526                .exec(&state.db)
527                .await;
528
529            // 5. Hapus Token
530            let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email)
531                .exec(&state.db)
532                .await;
533
534            req.session.set("success", "Password berhasil diubah. Silakan login.");
535            return Redirect::to("/login").into_response();
536        }
537
538        req.session.set("error", "Token tidak valid atau sudah kadaluarsa.");
539        Redirect::to("/login").into_response()
540    }
541
542    /// Proses Logout
543    pub async fn logout(req: Request) -> impl IntoResponse {
544        req.session.remove("user_id");
545        req.session.set("success", "Anda telah keluar.");
546        Redirect::to("/").into_response()
547    }
548}
549"#;
550        fs::write(auth_controller_path, controller_template).ok();
551        println!("   {} {}", "✅ Created:".green(), auth_controller_path.cyan());
552    }
553
554    // 5. Views (React Components in resources/js/Pages)
555    let auth_page_dir = "src/resources/js/Pages/Auth";
556    fs::create_dir_all(auth_page_dir).ok();
557
558    // 5.0 Create Components dir & shared components
559    let components_dir = "src/resources/js/Components";
560    fs::create_dir_all(components_dir).ok();
561
562    let toast_template = r##"import React, { useState, useEffect, useCallback } from 'react';
563
564const ICONS = {
565  success: (
566    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
567      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
568      <path d="M6 10.5L8.5 13L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
569    </svg>
570  ),
571  error: (
572    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
573      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
574      <path d="M7 7L13 13M13 7L7 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
575    </svg>
576  ),
577  warning: (
578    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
579      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
580      <path d="M10 6V11" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
581      <circle cx="10" cy="14" r="1" fill="currentColor" />
582    </svg>
583  ),
584  info: (
585    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
586      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
587      <path d="M10 9V14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
588      <circle cx="10" cy="6.5" r="1" fill="currentColor" />
589    </svg>
590  ),
591};
592
593const STYLES = {
594  success: {
595    bg: 'rgba(16, 185, 129, 0.08)',
596    border: 'rgba(16, 185, 129, 0.25)',
597    color: '#34d399',
598    progress: '#10b981',
599    shadow: '0 8px 32px rgba(16, 185, 129, 0.15)',
600  },
601  error: {
602    bg: 'rgba(244, 63, 94, 0.08)',
603    border: 'rgba(244, 63, 94, 0.25)',
604    color: '#fb7185',
605    progress: '#f43f5e',
606    shadow: '0 8px 32px rgba(244, 63, 94, 0.15)',
607  },
608  warning: {
609    bg: 'rgba(245, 158, 11, 0.08)',
610    border: 'rgba(245, 158, 11, 0.25)',
611    color: '#fbbf24',
612    progress: '#f59e0b',
613    shadow: '0 8px 32px rgba(245, 158, 11, 0.15)',
614  },
615  info: {
616    bg: 'rgba(99, 102, 241, 0.08)',
617    border: 'rgba(99, 102, 241, 0.25)',
618    color: '#818cf8',
619    progress: '#6366f1',
620    shadow: '0 8px 32px rgba(99, 102, 241, 0.15)',
621  },
622};
623
624function SingleToast({ id, type, message, duration = 5000, onDismiss }) {
625  const [isVisible, setIsVisible] = useState(false);
626  const [isLeaving, setIsLeaving] = useState(false);
627  const [progress, setProgress] = useState(100);
628
629  const style = STYLES[type] || STYLES.info;
630
631  const dismiss = useCallback(() => {
632    setIsLeaving(true);
633    setTimeout(() => onDismiss(id), 350);
634  }, [id, onDismiss]);
635
636  useEffect(() => {
637    const enterTimer = setTimeout(() => setIsVisible(true), 10);
638    const dismissTimer = setTimeout(() => dismiss(), duration);
639    const startTime = Date.now();
640    const progressInterval = setInterval(() => {
641      const elapsed = Date.now() - startTime;
642      const remaining = Math.max(0, 100 - (elapsed / duration) * 100);
643      setProgress(remaining);
644      if (remaining <= 0) clearInterval(progressInterval);
645    }, 30);
646
647    return () => {
648      clearTimeout(enterTimer);
649      clearTimeout(dismissTimer);
650      clearInterval(progressInterval);
651    };
652  }, [duration, dismiss]);
653
654  return (
655    <div
656      style={{
657        background: style.bg,
658        border: `1px solid ${style.border}`,
659        borderRadius: '16px',
660        padding: '14px 18px',
661        marginBottom: '10px',
662        display: 'flex',
663        alignItems: 'flex-start',
664        gap: '12px',
665        color: style.color,
666        backdropFilter: 'blur(20px)',
667        boxShadow: style.shadow,
668        transform: isVisible && !isLeaving
669          ? 'translateX(0) scale(1)'
670          : isLeaving
671            ? 'translateX(120%) scale(0.9)'
672            : 'translateX(120%) scale(0.9)',
673        opacity: isVisible && !isLeaving ? 1 : 0,
674        transition: 'all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
675        position: 'relative',
676        overflow: 'hidden',
677        minWidth: '320px',
678        maxWidth: '420px',
679        cursor: 'pointer',
680      }}
681      onClick={dismiss}
682      role="alert"
683    >
684      <div style={{ flexShrink: 0, marginTop: '1px' }}>{ICONS[type] || ICONS.info}</div>
685      <div style={{ flex: 1, minWidth: 0 }}>
686        <div style={{
687          fontSize: '11px',
688          fontWeight: 700,
689          textTransform: 'uppercase',
690          letterSpacing: '0.08em',
691          opacity: 0.7,
692          marginBottom: '2px',
693        }}>
694          {type === 'success' && 'Berhasil'}
695          {type === 'error' && 'Kesalahan'}
696          {type === 'warning' && 'Peringatan'}
697          {type === 'info' && 'Informasi'}
698        </div>
699        <div style={{
700          fontSize: '13px',
701          fontWeight: 500,
702          color: '#e2e8f0',
703          lineHeight: 1.5,
704          wordBreak: 'break-word',
705        }}>{message}</div>
706      </div>
707      <button
708        onClick={(e) => { e.stopPropagation(); dismiss(); }}
709        style={{
710          background: 'none',
711          border: 'none',
712          color: style.color,
713          cursor: 'pointer',
714          padding: '2px',
715          opacity: 0.5,
716          transition: 'opacity 0.2s',
717          flexShrink: 0,
718          marginTop: '1px',
719        }}
720      >
721722      </button>
723      <div style={{
724        position: 'absolute',
725        bottom: 0,
726        left: 0,
727        right: 0,
728        height: '3px',
729        background: `${style.progress}15`,
730        borderRadius: '0 0 16px 16px',
731        overflow: 'hidden',
732      }}>
733        <div style={{
734          width: `${progress}%`,
735          height: '100%',
736          background: `linear-gradient(90deg, ${style.progress}, ${style.progress}aa)`,
737          transition: 'width 0.1s linear',
738          borderRadius: '0 0 16px 16px',
739        }} />
740      </div>
741    </div>
742  );
743}
744
745export default function Toast({ flash, duration = 5000, position = 'top-right' }) {
746  const [toasts, setToasts] = useState([]);
747
748  useEffect(() => {
749    if (!flash) return;
750    const newToasts = [];
751    if (flash.success) newToasts.push({ id: Date.now() + '_s', type: 'success', message: flash.success });
752    if (flash.error) newToasts.push({ id: Date.now() + '_e', type: 'error', message: flash.error });
753    if (flash.warning) newToasts.push({ id: Date.now() + '_w', type: 'warning', message: flash.warning });
754    if (flash.info) newToasts.push({ id: Date.now() + '_i', type: 'info', message: flash.info });
755
756    if (newToasts.length > 0) {
757      setToasts(prev => [...prev, ...newToasts]);
758    }
759  }, [flash?.success, flash?.error, flash?.warning, flash?.info]);
760
761  const handleDismiss = useCallback((id) => {
762    setToasts(prev => prev.filter(t => t.id !== id));
763  }, []);
764
765  const positionStyle = {
766    'top-right': { top: '24px', right: '24px' },
767    'top-left': { top: '24px', left: '24px' },
768    'bottom-right': { bottom: '24px', right: '24px' },
769    'bottom-left': { bottom: '24px', left: '24px' },
770  };
771
772  if (toasts.length === 0) return null;
773
774  return (
775    <div style={{ position: 'fixed', zIndex: 99999, pointerEvents: 'none', ...positionStyle[position] }}>
776      <div style={{ pointerEvents: 'auto' }}>
777        {toasts.map(toast => (
778          <SingleToast
779            key={toast.id}
780            id={toast.id}
781            type={toast.type}
782            message={toast.message}
783            duration={duration}
784            onDismiss={handleDismiss}
785          />
786        ))}
787      </div>
788    </div>
789  );
790}
791"##;
792
793    let alert_banner_template = r##"import React from 'react';
794
795const ALERT_STYLES = {
796  success: {
797    bg: 'rgba(16, 185, 129, 0.08)',
798    border: 'rgba(16, 185, 129, 0.2)',
799    color: '#34d399',
800    icon: '✅',
801  },
802  error: {
803    bg: 'rgba(244, 63, 94, 0.08)',
804    border: 'rgba(244, 63, 94, 0.2)',
805    color: '#fb7185',
806    icon: '❌',
807  },
808  warning: {
809    bg: 'rgba(245, 158, 11, 0.08)',
810    border: 'rgba(245, 158, 11, 0.2)',
811    color: '#fbbf24',
812    icon: '⚠️',
813  },
814  info: {
815    bg: 'rgba(99, 102, 241, 0.08)',
816    border: 'rgba(99, 102, 241, 0.2)',
817    color: '#818cf8',
818    icon: 'ℹ️',
819  },
820};
821
822export default function AlertBanner({ type = 'info', message, onDismiss }) {
823  if (!message) return null;
824  const style = ALERT_STYLES[type] || ALERT_STYLES.info;
825
826  return (
827    <div
828      role="alert"
829      style={{
830        background: style.bg,
831        border: `1px solid ${style.border}`,
832        borderRadius: '14px',
833        padding: '14px 18px',
834        marginBottom: '20px',
835        display: 'flex',
836        alignItems: 'center',
837        gap: '10px',
838        animation: 'alertSlideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
839      }}
840    >
841      <span style={{ fontSize: '16px', flexShrink: 0 }}>{style.icon}</span>
842      <span style={{ flex: 1, fontSize: '13px', fontWeight: 600, color: style.color, lineHeight: 1.5 }}>{message}</span>
843      {onDismiss && (
844        <button
845          onClick={onDismiss}
846          style={{
847            background: 'none',
848            border: 'none',
849            color: style.color,
850            cursor: 'pointer',
851            padding: '2px 4px',
852            opacity: 0.6,
853            transition: 'opacity 0.2s',
854            fontSize: '14px',
855          }}
856        >
857858        </button>
859      )}
860      <style>{`
861        @keyframes alertSlideIn {
862          from { opacity: 0; transform: translateY(-8px) scale(0.97); }
863          to { opacity: 1; transform: translateY(0) scale(1); }
864        }
865      `}</style>
866    </div>
867  );
868}
869"##;
870
871    let form_input_template = r##"import React from 'react';
872
873export default function FormInput({
874  label,
875  type = 'text',
876  value,
877  onChange,
878  error,
879  placeholder,
880  required = false,
881  autoFocus = false,
882  disabled = false,
883}) {
884  const hasError = !!error;
885
886  return (
887    <div>
888      {label && (
889        <label style={{
890          display: 'block',
891          fontSize: '11px',
892          fontWeight: 700,
893          textTransform: 'uppercase',
894          letterSpacing: '0.08em',
895          marginBottom: '8px',
896          color: hasError ? '#fb7185' : '#94a3b8',
897          transition: 'color 0.3s ease',
898        }}>{label}</label>
899      )}
900      <input
901        type={type}
902        value={value}
903        onChange={onChange}
904        placeholder={placeholder}
905        required={required}
906        autoFocus={autoFocus}
907        disabled={disabled}
908        style={{
909          width: '100%',
910          boxSizing: 'border-box',
911          background: 'rgba(2, 6, 23, 0.8)',
912          border: `1px solid ${hasError ? 'rgba(244, 63, 94, 0.5)' : 'rgba(30, 41, 59, 1)'}`,
913          borderRadius: '12px',
914          padding: '12px 14px',
915          fontSize: '14px',
916          color: '#ffffff',
917          outline: 'none',
918          transition: 'all 0.3s ease',
919          opacity: disabled ? 0.5 : 1,
920        }}
921        onFocus={(e) => {
922          e.target.style.borderColor = hasError ? 'rgba(244, 63, 94, 0.7)' : 'rgba(99, 102, 241, 0.5)';
923          e.target.style.boxShadow = hasError ? '0 0 0 3px rgba(244, 63, 94, 0.1)' : '0 0 0 3px rgba(99, 102, 241, 0.1)';
924        }}
925        onBlur={(e) => {
926          e.target.style.borderColor = hasError ? 'rgba(244, 63, 94, 0.5)' : 'rgba(30, 41, 59, 1)';
927          e.target.style.boxShadow = 'none';
928        }}
929      />
930      {hasError && (
931        <div style={{
932          display: 'flex',
933          alignItems: 'center',
934          gap: '5px',
935          marginTop: '6px',
936          animation: 'errorShake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97)',
937        }}>
938          <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ flexShrink: 0 }}>
939            <circle cx="6" cy="6" r="6" fill="rgba(244, 63, 94, 0.15)" />
940            <path d="M6 3.5V6.5" stroke="#fb7185" strokeWidth="1.2" strokeLinecap="round" />
941            <circle cx="6" cy="8.2" r="0.6" fill="#fb7185" />
942          </svg>
943          <span style={{ fontSize: '12px', fontWeight: 600, color: '#fb7185', lineHeight: 1.3 }}>{error}</span>
944        </div>
945      )}
946      <style>{`
947        @keyframes errorShake {
948          0%, 100% { transform: translateX(0); }
949          20% { transform: translateX(-4px); }
950          40% { transform: translateX(4px); }
951          60% { transform: translateX(-2px); }
952          80% { transform: translateX(2px); }
953        }
954      `}</style>
955    </div>
956  );
957}
958"##;
959
960    let login_template = r##"import React from 'react';
961import { Link, useForm, usePage } from '@inertiajs/react';
962import Toast from '../../Components/Toast';
963import AlertBanner from '../../Components/AlertBanner';
964import FormInput from '../../Components/FormInput';
965
966export default function Login() {
967  const { flash } = usePage().props;
968  const { data, setData, post, processing, errors } = useForm({
969    email: '',
970    password: '',
971    remember: false,
972  });
973
974  const handleSubmit = (e) => {
975    e.preventDefault();
976    post('/login');
977  };
978
979  return (
980    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
981      <Toast flash={flash} />
982
983      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
984        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
985        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
986        
987        <div className="text-center mb-8">
988          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
989            RustBasic SPA
990          </span>
991          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Selamat Datang</h1>
992          <p className="text-slate-400 text-sm mt-2">Silakan masuk ke akun Anda</p>
993        </div>
994
995        {flash?.success && <AlertBanner type="success" message={flash.success} />}
996        {flash?.error && <AlertBanner type="error" message={flash.error} />}
997
998        <form onSubmit={handleSubmit} className="space-y-5">
999          <FormInput
1000            label="Email Address"
1001            type="email"
1002            value={data.email}
1003            onChange={(e) => setData('email', e.target.value)}
1004            error={errors.email}
1005            placeholder="nama@email.com"
1006            required
1007          />
1008
1009          <FormInput
1010            label="Password"
1011            type="password"
1012            value={data.password}
1013            onChange={(e) => setData('password', e.target.value)}
1014            error={errors.password}
1015            placeholder="••••••••"
1016            required
1017          />
1018
1019          <div className="flex items-center justify-between text-sm">
1020            <label className="flex items-center space-x-2 text-slate-400 cursor-pointer">
1021              <input
1022                type="checkbox"
1023                checked={data.remember}
1024                onChange={(e) => setData('remember', e.target.checked)}
1025                className="w-4 h-4 rounded border-slate-800 bg-slate-950 text-indigo-600 focus:ring-indigo-500 focus:ring-opacity-25"
1026              />
1027              <span className="select-none">Ingat Saya</span>
1028            </label>
1029            <Link href="/forgot-password" className="text-indigo-400 hover:text-indigo-300 font-semibold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1030              Lupa Password?
1031            </Link>
1032          </div>
1033
1034          <button
1035            type="submit"
1036            disabled={processing}
1037            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1038          >
1039            {processing ? 'MEMROSES...' : 'MASUK KE DASHBOARD'}
1040          </button>
1041        </form>
1042
1043        <p className="text-center text-sm text-slate-500 mt-8">
1044          Belum punya akun?{' '}
1045          <Link href="/register" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1046            Daftar Sekarang
1047          </Link>
1048        </p>
1049      </div>
1050    </div>
1051  );
1052}
1053"##;
1054
1055    let register_template = r##"import React from 'react';
1056import { Link, useForm, usePage } from '@inertiajs/react';
1057import Toast from '../../Components/Toast';
1058import AlertBanner from '../../Components/AlertBanner';
1059import FormInput from '../../Components/FormInput';
1060
1061export default function Register() {
1062  const { flash } = usePage().props;
1063  const { data, setData, post, processing, errors } = useForm({
1064    name: '',
1065    email: '',
1066    password: '',
1067  });
1068
1069  const handleSubmit = (e) => {
1070    e.preventDefault();
1071    post('/register');
1072  };
1073
1074  return (
1075    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1076      <Toast flash={flash} />
1077
1078      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1079        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1080        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1081        
1082        <div className="text-center mb-8">
1083          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1084            RustBasic SPA
1085          </span>
1086          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Daftar Akun</h1>
1087          <p className="text-slate-400 text-sm mt-2">Mulai perjalanan Anda bersama kami</p>
1088        </div>
1089
1090        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1091        {flash?.success && <AlertBanner type="success" message={flash.success} />}
1092
1093        <form onSubmit={handleSubmit} className="space-y-5">
1094          <FormInput
1095            label="Nama Lengkap"
1096            type="text"
1097            value={data.name}
1098            onChange={(e) => setData('name', e.target.value)}
1099            error={errors.name}
1100            placeholder="Nama Lengkap Anda"
1101            required
1102          />
1103
1104          <FormInput
1105            label="Email Address"
1106            type="email"
1107            value={data.email}
1108            onChange={(e) => setData('email', e.target.value)}
1109            error={errors.email}
1110            placeholder="nama@email.com"
1111            required
1112          />
1113
1114          <FormInput
1115            label="Password"
1116            type="password"
1117            value={data.password}
1118            onChange={(e) => setData('password', e.target.value)}
1119            error={errors.password}
1120            placeholder="Min. 8 karakter"
1121            required
1122          />
1123
1124          <button
1125            type="submit"
1126            disabled={processing}
1127            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1128          >
1129            {processing ? 'MENDAFTAR...' : 'BUAT AKUN SEKARANG'}
1130          </button>
1131        </form>
1132
1133        <p className="text-center text-sm text-slate-500 mt-8">
1134          Sudah punya akun?{' '}
1135          <Link href="/login" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1136            Login Disini
1137          </Link>
1138        </p>
1139      </div>
1140    </div>
1141  );
1142}
1143"##;
1144
1145    let forgot_template = r##"import React from 'react';
1146import { Link, useForm, usePage } from '@inertiajs/react';
1147import Toast from '../../Components/Toast';
1148import AlertBanner from '../../Components/AlertBanner';
1149import FormInput from '../../Components/FormInput';
1150
1151export default function ForgotPassword() {
1152  const { flash } = usePage().props;
1153  const { data, setData, post, processing, errors } = useForm({
1154    email: '',
1155  });
1156
1157  const handleSubmit = (e) => {
1158    e.preventDefault();
1159    post('/forgot-password');
1160  };
1161
1162  return (
1163    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1164      <Toast flash={flash} />
1165
1166      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1167        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1168        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1169        
1170        <div className="text-center mb-8">
1171          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1172            Keamanan Akun
1173          </span>
1174          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Lupa Password</h1>
1175          <p className="text-slate-400 text-sm mt-2">Kami akan mengirimkan instruksi ke email Anda</p>
1176        </div>
1177
1178        {flash?.success && <AlertBanner type="success" message={flash.success} />}
1179        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1180
1181        <form onSubmit={handleSubmit} className="space-y-5">
1182          <FormInput
1183            label="Email Address"
1184            type="email"
1185            value={data.email}
1186            onChange={(e) => setData('email', e.target.value)}
1187            error={errors.email}
1188            placeholder="nama@email.com"
1189            required
1190            autoFocus
1191          />
1192
1193          <button
1194            type="submit"
1195            disabled={processing}
1196            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1197          >
1198            {processing ? 'MENGIRIM...' : 'KIRIM LINK RESET PASSWORD'}
1199          </button>
1200        </form>
1201
1202        <p className="text-center text-sm text-slate-500 mt-8">
1203          Ingat password Anda?{' '}
1204          <Link href="/login" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1205            Login Disini
1206          </Link>
1207        </p>
1208      </div>
1209    </div>
1210  );
1211}
1212"##;
1213
1214    let toast_view = "src/resources/js/Components/Toast.jsx";
1215    if !std::path::Path::new(toast_view).exists() {
1216        fs::write(toast_view, toast_template).ok();
1217    }
1218
1219    let alert_banner_view = "src/resources/js/Components/AlertBanner.jsx";
1220    if !std::path::Path::new(alert_banner_view).exists() {
1221        fs::write(alert_banner_view, alert_banner_template).ok();
1222    }
1223
1224    let form_input_view = "src/resources/js/Components/FormInput.jsx";
1225    if !std::path::Path::new(form_input_view).exists() {
1226        fs::write(form_input_view, form_input_template).ok();
1227    }
1228
1229    let login_view = "src/resources/js/Pages/Auth/Login.jsx";
1230    if !std::path::Path::new(login_view).exists() {
1231        fs::write(login_view, login_template).ok();
1232    }
1233    
1234    let register_view = "src/resources/js/Pages/Auth/Register.jsx";
1235    if !std::path::Path::new(register_view).exists() {
1236        fs::write(register_view, register_template).ok();
1237    }
1238
1239    let forgot_view = "src/resources/js/Pages/Auth/ForgotPassword.jsx";
1240    if !std::path::Path::new(forgot_view).exists() {
1241        fs::write(forgot_view, forgot_template).ok();
1242    }
1243
1244    let reset_view = "src/resources/js/Pages/Auth/ResetPassword.jsx";
1245    if !std::path::Path::new(reset_view).exists() {
1246        let reset_template = r##"import React from 'react';
1247import { useForm, usePage } from '@inertiajs/react';
1248import Toast from '../../Components/Toast';
1249import AlertBanner from '../../Components/AlertBanner';
1250import FormInput from '../../Components/FormInput';
1251
1252export default function ResetPassword({ token }) {
1253  const { flash } = usePage().props;
1254  const { data, setData, post, processing, errors } = useForm({
1255    token: token || '',
1256    password: '',
1257  });
1258
1259  const handleSubmit = (e) => {
1260    e.preventDefault();
1261    post('/reset-password');
1262  };
1263
1264  return (
1265    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1266      <Toast flash={flash} />
1267
1268      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1269        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1270        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1271        
1272        <div className="text-center mb-8">
1273          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1274            Akses Akun
1275          </span>
1276          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Reset Password</h1>
1277          <p className="text-slate-400 text-sm mt-2">Silakan masukkan password baru Anda</p>
1278        </div>
1279
1280        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1281
1282        <form onSubmit={handleSubmit} className="space-y-5">
1283          <input type="hidden" value={data.token} />
1284
1285          <FormInput
1286            label="Password Baru"
1287            type="password"
1288            value={data.password}
1289            onChange={(e) => setData('password', e.target.value)}
1290            error={errors.password}
1291            placeholder="Minimal 8 karakter"
1292            required
1293            autoFocus
1294          />
1295
1296          <button
1297            type="submit"
1298            disabled={processing}
1299            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1300          >
1301            {processing ? 'MENYIMPAN...' : 'SIMPAN PASSWORD BARU'}
1302          </button>
1303        </form>
1304      </div>
1305    </div>
1306  );
1307}
1308"##;
1309        fs::write(reset_view, reset_template).ok();
1310    }
1311
1312    // 5.1 Create Email Reset Template (Minijinja string processing is still perfect here)
1313    let email_reset_view = "src/resources/views/emails/reset.rb.html";
1314    if !std::path::Path::new(email_reset_view).exists() {
1315        fs::create_dir_all("src/resources/views/emails").ok();
1316        let email_reset_template = r##"<!DOCTYPE html>
1317<html>
1318<head>
1319    <meta charset="utf-8">
1320    <style>
1321        body { font-family: 'Inter', -apple-system, sans-serif; line-height: 1.6; color: #1a1a1a; margin: 0; padding: 0; }
1322        .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; }
1323        .card { background: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
1324        .header { background: linear-gradient(135deg, #6366f1, #a855f7); padding: 40px; text-align: center; color: white; }
1325        .content { padding: 40px; }
1326        .button { display: inline-block; padding: 14px 32px; background: #6366f1; color: #ffffff !important; text-decoration: none; border-radius: 8px; font-weight: 600; margin: 24px 0; }
1327        .footer { padding: 24px; text-align: center; font-size: 13px; color: #6b7280; }
1328        h1 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -0.025em; }
1329        p { margin: 16px 0; color: #4b5563; }
1330        .divider { height: 1px; background: #f3f4f6; margin: 24px 0; }
1331    </style>
1332</head>
1333<body>
1334    <div class="container">
1335        <div class="card">
1336            <div class="header">
1337                <h1>{{ app_name }}</h1>
1338            </div>
1339            <div class="content">
1340                <h2 style="margin: 0; color: #111827; font-size: 20px;">Halo!</h2>
1341                <p>Anda menerima email ini karena kami menerima permintaan reset password untuk akun Anda di <strong>{{ app_name }}</strong>.</p>
1342                
1343                <div style="text-align: center;">
1344                    <a href="{{ reset_url }}" class="button">Reset Password Saya</a>
1345                </div>
1346
1347                <p style="font-size: 14px; color: #9ca3af;">Link ini akan kadaluarsa dalam 60 menit. Jika Anda tidak merasa meminta reset password, abaikan saja email ini.</p>
1348                
1349                <div class="divider"></div>
1350                
1351                <p style="font-size: 12px; color: #9ca3af;">
1352                    Jika Anda kesulitan menekan tombol, salin dan tempel URL berikut ke browser Anda:<br>
1353                    <span style="word-break: break-all; color: #6366f1;">{{ reset_url }}</span>
1354                </p>
1355            </div>
1356        </div>
1357        <div class="footer">
1358            &copy; 2026 {{ app_name }}. All rights reserved.
1359        </div>
1360    </div>
1361</body>
1362</html>
1363"##;
1364        fs::write(email_reset_view, email_reset_template).ok();
1365    }
1366
1367    // 5.2 Create Dashboard Page in React
1368    let dashboard_view = "src/resources/js/Pages/Dashboard.jsx";
1369    if !std::path::Path::new(dashboard_view).exists() {
1370        let dashboard_template = r##"import React from 'react';
1371import { Link, router, usePage } from '@inertiajs/react';
1372import Toast from '../Components/Toast';
1373
1374export default function Dashboard({ title, userName, userEmail, totalUsers }) {
1375  const { flash } = usePage().props;
1376
1377  const handleLogout = (e) => {
1378    e.preventDefault();
1379    router.post('/logout');
1380  };
1381
1382  return (
1383    <div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col md:flex-row font-sans">
1384      {/* Sidebar */}
1385      <aside className="w-full md:w-80 bg-slate-900 border-b md:border-b-0 md:border-r border-slate-800/80 p-6 flex flex-col justify-between relative overflow-hidden">
1386        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/5 rounded-full blur-3xl pointer-events-none" />
1387        
1388        <div>
1389          {/* Logo */}
1390          <div className="flex items-center space-x-3 mb-10">
1391            <div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center font-extrabold text-white text-lg shadow-lg shadow-indigo-600/30">
1392              R
1393            </div>
1394            <span className="text-xl font-extrabold text-white tracking-tight">RustBasic</span>
1395          </div>
1396
1397          {/* User Profile Info Card */}
1398          <div className="bg-slate-950/60 border border-slate-800/50 rounded-2xl p-4 mb-8">
1399            <div className="flex items-center space-x-3">
1400              <div className="w-12 h-12 bg-gradient-to-tr from-indigo-500 to-purple-500 rounded-full flex items-center justify-center font-extrabold text-white text-lg">
1401                {userName ? userName[0].toUpperCase() : 'G'}
1402              </div>
1403              <div className="overflow-hidden">
1404                <h4 className="text-sm font-bold text-white truncate">{userName || 'Administrator'}</h4>
1405                <p className="text-xs text-slate-500 truncate">{userEmail || 'admin@rustbasic.dev'}</p>
1406              </div>
1407            </div>
1408          </div>
1409
1410          {/* Navigation links */}
1411          <nav className="space-y-2">
1412            <Link
1413              href="/dashboard"
1414              className="flex items-center space-x-3 w-full px-4 py-3 bg-indigo-600 text-white rounded-xl font-bold text-sm shadow-lg shadow-indigo-600/10 transition-all duration-300"
1415              style={{ textDecoration: 'none' }}
1416            >
1417              <span>📊</span>
1418              <span>Dashboard Overview</span>
1419            </Link>
1420            <Link
1421              href="/"
1422              className="flex items-center space-x-3 w-full px-4 py-3 text-slate-400 hover:text-white rounded-xl font-semibold text-sm hover:bg-slate-800/30 transition-all duration-300"
1423              style={{ textDecoration: 'none' }}
1424            >
1425              <span>🏠</span>
1426              <span>Main Website</span>
1427            </Link>
1428          </nav>
1429        </div>
1430
1431        {/* Logout Form / Button */}
1432        <div className="mt-8 md:mt-0">
1433          <form onSubmit={handleLogout}>
1434            <button
1435              type="submit"
1436              className="w-full py-3 px-4 bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/20 text-rose-400 rounded-xl font-bold text-sm transition-all duration-300 flex items-center justify-center space-x-2"
1437            >
1438              <span>🚪</span>
1439              <span>KELUAR SISTEM</span>
1440            </button>
1441          </form>
1442        </div>
1443      </aside>
1444
1445      {/* Main Workspace */}
1446      <main className="flex-1 p-6 md:p-12 overflow-y-auto">
1447        <div className="max-w-6xl mx-auto">
1448          {/* Header */}
1449          <header className="flex flex-col md:flex-row md:items-center md:justify-between mb-10 gap-4">
1450            <div>
1451              <h1 className="text-3xl font-extrabold text-white tracking-tight">{title || 'Overview'}</h1>
1452              <p className="text-slate-400 text-sm mt-1">Selamat datang kembali, kendalikan project Anda secara instan.</p>
1453            </div>
1454            <div>
1455              <span className="inline-flex items-center px-4 py-2 bg-slate-900 border border-slate-800 rounded-xl text-xs font-bold text-slate-300 shadow-sm">
1456                <span className="w-2.5 h-2.5 bg-emerald-500 rounded-full mr-2 animate-ping" />
1457                Server Status: <span className="text-emerald-400 ml-1">Running</span>
1458              </span>
1459            </div>
1460          </header>
1461
1462          {/* Toast Notifications */}
1463          <Toast flash={flash} />
1464
1465          {/* Stats Grid */}
1466          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-10">
1467            {/* Stat 1 */}
1468            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1469              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1470                User Terdaftar
1471              </span>
1472              <div className="flex items-baseline space-x-2">
1473                <span className="text-5xl font-black text-white tracking-tight">{totalUsers || 0}</span>
1474                <span className="text-emerald-400 text-sm font-bold">↑ 12%</span>
1475              </div>
1476            </div>
1477
1478            {/* Stat 2 */}
1479            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1480              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1481                Response Time
1482              </span>
1483              <div className="flex items-baseline space-x-1">
1484                <span className="text-5xl font-black text-indigo-400 tracking-tight">24</span>
1485                <span className="text-slate-400 text-lg font-bold">ms</span>
1486              </div>
1487            </div>
1488
1489            {/* Stat 3 */}
1490            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1491              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1492                Database Status
1493              </span>
1494              <div className="flex items-center space-x-3 mt-2">
1495                <div className="w-3 h-3 bg-emerald-500 rounded-full shadow-[0_0_12px_#10b981]" />
1496                <span className="text-xl font-extrabold text-emerald-400 tracking-wide uppercase">HEALTHY</span>
1497              </div>
1498            </div>
1499          </div>
1500
1501          {/* Main Info Panel */}
1502          <div className="bg-slate-900/40 border border-slate-800/60 rounded-3xl p-8 glassmorphism">
1503            <div className="flex items-center justify-between mb-6">
1504              <div>
1505                <h3 className="text-lg font-bold text-white">Informasi Kernel Server</h3>
1506                <p className="text-xs text-slate-400 mt-0.5">Detail lingkungan runtime eksekusi Axum Anda.</p>
1507              </div>
1508              <span className="text-[10px] font-bold text-indigo-400 bg-indigo-500/10 border border-indigo-500/20 px-3 py-1 rounded-full uppercase tracking-wider">
1509                v2026.1
1510              </span>
1511            </div>
1512
1513            <div className="bg-slate-950 border border-slate-800/50 rounded-2xl p-6 font-mono text-xs text-emerald-400 leading-relaxed shadow-inner">
1514              <div className="text-slate-600 mb-2">// RustBasic SPA Kernel Logs</div>
1515              <div>[OK] Compiled with Axum 0.8.2</div>
1516              <div>[OK] Database Pool: Sea-ORM Connection Established</div>
1517              <div>[OK] Modern SPA Routing: Powered by Inertia.js Bridge</div>
1518              <div>[OK] Single-Binary Mode: Compile-time embedding enabled</div>
1519              <div>[OK] Workers: 8 logical threads spawned on CPU cores</div>
1520            </div>
1521          </div>
1522        </div>
1523      </main>
1524    </div>
1525  );
1526}
1527"##;
1528        fs::write(dashboard_view, dashboard_template).ok();
1529    }
1530    
1531    // 6. Create Dashboard Controller in Rust
1532    let dashboard_controller_path = "src/app/http/controllers/dashboard_controller.rs";
1533    if !std::path::Path::new(dashboard_controller_path).exists() {
1534        let dashboard_template = r#"use crate::app::inertia::inertia;
1535use crate::app::models::users;
1536use rustbasic_core::requests::Request;
1537use rustbasic_core::server::AppState;
1538use rustbasic_core::axum::{response::Response, extract::State};
1539use rustbasic_core::sea_orm::{EntityTrait, PaginatorTrait};
1540use rustbasic_core::serde_json::json;
1541
1542pub struct DashboardController;
1543
1544impl DashboardController {
1545    pub async fn index(State(state): State<AppState>, req: Request) -> Response {
1546        let user_id = req.session.get::<i32>("user_id").unwrap_or(0);
1547        let user = users::Entity::find_by_id(user_id).one(&state.db).await.ok().flatten();
1548        let total_users = users::Entity::find().count(&state.db).await.unwrap_or(0);
1549
1550        inertia(&req, "Dashboard", json!({
1551            "title": "Dashboard",
1552            "userName": user.as_ref().map(|u| u.name.clone()).unwrap_or("Guest".to_string()),
1553            "userEmail": user.as_ref().map(|u| u.email.clone()).unwrap_or_default(),
1554            "totalUsers": total_users,
1555        }))
1556    }
1557}
1558"#;
1559        fs::write(dashboard_controller_path, dashboard_template).ok();
1560        println!("   {} {}", "✅ Created:".green(), dashboard_controller_path.cyan());
1561    }
1562    update_controller_mod_rs("dashboard_controller");
1563
1564    println!("   {} Folder src/resources/js/Pages/Auth dan Dashboard siap.", "✅ Views:".green());
1565
1566    // 7. Update Welcome.jsx
1567    let welcome_path = "src/resources/js/Pages/Welcome.jsx";
1568    if let Ok(content) = fs::read_to_string(welcome_path)
1569        && content.contains("Backend Online") && !content.contains("auth_installed ?") {
1570            let target = r#"          <div className="flex items-center gap-4">
1571            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
1572              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1573              Backend Online
1574            </span>
1575          </div>"#;
1576
1577            let replacement = r#"          <div className="flex items-center gap-4">
1578            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 mr-2">
1579              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1580              Backend Online
1581            </span>
1582            {auth_installed ? (
1583              <Link 
1584                href="/dashboard" 
1585                className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1586                style={{ textDecoration: 'none' }}
1587              >
1588                Dashboard
1589              </Link>
1590            ) : (
1591              <div className="flex gap-2">
1592                <Link 
1593                  href="/login" 
1594                  className="px-4 py-2 rounded-lg border border-white/10 text-sm font-bold hover:bg-white/5 transition-all duration-300 text-gray-300 hover:text-white"
1595                  style={{ textDecoration: 'none' }}
1596                >
1597                  Masuk
1598                </Link>
1599                <Link 
1600                  href="/register" 
1601                  className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1602                  style={{ textDecoration: 'none' }}
1603                >
1604                  Daftar
1605                </Link>
1606              </div>
1607            )}
1608          </div>"#;
1609
1610            let updated = content.replace(target, replacement);
1611            fs::write(welcome_path, updated).ok();
1612            println!("   {} {}", "📝 Updated:".blue(), welcome_path.cyan());
1613        }
1614
1615    println!("\n{}", "✨ Authentication scaffolded successfully!".green().bold());
1616    println!("{}", "Jalankan 'cargo rustbasic route:list' untuk melihat rute baru.".dimmed());
1617}
1618
1619pub async fn remove_auth() {
1620    println!("\n{}", "🗑️  Removing Authentication Scaffold...".red().bold());
1621
1622    // 1. Delete src/routes/auth.rs
1623    let auth_route_path = "src/routes/auth.rs";
1624    if std::path::Path::new(auth_route_path).exists() {
1625        fs::remove_file(auth_route_path).ok();
1626        println!("   {} {}", "✅ Deleted:".green(), auth_route_path.cyan());
1627    }
1628
1629    // 2. Update src/routes/mod.rs
1630    let routes_mod_path = "src/routes/mod.rs";
1631    if let Ok(mut content) = fs::read_to_string(routes_mod_path)
1632        && content.contains("pub mod auth;") {
1633            content = content.replace("pub mod auth;\n", "");
1634            fs::write(routes_mod_path, content).ok();
1635            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
1636        }
1637
1638    // 3. Update src/routes/web.rs
1639    let web_route_path = "src/routes/web.rs";
1640    if let Ok(mut content) = fs::read_to_string(web_route_path) {
1641        let mut changed = false;
1642        
1643        // Remove imports
1644        if content.contains("use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};") {
1645            content = content.replace("use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};", "use rustbasic_core::axum::{Router, routing::get};");
1646            changed = true;
1647        }
1648        
1649        let imports_to_remove = [
1650            "use crate::app::http::controllers::{auth, dashboard_controller};\n",
1651            "use crate::app::http::middleware::auth::auth_middleware;\n",
1652            "use rustbasic_core::server::AppState;\n",
1653            "use crate::routes::auth as auth_routes;\n",
1654            "use crate::app::http::controllers::{auth, dashboard_controller};",
1655            "use crate::app::http::middleware::auth::auth_middleware;",
1656            "use crate::routes::auth as auth_routes;",
1657        ];
1658        
1659        for imp in imports_to_remove {
1660            if content.contains(imp) {
1661                content = content.replace(imp, "");
1662                changed = true;
1663            }
1664        }
1665        
1666        // Re-add server::AppState if it was removed
1667        if !content.contains("use rustbasic_core::server::AppState;") {
1668            content = content.replace("use rustbasic_core::axum::{Router, routing::get};", "use rustbasic_core::axum::{Router, routing::get};\nuse rustbasic_core::server::AppState;");
1669        }
1670
1671        // Remove auth_protected_routes logic and restore basic Router
1672        if content.contains("let auth_protected_routes = Router::new()") {
1673            let re = Regex::new(r##"(?s)\s*let auth_protected_routes = Router::new\(\).*?\.layer\(from_fn\(auth_middleware\)\);\s*"##).unwrap();
1674            content = re.replace(&content, "\n").to_string();
1675            
1676            content = content.replace(".merge(auth_routes::router())", "");
1677            content = content.replace(".merge(auth_protected_routes)", "");
1678            
1679            // Restore clean Router::new()
1680            let clean_router = r#"    Router::new()
1681        .route("/", get(welcome_controller::index))
1682        .route("/about", get(welcome_controller::about))
1683        .route("/dev", get(welcome_controller::dev_info))"#;
1684            
1685            let router_re = Regex::new(r##"(?s)Router::new\(\).*?\.route\(\s*\"/dev\"\s*,\s*get\(welcome_controller::dev_info\)\s*\)"##).unwrap();
1686            content = router_re.replace(&content, clean_router).to_string();
1687            
1688            // Final cleanup of multiple newlines
1689            let multi_newline_re = Regex::new(r#"\n{3,}"#).unwrap();
1690            content = multi_newline_re.replace_all(&content, "\n\n").to_string();
1691            
1692            changed = true;
1693        }
1694
1695        if changed {
1696            fs::write(web_route_path, content).ok();
1697            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
1698        }
1699    }
1700
1701    // 4. Delete Controllers
1702    let auth_controller_dir = "src/app/http/controllers/auth";
1703    if std::path::Path::new(auth_controller_dir).exists() {
1704        fs::remove_dir_all(auth_controller_dir).ok();
1705        println!("   {} {}", "✅ Deleted:".green(), auth_controller_dir.cyan());
1706    }
1707
1708    // 4.1 Delete Password Resets Migration & Model
1709    if let Ok(entries) = std::fs::read_dir("database/migrations") {
1710        for entry in entries.flatten() {
1711            if let Some(name) = entry.file_name().to_str()
1712                && name.ends_with("_create_password_resets_table.rs") {
1713                    let path = entry.path();
1714                    fs::remove_file(&path).ok();
1715                    println!("   {} {}", "✅ Deleted:".green(), path.display().to_string().cyan());
1716                }
1717        }
1718    }
1719    
1720    let model_path = "src/app/models/password_resets.rs";
1721    if std::path::Path::new(model_path).exists() {
1722        fs::remove_file(model_path).ok();
1723        println!("   {} {}", "✅ Deleted:".green(), model_path.cyan());
1724    }
1725
1726    // 5. Delete Components, React Pages/Auth & Dashboard.jsx
1727    let components_dir = "src/resources/js/Components";
1728    let toast_view = "src/resources/js/Components/Toast.jsx";
1729    if std::path::Path::new(toast_view).exists() {
1730        fs::remove_file(toast_view).ok();
1731    }
1732    let alert_banner_view = "src/resources/js/Components/AlertBanner.jsx";
1733    if std::path::Path::new(alert_banner_view).exists() {
1734        fs::remove_file(alert_banner_view).ok();
1735    }
1736    let form_input_view = "src/resources/js/Components/FormInput.jsx";
1737    if std::path::Path::new(form_input_view).exists() {
1738        fs::remove_file(form_input_view).ok();
1739    }
1740    if std::path::Path::new(components_dir).exists()
1741        && let Ok(entries) = std::fs::read_dir(components_dir)
1742            && entries.count() == 0 {
1743                fs::remove_dir(components_dir).ok();
1744            }
1745
1746    let auth_page_dir = "src/resources/js/Pages/Auth";
1747    if std::path::Path::new(auth_page_dir).exists() {
1748        fs::remove_dir_all(auth_page_dir).ok();
1749        println!("   {} {}", "✅ Deleted:".green(), auth_page_dir.cyan());
1750    }
1751
1752    let dashboard_page = "src/resources/js/Pages/Dashboard.jsx";
1753    if std::path::Path::new(dashboard_page).exists() {
1754        fs::remove_file(dashboard_page).ok();
1755        println!("   {} {}", "✅ Deleted:".green(), dashboard_page.cyan());
1756    }
1757
1758    // 5.1 Delete Auth Middleware
1759    let auth_middleware_path = "src/app/http/middleware/auth.rs";
1760    if std::path::Path::new(auth_middleware_path).exists() {
1761        fs::remove_file(auth_middleware_path).ok();
1762        println!("   {} {}", "✅ Deleted:".green(), auth_middleware_path.cyan());
1763    }
1764
1765    let middleware_mod_path = "src/app/http/middleware/mod.rs";
1766    if let Ok(mut content) = fs::read_to_string(middleware_mod_path)
1767        && content.contains("pub mod auth;") {
1768            content = content.replace("pub mod auth;\n", "");
1769            fs::write(middleware_mod_path, content).ok();
1770            println!("   {} {}", "📝 Updated:".blue(), middleware_mod_path.cyan());
1771        }
1772
1773    // 6. Delete Dashboard Controller
1774    let dashboard_path = "src/app/http/controllers/dashboard_controller.rs";
1775    if std::path::Path::new(dashboard_path).exists() {
1776        fs::remove_file(dashboard_path).ok();
1777        println!("   {} {}", "✅ Deleted:".green(), dashboard_path.cyan());
1778    }
1779
1780    // 7. Update src/app/http/controllers/mod.rs
1781    let controllers_mod_path = "src/app/http/controllers/mod.rs";
1782    if let Ok(mut content) = fs::read_to_string(controllers_mod_path) {
1783        let mut changed = false;
1784        if content.contains("pub mod auth;") {
1785            content = content.replace("pub mod auth;\n", "");
1786            changed = true;
1787        }
1788        if content.contains("pub mod dashboard_controller;") {
1789            content = content.replace("pub mod dashboard_controller;\n", "");
1790            changed = true;
1791        }
1792        if changed {
1793            fs::write(controllers_mod_path, content).ok();
1794            println!("   {} {}", "📝 Updated:".blue(), controllers_mod_path.cyan());
1795        }
1796    }
1797
1798    // 7.1 Update src/app/models/mod.rs
1799    let models_mod_path = "src/app/models/mod.rs";
1800    if let Ok(mut content) = fs::read_to_string(models_mod_path) {
1801        let mut changed = false;
1802        if content.contains("pub mod password_resets;") {
1803            content = content.replace("pub mod password_resets;\n", "");
1804            content = content.replace("pub mod password_resets;", "");
1805            changed = true;
1806        }
1807        if changed {
1808            fs::write(models_mod_path, content).ok();
1809            println!("   {} {}", "📝 Updated:".blue(), models_mod_path.cyan());
1810        }
1811    }
1812
1813    // 7.2 Update database/migrations/mod.rs
1814    let migration_mod_path = "database/migrations/mod.rs";
1815    if let Ok(content) = fs::read_to_string(migration_mod_path) {
1816        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
1817        let mut changed = false;
1818        
1819        // Remove the mod line
1820        lines.retain(|line| {
1821            if line.contains("_create_password_resets_table;") || (line.contains("Box::new(") && line.contains("_create_password_resets_table::Migration")) {
1822                changed = true;
1823                false
1824            } else {
1825                true
1826            }
1827        });
1828
1829        if changed {
1830            fs::write(migration_mod_path, lines.join("\n")).ok();
1831            println!("   {} {}", "📝 Updated:".blue(), migration_mod_path.cyan());
1832        }
1833    }
1834
1835    // 7.3 Restore Welcome.jsx
1836    let welcome_path = "src/resources/js/Pages/Welcome.jsx";
1837    if let Ok(content) = fs::read_to_string(welcome_path)
1838        && content.contains("auth_installed ?") {
1839            let target = r#"          <div className="flex items-center gap-4">
1840            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 mr-2">
1841              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1842              Backend Online
1843            </span>
1844            {auth_installed ? (
1845              <Link 
1846                href="/dashboard" 
1847                className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1848                style={{ textDecoration: 'none' }}
1849              >
1850                Dashboard
1851              </Link>
1852            ) : (
1853              <div className="flex gap-2">
1854                <Link 
1855                  href="/login" 
1856                  className="px-4 py-2 rounded-lg border border-white/10 text-sm font-bold hover:bg-white/5 transition-all duration-300 text-gray-300 hover:text-white"
1857                  style={{ textDecoration: 'none' }}
1858                >
1859                  Masuk
1860                </Link>
1861                <Link 
1862                  href="/register" 
1863                  className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1864                  style={{ textDecoration: 'none' }}
1865                >
1866                  Daftar
1867                </Link>
1868              </div>
1869            )}
1870          </div>"#;
1871
1872            let replacement = r#"          <div className="flex items-center gap-4">
1873            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
1874              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1875              Backend Online
1876            </span>
1877          </div>"#;
1878
1879            let updated = content.replace(target, replacement);
1880            fs::write(welcome_path, updated).ok();
1881            println!("   {} {}", "📝 Restored:".blue(), welcome_path.cyan());
1882        }
1883
1884    // 7.4 Delete Migration Record from Database
1885    println!("   {} {}", "⏳".blue(), "Cleaning up migration records from database...".dimmed());
1886    let db_connection = std::env::var("DB_CONNECTION").unwrap_or_else(|_| "sqlite".to_string());
1887    let db_host = std::env::var("DB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
1888    let db_port = std::env::var("DB_PORT").unwrap_or_else(|_| "3306".to_string());
1889    let db_database = std::env::var("DB_DATABASE").unwrap_or_else(|_| "rustbasic".to_string());
1890    let db_username = std::env::var("DB_USERNAME").unwrap_or_else(|_| "root".to_string());
1891    let db_password = std::env::var("DB_PASSWORD").unwrap_or_default();
1892
1893    let db_url = if db_connection == "mysql" {
1894        format!(
1895            "mysql://{}:{}@{}:{}/{}",
1896            db_username, db_password, db_host, db_port, db_database
1897        )
1898    } else {
1899        format!("sqlite:database/{}.sqlite?mode=rwc", db_database)
1900    };
1901
1902    if let Ok(db) = sea_orm::Database::connect(db_url).await {
1903        use sea_orm::ConnectionTrait;
1904        let table_name = if db_connection == "mysql" { "sea_orm_migrations" } else { "seaql_migrations" };
1905        let sql = format!("DELETE FROM {} WHERE version LIKE '%_create_password_resets_table'", table_name);
1906        let db_backend = if db_connection == "mysql" { sea_orm::DbBackend::MySql } else { sea_orm::DbBackend::Sqlite };
1907        let _ = db.execute(sea_orm::Statement::from_string(db_backend, sql)).await;
1908        println!("   {} {}", "✅ Cleaned:".green(), "Database migration records removed.".cyan());
1909    }
1910
1911    println!("\n{}", "✨ Authentication removed successfully!".green().bold());
1912}