Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/app/(app)/achievements/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default function AchievementsPage() {
{achievementsByCategory.map((category) => (
<div key={category.key}>
<h2 className="form-label mb-3">
{t(`gamification.categories.${category.key}` as Parameters<typeof t>[0])}
{t(`gamification.categories.${category.key}` as Parameters<typeof t>[0])} {/* NOSONAR - dynamic i18n key requires assertion */}
</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{category.items.map((achievement) => (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export default function TodayPage() {
const { tags } = useTags()

// Show general on today preference (local storage)
const [showGeneralOnToday, _setShowGeneralOnToday] = useState(() => {
const [showGeneralOnToday] = useState(() => {
if (typeof globalThis === 'undefined' || typeof globalThis.localStorage === 'undefined') return true // NOSONAR - SSR guard
return localStorage.getItem('orbit_show_general_on_today') !== 'false'
})
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/preferences/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export default function PreferencesPage() {
{colorSchemeOptions.map((option) => (
<button
key={option.value}
aria-label={t(`preferences.color${option.value.charAt(0).toUpperCase() + option.value.slice(1)}` as Parameters<typeof t>[0])}
aria-label={t(`preferences.color${option.value.charAt(0).toUpperCase() + option.value.slice(1)}` as Parameters<typeof t>[0])} // NOSONAR - dynamic i18n key requires assertion
aria-pressed={currentScheme === option.value}
className={`size-9 rounded-full transition-all active:scale-90 flex items-center justify-center ${
currentScheme === option.value
Expand Down
456 changes: 248 additions & 208 deletions apps/web/app/(app)/upgrade/page.tsx

Large diffs are not rendered by default.

44 changes: 23 additions & 21 deletions apps/web/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ function extractError(err: unknown, t: ReturnType<typeof useTranslations>): stri
return backendError ? translateBackendError(backendError, t) : t('auth.genericError')
}

/** Fill code digits from a multi-char string input (typing or paste) */
function fillCodeDigits(
startIndex: number,
cleanValue: string,
current: string[],
): { digits: string[]; nextFocusIndex: number } {
const chars = cleanValue.split('')
const newDigits = [...current]
for (let i = 0; i < chars.length && startIndex + i < 6; i++) {
newDigits[startIndex + i] = chars[i] ?? ''
}
const nextFocusIndex = Math.min(startIndex + chars.length, 5)
return { digits: newDigits, nextFocusIndex }
}

// ---------------------------------------------------------------------------
// Sub-components (S3776: extracted to reduce cognitive complexity)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -194,7 +209,7 @@ function CodeStep({
<div className="flex justify-center gap-1.5 sm:gap-2">
{codeDigits.map((digit, index) => (
<input
key={`code-digit-${index}`}
key={`code-digit-${index}`} // NOSONAR - fixed-length array where position is identity
ref={(el) => { codeInputRefs.current[index] = el }}
value={digit}
data-code-index={index}
Expand Down Expand Up @@ -419,18 +434,11 @@ export default function LoginPage() {
const cleanValue = value.replaceAll(/\D/g, '')

if (cleanValue.length > 1) {
const digits = cleanValue.split('')
const newCodeDigits = [...codeDigits]
for (let i = 0; i < digits.length && index + i < 6; i++) {
newCodeDigits[index + i] = digits[i] ?? ''
}
const { digits: newCodeDigits, nextFocusIndex } = fillCodeDigits(index, cleanValue, codeDigits)
setCodeDigits(newCodeDigits)
const nextIndex = Math.min(index + digits.length, 5)
codeInputRefs.current[nextIndex]?.focus()

codeInputRefs.current[nextFocusIndex]?.focus()
if (newCodeDigits.join('').length === 6) {
const fullCode = newCodeDigits.join('')
setTimeout(() => verifyCode(fullCode), 0)
setTimeout(() => verifyCode(newCodeDigits.join('')), 0)
}
return
}
Expand All @@ -448,17 +456,11 @@ export default function LoginPage() {
event.preventDefault()
const pasted = event.clipboardData.getData('text').replaceAll(/\D/g, '')
if (!pasted) return
const digits = pasted.slice(0, 6).split('')
const newCodeDigits = ['', '', '', '', '', '']
for (let i = 0; i < digits.length && i < 6; i++) {
newCodeDigits[i] = digits[i] ?? ''
}
const { digits: newCodeDigits, nextFocusIndex } = fillCodeDigits(0, pasted.slice(0, 6), ['', '', '', '', '', ''])
setCodeDigits(newCodeDigits)
const focusIndex = Math.min(digits.length, 5)
codeInputRefs.current[focusIndex]?.focus()
if (digits.length === 6) {
const fullCode = newCodeDigits.join('')
setTimeout(() => verifyCode(fullCode), 0)
codeInputRefs.current[nextFocusIndex]?.focus()
if (pasted.length >= 6) {
setTimeout(() => verifyCode(newCodeDigits.join('')), 0)
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/chat/breakdown-suggestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export function BreakdownSuggestion({
className="w-8 bg-transparent text-[11px] text-text-secondary text-center outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
/>
<span className="text-[11px] text-text-muted">
{t(`habits.form.unit${habit.frequencyUnit}` as Parameters<typeof t>[0])}
{t(`habits.form.unit${habit.frequencyUnit}` as Parameters<typeof t>[0])} {/* NOSONAR - dynamic i18n key requires assertion */}
</span>
</>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/habits/description-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function DescriptionViewer({
setRenderedHtml('')
return
}
const raw = marked.parse(description, { async: false }) as string
const raw = marked.parse(description, { async: false }) as string // NOSONAR - marked.parse with async:false returns string but typed as string | Promise<string>
setRenderedHtml(DOMPurify.sanitize(raw))
}, [open, description])

Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/habits/habit-calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function HabitCalendar({ habitId, logs: externalLogs }: Readonly<HabitCal
weekStartsOn === 1
? [...sundayFirst.slice(1), sundayFirst[0]]
: sundayFirst
return keys.map((k) => t(`dates.daysShort.${k}` as Parameters<typeof t>[0]).charAt(0))
return keys.map((k) => t(`dates.daysShort.${k}` as Parameters<typeof t>[0]).charAt(0)) // NOSONAR - dynamic i18n key requires assertion
}, [weekStartsOn, t])

const calendarDays = useMemo(() => {
Expand Down
23 changes: 12 additions & 11 deletions apps/web/components/habits/habit-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,17 @@ function computeFrequencyLabel(
if (isFlexible) {
return t('habits.frequency.flexibleLabel', {
n: frequencyQuantity ?? 1,
unit: t(`habits.form.unit${frequencyUnit}` as Parameters<typeof t>[0]),
unit: t(`habits.form.unit${frequencyUnit}` as Parameters<typeof t>[0]), // NOSONAR - dynamic i18n key requires assertion
})
}
if (frequencyQuantity === 1 && days.length > 0) {
return days
.map((day) => t(`dates.daysShort.${day.toLowerCase()}` as Parameters<typeof t>[0]))
.map((day) => t(`dates.daysShort.${day.toLowerCase()}` as Parameters<typeof t>[0])) // NOSONAR - dynamic i18n key requires assertion
.join(', ')
}
if (frequencyQuantity === 1)
return t(`habits.frequency.every${frequencyUnit}` as Parameters<typeof t>[0])
return t(`habits.frequency.everyN${frequencyUnit}s` as Parameters<typeof t>[0], {
return t(`habits.frequency.every${frequencyUnit}` as Parameters<typeof t>[0]) // NOSONAR - dynamic i18n key requires assertion
return t(`habits.frequency.everyN${frequencyUnit}s` as Parameters<typeof t>[0], { // NOSONAR - dynamic i18n key requires assertion
n: frequencyQuantity ?? 1,
})
}
Expand All @@ -127,7 +127,7 @@ function computeFlexibleProgressLabel(
const target = habit.flexibleTarget ?? habit.frequencyQuantity ?? 1
const done = habit.flexibleCompleted ?? 0
const unit = habit.frequencyUnit
? t(`habits.form.unit${habit.frequencyUnit}` as Parameters<typeof t>[0])
? t(`habits.form.unit${habit.frequencyUnit}` as Parameters<typeof t>[0]) // NOSONAR - dynamic i18n key requires assertion
: ''
return t('habits.frequency.flexibleProgress', { done, target, unit })
}
Expand Down Expand Up @@ -548,11 +548,12 @@ function SimpleLogButton({ isChild, isDoneForRange, status, justCompleted, habit
}
}, [isDoneForRange, onUnlog, onLog])

const borderClass = isDoneForRange
? 'log-btn-done text-white'
: status === 'overdue'
? 'border-2 border-red-500/20 hover:border-red-500/40'
: 'border-2 border-border-emphasis hover:border-primary/35'
let borderClass = 'border-2 border-border-emphasis hover:border-primary/35'
if (isDoneForRange) {
borderClass = 'log-btn-done text-white'
} else if (status === 'overdue') {
borderClass = 'border-2 border-red-500/20 hover:border-red-500/40'
}

return (
<button
Expand Down Expand Up @@ -799,6 +800,7 @@ function ActionsMenuPanel({ panelRef, menuPosition, menuOpensUp, showAddSubHabit
transform: menuOpensUp ? 'translateY(-100%)' : 'none',
}}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => { if (e.key === 'Escape') closeMenu() }}
>
{showAddSubHabit && depth < maxHabitDepth - 1 && (
<button
Expand Down Expand Up @@ -1085,7 +1087,6 @@ export function HabitCard({
<>
<div style={isChild ? indentStyle : undefined}>
<article
role="button"
className={articleClassName}
tabIndex={0}
onClick={handleCardClick}
Expand Down
Loading
Loading