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