···11+package models
22+33+import (
44+ "time"
55+)
66+77+type OnboardingStatus string
88+99+const (
1010+ OnboardingInProgress OnboardingStatus = "in_progress"
1111+ OnboardingCompleted OnboardingStatus = "completed"
1212+ OnboardingSkipped OnboardingStatus = "skipped"
1313+)
1414+1515+// onboarding steps, in order. Done is a sentinel that marks completion.
1616+const (
1717+ OnboardingStepProfile = 0
1818+ OnboardingStepSocial = 1
1919+ OnboardingStepKeys = 2
2020+ OnboardingStepRepo = 3
2121+ OnboardingStepDone = 4
2222+)
2323+2424+type Onboarding struct {
2525+ Did string
2626+ Step int
2727+ Status OnboardingStatus
2828+ Created time.Time
2929+ Updated time.Time
3030+}
3131+3232+// OnboardingProgress feeds the "resume onboarding" banner/panel. Active is false
3333+// when there is nothing to resume (no in-progress onboarding).
3434+type OnboardingProgress struct {
3535+ Active bool
3636+ Step int
3737+ Total int
3838+ Percent int
3939+}
4040+4141+// Progress derives display progress for the resume banner/panel. It is nil-safe,
4242+// so callers can pass the result of GetOnboarding directly.
4343+func (o *Onboarding) Progress() OnboardingProgress {
4444+ if o == nil || o.Status != OnboardingInProgress {
4545+ return OnboardingProgress{}
4646+ }
4747+ // Percent reflects completed steps out of the total, so the bar only fills
4848+ // as steps are finished (e.g. on the last step it is not yet 100%).
4949+ total := OnboardingStepDone
5050+ percent := o.Step * 100 / total
5151+ if percent > 100 {
5252+ percent = 100
5353+ }
5454+ return OnboardingProgress{
5555+ Active: true,
5656+ Step: o.Step,
5757+ Total: total,
5858+ Percent: percent,
5959+ }
6060+}