mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-19 15:27:16 -05:00
Cache data for required form as well
This commit is contained in:
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "com.compassconnections.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 36
|
||||
versionName "1.6.0"
|
||||
versionCode 38
|
||||
versionName "1.7.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -15,6 +15,7 @@ import {ShowMore} from 'web/components/widgets/show-more'
|
||||
import {NewTabLink} from 'web/components/widgets/new-tab-link'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {richTextToString} from 'common/util/parse'
|
||||
import {useRouter} from "next/router";
|
||||
|
||||
export function BioTips({onClick}: { onClick?: () => void }) {
|
||||
const t = useT();
|
||||
@@ -27,6 +28,13 @@ export function BioTips({onClick}: { onClick?: () => void }) {
|
||||
- Optional: romantic preferences, lifestyle habits, and conversation starters
|
||||
`);
|
||||
|
||||
let href = '/tips-bio'
|
||||
|
||||
const router = useRouter()
|
||||
if (router.pathname === '/signup') {
|
||||
href += '?fromSignup=true'
|
||||
}
|
||||
|
||||
return (
|
||||
<ShowMore
|
||||
labelClosed={t('profile.bio.tips', 'Tips')}
|
||||
@@ -36,7 +44,7 @@ export function BioTips({onClick}: { onClick?: () => void }) {
|
||||
<p>{t('profile.bio.tips_intro', "Write a clear and engaging bio to help others understand who you are and the connections you seek. Include:")}</p>
|
||||
<ReactMarkdown>{tips}</ReactMarkdown>
|
||||
<NewTabLink
|
||||
href="/tips-bio"
|
||||
href={href}
|
||||
onClick={onClick}>{t('profile.bio.tips_link', 'Read full tips for writing a high-quality bio')}</NewTabLink>
|
||||
</ShowMore>
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ export function EmailVerificationButton() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Col className={'gap-2 mx-4'}>
|
||||
<Col className={'gap-2'}>
|
||||
<Button
|
||||
color={'gray-outline'}
|
||||
onClick={() => sendVerificationEmail(user, t)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {SEO} from "web/components/SEO";
|
||||
import {capitalize} from "lodash";
|
||||
import {CustomLink} from "web/components/links";
|
||||
import {BackButton} from "web/components/back-button";
|
||||
import {useRouter} from "next/router";
|
||||
|
||||
export const MD_PATHS = [
|
||||
'constitution',
|
||||
@@ -32,6 +33,25 @@ export const CustomMarkdown = ({children}: { children: string }) => {
|
||||
|
||||
export default function MarkdownPage({content, filename}: Props) {
|
||||
const title = /[A-Z]/.test(filename) ? filename : capitalize(filename)
|
||||
const router = useRouter()
|
||||
const {query} = router
|
||||
const fromSignup = query.fromSignup === 'true'
|
||||
|
||||
if (fromSignup) {
|
||||
return (
|
||||
<Col className="items-center">
|
||||
<Col className="items-center justify-center mb-8 max-w-4xl">
|
||||
<Col className='w-full rounded px-3 py-4 sm:px-6 space-y-4'>
|
||||
<BackButton className="-ml-2 self-start"/>
|
||||
<div className={'custom-link !mt-0'}>
|
||||
<CustomMarkdown>{content}</CustomMarkdown>
|
||||
</div>
|
||||
</Col>
|
||||
</Col>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PageBase trackPageView={filename} className={'col-span-8'}>
|
||||
<SEO
|
||||
@@ -41,7 +61,6 @@ export default function MarkdownPage({content, filename}: Props) {
|
||||
/>
|
||||
<Col className="items-center mb-8">
|
||||
<Col className='w-full rounded px-3 py-4 sm:px-6 space-y-4'>
|
||||
<BackButton className="-ml-2 self-start"/>
|
||||
<div className={'custom-link !mt-0'}>
|
||||
<CustomMarkdown>{content}</CustomMarkdown>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Fragment, useCallback, useEffect, useRef, useState} from 'react'
|
||||
import {Fragment, useEffect, useRef, useState} from 'react'
|
||||
import {Title} from 'web/components/widgets/title'
|
||||
import {Col} from 'web/components/layout/col'
|
||||
import clsx from 'clsx'
|
||||
@@ -27,6 +27,7 @@ import {City, CityRow, profileToCity, useCitySearch} from "web/components/search
|
||||
import {AddPhotosWidget} from './widgets/add-photos'
|
||||
import {RadioToggleGroup} from "web/components/widgets/radio-toggle-group";
|
||||
import {MultipleChoiceOptions} from "common/profiles/multiple-choice";
|
||||
import {useProfileDraft} from 'web/hooks/use-profile-draft'
|
||||
import {
|
||||
DIET_CHOICES,
|
||||
EDUCATION_CHOICES,
|
||||
@@ -48,7 +49,6 @@ import {sleep} from "common/util/time"
|
||||
import {SignupBio} from "web/components/bio/editable-bio";
|
||||
import {Editor} from "@tiptap/core";
|
||||
import {Slider} from "web/components/widgets/slider";
|
||||
import {safeLocalStorage} from "web/lib/util/local";
|
||||
|
||||
|
||||
export const OptionalProfileUserForm = (props: {
|
||||
@@ -100,79 +100,7 @@ export const OptionalProfileUserForm = (props: {
|
||||
const [workChoices, setWorkChoices] = useState({})
|
||||
const {locale} = useLocale()
|
||||
|
||||
const KEY = `draft-profile-${user.id}`
|
||||
|
||||
const clearProfileDraft = (userId: string) => {
|
||||
try {
|
||||
safeLocalStorage?.removeItem(`draft-profile-${userId}`)
|
||||
safeLocalStorage?.removeItem(`draft-profile-${userId}-timestamp`)
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear profile from store:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced save function
|
||||
const debouncedSaveProfile = useCallback(
|
||||
(() => {
|
||||
let timeoutId: NodeJS.Timeout
|
||||
return (profileToSave: ProfileWithoutUser) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => {
|
||||
try {
|
||||
safeLocalStorage?.setItem(KEY, JSON.stringify({
|
||||
profile: profileToSave,
|
||||
timestamp: Date.now().toString(),
|
||||
}))
|
||||
} catch (error) {
|
||||
console.warn('Failed to save profile to store:', error)
|
||||
}
|
||||
}, 500) // 500ms debounce delay
|
||||
}
|
||||
})(),
|
||||
[KEY]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
console.log({profile})
|
||||
if (profile && Object.keys(profile).length > 0) {
|
||||
debouncedSaveProfile(profile)
|
||||
}
|
||||
}, [profile, user.id, debouncedSaveProfile])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const savedProfileString = safeLocalStorage?.getItem(KEY)
|
||||
if (savedProfileString) {
|
||||
const data = JSON.parse(savedProfileString)
|
||||
if (data) {
|
||||
const {profile: savedProfile, timestamp} = data
|
||||
// Check if saved data is older than 24 hours
|
||||
if (timestamp) {
|
||||
const savedTime = parseInt(timestamp, 10)
|
||||
const now = Date.now()
|
||||
const twentyFourHoursInMs = 24 * 60 * 60 * 1000
|
||||
|
||||
if (now - savedTime > twentyFourHoursInMs) {
|
||||
console.log('Skipping profile update: saved data is older than 24 hours')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update all profile fields
|
||||
Object.entries(savedProfile).forEach(([key, value]) => {
|
||||
const typedKey = key as keyof ProfileWithoutUser
|
||||
if (value !== profile[typedKey]) {
|
||||
console.log(key, value)
|
||||
setProfile(typedKey, value)
|
||||
if (typedKey === 'height_in_inches') updateHeight(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load profile from store:', error)
|
||||
}
|
||||
}, []) // Only run once on mount
|
||||
const {clearProfileDraft} = useProfileDraft(user.id, profile, setProfile, updateHeight)
|
||||
|
||||
useEffect(() => {
|
||||
fetchChoices('interests', locale).then(setInterestChoices)
|
||||
|
||||
@@ -13,6 +13,7 @@ import {ProfileRow, ProfileWithoutUser} from 'common/profiles/profile'
|
||||
import {SignupBio} from "web/components/bio/editable-bio"
|
||||
import {Editor} from "@tiptap/core"
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {useProfileDraft} from 'web/hooks/use-profile-draft'
|
||||
|
||||
export const initialRequiredState = {
|
||||
age: undefined,
|
||||
@@ -53,6 +54,14 @@ export const RequiredProfileUserForm = (props: {
|
||||
const [step, setStep] = useState<number>(0)
|
||||
const t = useT()
|
||||
|
||||
const {draftLoaded} = useProfileDraft(user.id, profile, setProfile)
|
||||
|
||||
useEffect(() => {
|
||||
if (draftLoaded) {
|
||||
setStep(1)
|
||||
}
|
||||
}, [draftLoaded])
|
||||
|
||||
const {
|
||||
name,
|
||||
username,
|
||||
@@ -140,6 +149,7 @@ export const RequiredProfileUserForm = (props: {
|
||||
{t('profile.basics.bio', 'Bio')}
|
||||
</label>
|
||||
<SignupBio
|
||||
profile={profile}
|
||||
onChange={(e: Editor) => {
|
||||
console.debug('bio changed', e, profile.bio)
|
||||
setProfile('bio', e.getJSON())
|
||||
|
||||
90
web/hooks/use-profile-draft.ts
Normal file
90
web/hooks/use-profile-draft.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {useCallback, useEffect, useState} from 'react'
|
||||
import {ProfileWithoutUser} from 'common/profiles/profile'
|
||||
|
||||
const safeLocalStorage = typeof window !== 'undefined' ? window.localStorage : null
|
||||
|
||||
export const useProfileDraft = (
|
||||
userId: string,
|
||||
profile: ProfileWithoutUser,
|
||||
setProfile: <K extends keyof ProfileWithoutUser>(key: K, value: ProfileWithoutUser[K]) => void,
|
||||
updateHeight?: (value: number | undefined) => void
|
||||
) => {
|
||||
const KEY = `draft-profile-${userId}`
|
||||
const [draftLoaded, setDraftLoaded] = useState(false)
|
||||
|
||||
const clearProfileDraft = (userId: string) => {
|
||||
try {
|
||||
safeLocalStorage?.removeItem(`draft-profile-${userId}`)
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear profile from store:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced save function
|
||||
const debouncedSaveProfile = useCallback(
|
||||
(() => {
|
||||
let timeoutId: NodeJS.Timeout
|
||||
return (profileToSave: ProfileWithoutUser) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => {
|
||||
try {
|
||||
safeLocalStorage?.setItem(KEY, JSON.stringify({
|
||||
profile: profileToSave,
|
||||
timestamp: Date.now().toString(),
|
||||
}))
|
||||
} catch (error) {
|
||||
console.warn('Failed to save profile to store:', error)
|
||||
}
|
||||
}, 500) // 500ms debounce delay
|
||||
}
|
||||
})(),
|
||||
[KEY]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
console.log({profile})
|
||||
if (profile && Object.keys(profile).length > 0) {
|
||||
debouncedSaveProfile(profile)
|
||||
}
|
||||
}, [profile, userId, debouncedSaveProfile])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const savedProfileString = safeLocalStorage?.getItem(KEY)
|
||||
if (savedProfileString) {
|
||||
const data = JSON.parse(savedProfileString)
|
||||
if (data) {
|
||||
const {profile: savedProfile, timestamp} = data
|
||||
// Check if saved data is older than 24 hours
|
||||
if (timestamp) {
|
||||
const savedTime = parseInt(timestamp, 10)
|
||||
const now = Date.now()
|
||||
const twentyFourHoursInMs = 24 * 60 * 60 * 1000
|
||||
|
||||
if (now - savedTime > twentyFourHoursInMs) {
|
||||
console.log('Skipping profile update: saved data is older than 24 hours')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update all profile fields
|
||||
Object.entries(savedProfile).forEach(([key, value]) => {
|
||||
const typedKey = key as keyof ProfileWithoutUser
|
||||
if (value !== profile[typedKey]) {
|
||||
console.log(key, value)
|
||||
setProfile(typedKey, value)
|
||||
if (typedKey === 'height_in_inches' && updateHeight) {
|
||||
updateHeight(value as number)
|
||||
}
|
||||
}
|
||||
})
|
||||
setDraftLoaded(true)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load profile from store:', error)
|
||||
}
|
||||
}, []) // Only run once on mount
|
||||
|
||||
return {clearProfileDraft, draftLoaded}
|
||||
}
|
||||
Reference in New Issue
Block a user