use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OnboardingState {
#[serde(default)]
pub completed: bool,
#[serde(default)]
pub last_step: Option<String>,
#[serde(default)]
pub mode: Option<String>,
}
impl OnboardingState {
pub fn path() -> PathBuf {
crate::config::opencrabs_home().join("onboarding.json")
}
pub fn load() -> Self {
let path = Self::path();
let raw = match std::fs::read_to_string(&path) {
Ok(raw) => raw,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Self::default(),
Err(e) => {
tracing::warn!(
"Onboarding progress at {} could not be read ({e}) — treating setup as unfinished",
path.display()
);
return Self::default();
}
};
match serde_json::from_str(&raw) {
Ok(state) => state,
Err(e) => {
tracing::warn!(
"Onboarding progress at {} is not valid JSON ({e}) — treating setup as unfinished",
path.display()
);
Self::default()
}
}
}
pub fn save(&self) {
let path = Self::path();
if let Some(parent) = path.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
tracing::warn!(
"Could not create {} for onboarding progress: {e}",
parent.display()
);
return;
}
let json = match serde_json::to_string_pretty(self) {
Ok(json) => json,
Err(e) => {
tracing::warn!("Could not serialize onboarding progress: {e}");
return;
}
};
if let Err(e) = std::fs::write(&path, json) {
tracing::warn!(
"Could not write onboarding progress to {}: {e}",
path.display()
);
}
}
pub fn record_step(step: &str, mode: &str) {
let mut state = Self::load();
if state.last_step.as_deref() == Some(step) && state.mode.as_deref() == Some(mode) {
return;
}
state.last_step = Some(step.to_string());
state.mode = Some(mode.to_string());
state.save();
}
pub fn mark_completed() {
let mut state = Self::load();
state.completed = true;
state.last_step = None;
state.save();
}
}