* Initialize Onboarding Flow * Shorten Onboarding * Add Setup circle and what are the current way to add tasks to the onboarding. update donetick logo * improve wording * Skip onboarding flow for now * Enable onboarding flow
115 lines
3.2 KiB
JavaScript
115 lines
3.2 KiB
JavaScript
import { Capacitor } from '@capacitor/core'
|
|
|
|
// First-run onboarding is a native-app-only flow. On the web the user already
|
|
// chose to visit a URL, so we drop them straight on the auth screens.
|
|
const STORAGE_KEY = 'onboardingCompletedAt'
|
|
|
|
export const isNativeApp = () => {
|
|
try {
|
|
return Capacitor.isNativePlatform()
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export const hasSeenOnboarding = () => {
|
|
try {
|
|
return Boolean(localStorage.getItem(STORAGE_KEY))
|
|
} catch {
|
|
// Private-mode / storage-disabled webviews: never trap the user in a loop.
|
|
return true
|
|
}
|
|
}
|
|
|
|
export const markOnboardingSeen = () => {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, new Date().toISOString())
|
|
} catch {
|
|
// ignore, worst case the flow is shown once more
|
|
}
|
|
}
|
|
|
|
export const resetOnboarding = () => {
|
|
try {
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
const ACQUISITION_SOURCE_KEY = 'acquisitionSource'
|
|
|
|
/**
|
|
* Stashes the "where'd you hear about us" answer locally for now. No
|
|
* analytics pipeline is wired up yet — this is the single place that'll
|
|
* change once there is one, so the survey screen itself never has to.
|
|
*/
|
|
export const recordAcquisitionSource = source => {
|
|
try {
|
|
localStorage.setItem(ACQUISITION_SOURCE_KEY, source)
|
|
} catch {
|
|
// ignore, this is best-effort telemetry
|
|
}
|
|
}
|
|
|
|
const PRIVACY_PREFERENCES_KEY = 'privacyPreferences'
|
|
|
|
/**
|
|
* Stashes the self-hosted privacy opt-ins locally, same stub-for-now
|
|
* treatment as recordAcquisitionSource: no crash reporter or PostHog is wired
|
|
* up yet, so this is just the one place that'll change once there is one.
|
|
*/
|
|
export const recordPrivacyPreferences = ({ crashReports, analytics }) => {
|
|
try {
|
|
localStorage.setItem(
|
|
PRIVACY_PREFERENCES_KEY,
|
|
JSON.stringify({ crashReports, analytics }),
|
|
)
|
|
} catch {
|
|
// ignore, this is best-effort telemetry
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asks the OS for notification permission during onboarding and records the
|
|
* answer under the same `notificationPreferences` key NotificationAccessSnackbar
|
|
* reads, so a user who says yes here is never asked again after signing in.
|
|
*
|
|
* Only the permission is requested: registering the push token needs a session,
|
|
* and there isn't one yet. The snackbar picks that up once the user is in.
|
|
*/
|
|
export const requestNotificationPermission = async () => {
|
|
if (!isNativeApp()) return false
|
|
try {
|
|
const { LocalNotifications } = await import(
|
|
'@capacitor/local-notifications'
|
|
)
|
|
const { Preferences } = await import('@capacitor/preferences')
|
|
|
|
const result = await LocalNotifications.requestPermissions()
|
|
const granted = result?.display === 'granted'
|
|
|
|
await Preferences.set({
|
|
key: 'notificationPreferences',
|
|
value: JSON.stringify({ optOut: false, granted }),
|
|
})
|
|
return granted
|
|
} catch {
|
|
// Permission plugins are missing or the prompt was dismissed: carry on,
|
|
// the in-app snackbar can still ask later.
|
|
return false
|
|
}
|
|
}
|
|
|
|
export const haptic = async (kind = 'light') => {
|
|
if (!isNativeApp()) return
|
|
try {
|
|
const { Haptics, ImpactStyle } = await import('@capacitor/haptics')
|
|
await Haptics.impact({
|
|
style: kind === 'medium' ? ImpactStyle.Medium : ImpactStyle.Light,
|
|
})
|
|
} catch {
|
|
// no haptics on this platform
|
|
}
|
|
}
|