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