forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {
3 ageAssuranceRuleIDs as ids,
4 type AppBskyAgeassuranceDefs,
5 getAgeAssuranceRegionConfig,
6 type ModerationPrefs,
7} from '@atproto/api'
8
9import {getAge} from '#/lib/strings/time'
10import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
11import {useAgeAssuranceDataContext} from '#/ageAssurance/data'
12import {AgeAssuranceAccess} from '#/ageAssurance/types'
13import {type Geolocation, useGeolocation} from '#/geolocation'
14
15const DEFAULT_MIN_AGE = 13
16
17/**
18 * Get age assurance region config based on geolocation, with fallback to
19 * app defaults if no region config is found.
20 *
21 * See {@link getAgeAssuranceRegionConfig} for the generic option, which can
22 * return undefined if the geolocation does not match any AA region.
23 */
24export function getAgeAssuranceRegionConfigWithFallback(
25 config: AppBskyAgeassuranceDefs.Config,
26 geolocation: Geolocation,
27): AppBskyAgeassuranceDefs.ConfigRegion {
28 const region = getAgeAssuranceRegionConfig(config, {
29 countryCode: geolocation.countryCode ?? '',
30 regionCode: geolocation.regionCode,
31 })
32
33 return (
34 region || {
35 countryCode: '*',
36 regionCode: undefined,
37 rules: [
38 {
39 $type: ids.IfDeclaredOverAge,
40 age: DEFAULT_MIN_AGE,
41 access: AgeAssuranceAccess.Full,
42 },
43 {
44 $type: ids.Default,
45 access: AgeAssuranceAccess.None,
46 },
47 ],
48 }
49 )
50}
51
52/**
53 * Hook to get the age assurance region config based on current geolocation.
54 * Does not fall-back to our app defaults. If no config is found, returns
55 * undefined, which indicates no regional age assurance rules apply.
56 */
57export function useAgeAssuranceRegionConfig() {
58 const geolocation = useGeolocation()
59 const {config} = useAgeAssuranceDataContext()
60 return useMemo(() => {
61 if (!config) return
62 // use generic helper, we want to potentially return undefined
63 return getAgeAssuranceRegionConfig(config, {
64 countryCode: geolocation.countryCode ?? '',
65 regionCode: geolocation.regionCode,
66 })
67 }, [config, geolocation])
68}
69
70/**
71 * Some users may have erroneously set their birth date to the current date
72 * if one wasn't set on their account. We previously didn't do validation on
73 * the bday dialog, and it defaulted to the current date. This bug _has_ been
74 * seen in production, so we need to check for it where possible.
75 */
76export function isLegacyBirthdateBug(birthDate: string) {
77 return ['2025', '2024', '2023'].includes((birthDate || '').slice(0, 4))
78}
79
80/**
81 * Returns whether the user is under the minimum age required to use the app.
82 * This applies to all regions.
83 */
84export function isUserUnderMinimumAge(birthDate: string) {
85 return getAge(new Date(birthDate)) < DEFAULT_MIN_AGE
86}
87
88export function isUserUnderAdultAge(birthDate: string) {
89 return getAge(new Date(birthDate)) < 18
90}
91
92export function getBirthdateStringFromAge(age: number) {
93 const today = new Date()
94 return new Date(
95 today.getFullYear() - age,
96 today.getMonth(),
97 today.getDate() - 1, // set to day before to ensure age is reached
98 ).toISOString()
99}
100
101export const makeAgeRestrictedModerationPrefs = (
102 prefs: ModerationPrefs,
103): ModerationPrefs => ({
104 ...prefs,
105 adultContentEnabled: false,
106 labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
107})