about summary refs log tree commit diff
path: root/src/state/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models')
-rw-r--r--src/state/models/onboard.ts62
-rw-r--r--src/state/models/root-store.ts6
-rw-r--r--src/state/models/session.ts32
-rw-r--r--src/state/models/suggested-actors-view.ts121
4 files changed, 190 insertions, 31 deletions
diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts
new file mode 100644
index 000000000..77a066332
--- /dev/null
+++ b/src/state/models/onboard.ts
@@ -0,0 +1,62 @@
+import {makeAutoObservable} from 'mobx'
+import {isObj, hasProp} from '../lib/type-guards'
+
+export const OnboardStage = {
+  Explainers: 'explainers',
+  Follows: 'follows',
+}
+
+export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows]
+
+export class OnboardModel {
+  isOnboarding: boolean = true
+  stage: string = OnboardStageOrder[0]
+
+  constructor() {
+    makeAutoObservable(this, {
+      serialize: false,
+      hydrate: false,
+    })
+  }
+
+  serialize(): unknown {
+    return {
+      isOnboarding: this.isOnboarding,
+      stage: this.stage,
+    }
+  }
+
+  hydrate(v: unknown) {
+    if (isObj(v)) {
+      if (hasProp(v, 'isOnboarding') && typeof v.isOnboarding === 'boolean') {
+        this.isOnboarding = v.isOnboarding
+      }
+      if (
+        hasProp(v, 'stage') &&
+        typeof v.stage === 'string' &&
+        OnboardStageOrder.includes(v.stage)
+      ) {
+        this.stage = v.stage
+      }
+    }
+  }
+
+  start() {
+    this.isOnboarding = true
+  }
+
+  stop() {
+    this.isOnboarding = false
+  }
+
+  next() {
+    if (!this.isOnboarding) return
+    let i = OnboardStageOrder.indexOf(this.stage)
+    i++
+    if (i >= OnboardStageOrder.length) {
+      this.isOnboarding = false
+    } else {
+      this.stage = OnboardStageOrder[i]
+    }
+  }
+}
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts
index 717caa4a9..da846a3b0 100644
--- a/src/state/models/root-store.ts
+++ b/src/state/models/root-store.ts
@@ -11,12 +11,14 @@ import {SessionModel} from './session'
 import {NavigationModel} from './navigation'
 import {ShellModel} from './shell'
 import {MeModel} from './me'
+import {OnboardModel} from './onboard'
 
 export class RootStoreModel {
   session = new SessionModel(this)
   nav = new NavigationModel()
   shell = new ShellModel()
   me = new MeModel(this)
+  onboard = new OnboardModel()
 
   constructor(public api: SessionServiceClient) {
     makeAutoObservable(this, {
@@ -53,6 +55,7 @@ export class RootStoreModel {
     return {
       session: this.session.serialize(),
       nav: this.nav.serialize(),
+      onboard: this.onboard.serialize(),
     }
   }
 
@@ -64,6 +67,9 @@ export class RootStoreModel {
       if (hasProp(v, 'nav')) {
         this.nav.hydrate(v.nav)
       }
+      if (hasProp(v, 'onboard')) {
+        this.onboard.hydrate(v.onboard)
+      }
     }
   }
 
diff --git a/src/state/models/session.ts b/src/state/models/session.ts
index e29960954..e10a08e86 100644
--- a/src/state/models/session.ts
+++ b/src/state/models/session.ts
@@ -15,17 +15,8 @@ interface SessionData {
   did: string
 }
 
-export enum OnboardingStage {
-  Init = 'init',
-}
-
-interface OnboardingState {
-  stage: OnboardingStage
-}
-
 export class SessionModel {
   data: SessionData | null = null
-  onboardingState: OnboardingState | null = null
 
   constructor(public rootStore: RootStoreModel) {
     makeAutoObservable(this, {
@@ -42,7 +33,6 @@ export class SessionModel {
   serialize(): unknown {
     return {
       data: this.data,
-      onboardingState: this.onboardingState,
     }
   }
 
@@ -87,18 +77,6 @@ export class SessionModel {
           this.data = data
         }
       }
-      if (
-        this.data &&
-        hasProp(v, 'onboardingState') &&
-        isObj(v.onboardingState)
-      ) {
-        if (
-          hasProp(v.onboardingState, 'stage') &&
-          typeof v.onboardingState === 'string'
-        ) {
-          this.onboardingState = v.onboardingState
-        }
-      }
     }
   }
 
@@ -212,7 +190,7 @@ export class SessionModel {
         handle: res.data.handle,
         did: res.data.did,
       })
-      this.setOnboardingStage(OnboardingStage.Init)
+      this.rootStore.onboard.start()
       this.configureApi()
       this.rootStore.me.load().catch(e => {
         console.error('Failed to fetch local user information', e)
@@ -228,12 +206,4 @@ export class SessionModel {
     }
     this.rootStore.clearAll()
   }
-
-  setOnboardingStage(stage: OnboardingStage | null) {
-    if (stage === null) {
-      this.onboardingState = null
-    } else {
-      this.onboardingState = {stage}
-    }
-  }
 }
diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts
new file mode 100644
index 000000000..245dbf020
--- /dev/null
+++ b/src/state/models/suggested-actors-view.ts
@@ -0,0 +1,121 @@
+import {makeAutoObservable} from 'mobx'
+import {RootStoreModel} from './root-store'
+
+interface Response {
+  data: {
+    suggestions: ResponseSuggestedActor[]
+  }
+}
+export type ResponseSuggestedActor = {
+  did: string
+  handle: string
+  displayName?: string
+  description?: string
+  createdAt?: string
+  indexedAt: string
+}
+
+export type SuggestedActor = ResponseSuggestedActor & {
+  _reactKey: string
+}
+
+export class SuggestedActorsViewModel {
+  // state
+  isLoading = false
+  isRefreshing = false
+  hasLoaded = false
+  error = ''
+
+  // data
+  suggestions: SuggestedActor[] = []
+
+  constructor(public rootStore: RootStoreModel) {
+    makeAutoObservable(
+      this,
+      {
+        rootStore: false,
+      },
+      {autoBind: true},
+    )
+  }
+
+  get hasContent() {
+    return this.suggestions.length > 0
+  }
+
+  get hasError() {
+    return this.error !== ''
+  }
+
+  get isEmpty() {
+    return this.hasLoaded && !this.hasContent
+  }
+
+  // public api
+  // =
+
+  async setup() {
+    await this._fetch()
+  }
+
+  async refresh() {
+    await this._fetch(true)
+  }
+
+  async loadMore() {
+    // TODO
+  }
+
+  // state transitions
+  // =
+
+  private _xLoading(isRefreshing = false) {
+    this.isLoading = true
+    this.isRefreshing = isRefreshing
+    this.error = ''
+  }
+
+  private _xIdle(err: string = '') {
+    this.isLoading = false
+    this.isRefreshing = false
+    this.hasLoaded = true
+    this.error = err
+  }
+
+  // loader functions
+  // =
+
+  private async _fetch(isRefreshing = false) {
+    this._xLoading(isRefreshing)
+    try {
+      const debugRes = await this.rootStore.api.app.bsky.graph.getFollowers({
+        user: 'alice.test',
+      })
+      const res = {
+        data: {
+          suggestions: debugRes.data.followers,
+        },
+      }
+      this._replaceAll(res)
+      this._xIdle()
+    } catch (e: any) {
+      this._xIdle(e.toString())
+    }
+  }
+
+  private _replaceAll(res: Response) {
+    this.suggestions.length = 0
+    let counter = 0
+    for (const item of res.data.suggestions) {
+      this._append({
+        _reactKey: `item-${counter++}`,
+        description: 'Just another cool person using Bluesky',
+        ...item,
+      })
+    }
+  }
+
+  private _append(item: SuggestedActor) {
+    this.suggestions.push(item)
+  }
+}