fix demo bugs

This commit is contained in:
dohsimpson
2025-02-26 18:51:13 -05:00
parent dea2b30c3b
commit a615a45c39
15 changed files with 135 additions and 52 deletions

View File

@@ -8,6 +8,7 @@ import {
ViewType,
getDefaultUsersData,
CompletionCache,
getDefaultServerSettings,
} from "./types";
import {
getTodayInTimezone,
@@ -46,6 +47,7 @@ export const settingsAtom = atom(getDefaultSettings());
export const habitsAtom = atom(getDefaultHabitsData());
export const coinsAtom = atom(getDefaultCoinsData());
export const wishlistAtom = atom(getDefaultWishlistData());
export const serverSettingsAtom = atom(getDefaultServerSettings());
// Derived atom for coins earned today
export const coinsEarnedTodayAtom = atom((get) => {

View File

@@ -24,10 +24,9 @@ export function init() {
)
.join("\n ")
console.error(
throw new Error(
`Missing environment variables:\n ${errorMessage}`,
)
process.exit(1)
}
}
}

View File

@@ -130,6 +130,10 @@ export const getDefaultSettings = (): Settings => ({
profile: {}
});
export const getDefaultServerSettings = (): ServerSettings => ({
isDemo: false
})
// Map of data types to their default values
export const DATA_DEFAULTS = {
wishlist: getDefaultWishlistData,
@@ -178,4 +182,9 @@ export interface JotaiHydrateInitialValues {
habits: HabitsData;
wishlist: WishlistData;
users: UserData;
serverSettings: ServerSettings;
}
export interface ServerSettings {
isDemo: boolean
}

View File

@@ -535,13 +535,8 @@ describe('isHabitDueToday', () => {
test('should return false for invalid recurrence rule', () => {
const habit = testHabit('INVALID_RRULE')
// Mock console.error to prevent test output pollution
const consoleSpy = spyOn(console, 'error').mockImplementation(() => { })
// Expect the function to throw an error
expect(() => isHabitDueToday({ habit, timezone: 'UTC' })).toThrow()
consoleSpy.mockRestore()
const consoleSpy = spyOn(console, 'error').mockImplementation(() => {})
expect(isHabitDueToday({ habit, timezone: 'UTC' })).toBe(false)
})
})
@@ -653,8 +648,7 @@ describe('isHabitDue', () => {
test('should return false for invalid recurrence rule', () => {
const habit = testHabit('INVALID_RRULE')
const date = DateTime.fromISO('2024-01-01T00:00:00Z')
const consoleSpy = spyOn(console, 'error').mockImplementation(() => { })
expect(() => isHabitDue({ habit, timezone: 'UTC', date })).toThrow()
consoleSpy.mockRestore()
const consoleSpy = spyOn(console, 'error').mockImplementation(() => {})
expect(isHabitDue({ habit, timezone: 'UTC', date })).toBe(false)
})
})

View File

@@ -3,7 +3,7 @@ import { twMerge } from "tailwind-merge"
import { DateTime, DateTimeFormatOptions } from "luxon"
import { datetime, RRule } from 'rrule'
import { Freq, Habit, CoinTransaction, Permission } from '@/lib/types'
import { DUE_MAP, RECURRENCE_RULE_MAP } from "./constants"
import { DUE_MAP, INITIAL_DUE, INITIAL_RECURRENCE_RULE, RECURRENCE_RULE_MAP } from "./constants"
import * as chrono from 'chrono-node'
import _ from "lodash"
import { v4 as uuidv4 } from 'uuid'
@@ -191,20 +191,28 @@ export function getRRuleUTC(recurrenceRule: string) {
export function parseNaturalLanguageRRule(ruleText: string) {
ruleText = ruleText.trim()
let rrule: RRule
if (RECURRENCE_RULE_MAP[ruleText]) {
return RRule.fromString(RECURRENCE_RULE_MAP[ruleText])
rrule = RRule.fromString(RECURRENCE_RULE_MAP[ruleText])
} else {
rrule = RRule.fromText(ruleText)
}
return RRule.fromText(ruleText)
if (isUnsupportedRRule(rrule)) return RRule.fromString('invalid') // return invalid if unsupported
return rrule
}
export function parseRRule(ruleText: string) {
ruleText = ruleText.trim()
let rrule: RRule
if (RECURRENCE_RULE_MAP[ruleText]) {
return RRule.fromString(RECURRENCE_RULE_MAP[ruleText])
rrule = RRule.fromString(RECURRENCE_RULE_MAP[ruleText])
} else {
rrule = RRule.fromString(ruleText)
}
return RRule.fromString(ruleText)
if (isUnsupportedRRule(rrule)) return RRule.fromString('invalid') // return invalid if unsupported
return rrule
}
export function serializeRRule(rrule: RRule) {
@@ -222,6 +230,25 @@ export function parseNaturalLanguageDate({ text, timezone }: { text: string, tim
return DateTime.fromJSDate(due).setZone(timezone)
}
export function getFrequencyDisplayText(frequency: string | undefined, isRecurRule: boolean, timezone: string) {
if (isRecurRule) {
try {
return parseRRule((frequency) || INITIAL_RECURRENCE_RULE).toText();
} catch {
return 'invalid'
}
} else {
if (!frequency) {
return INITIAL_DUE
}
return d2s({
dateTime: t2d({ timestamp: frequency, timezone: timezone }),
timezone: timezone,
format: DateTime.DATE_MED_WITH_WEEKDAY
});
}
}
export function isHabitDue({
habit,
timezone,
@@ -247,8 +274,13 @@ export function isHabitDue({
const endOfDay = date.setZone(timezone).endOf('day')
const ruleText = habit.frequency
const rrule = parseRRule(ruleText)
let rrule
try {
rrule = parseRRule(ruleText)
} catch (error) {
console.error(`Failed to parse rrule for habit: ${habit.id} ${habit.name}`)
return false
}
rrule.origOptions.tzid = timezone
rrule.options.tzid = rrule.origOptions.tzid
rrule.origOptions.dtstart = datetime(startOfDay.year, startOfDay.month, startOfDay.day, startOfDay.hour, startOfDay.minute, startOfDay.second)
@@ -296,10 +328,18 @@ export function getHabitFreq(habit: Habit): Freq {
case RRule.WEEKLY: return 'weekly'
case RRule.MONTHLY: return 'monthly'
case RRule.YEARLY: return 'yearly'
default: throw new Error(`Invalid frequency: ${freq}`)
default:
console.error(`Invalid frequency: ${freq} (habit: ${habit.id} ${habit.name}) (rrule: ${rrule.toString()}). Defaulting to daily`)
return 'daily'
}
}
export function isUnsupportedRRule(rrule: RRule): boolean {
const freq = rrule.origOptions.freq
return freq === RRule.HOURLY || freq === RRule.MINUTELY || freq === RRule.SECONDLY
}
// play sound (client side only, must be run in browser)
export const playSound = (soundPath: string = '/sounds/timer-end.wav') => {
const audio = new Audio(soundPath)