diff --git a/backend/api/settings.py b/backend/api/settings.py index 5e3372e16..56105b46e 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -125,7 +125,7 @@ class ThrottleScopes: ThrottleScopes.PASSWORD_RESET.key: "10/hour", ThrottleScopes.EVENT_CREATION.key: "25/hour", ThrottleScopes.AVAILABILITY_ADD.key: "50/hour", - ThrottleScopes.CODE_CHECK.key: "50/hour", + ThrottleScopes.CODE_CHECK.key: "10/min", }, } @@ -195,7 +195,7 @@ class ThrottleScopes: "class": "logging.handlers.RotatingFileHandler", "filename": f"{LOG_DIR}/django.log", "formatter": "verbose", - "level": "DEBUG", + "level": "DEBUG" if DEBUG else "INFO", "maxBytes": 1024 * 1024 * 5, # 5 MB "backupCount": 5, }, diff --git a/frontend/src/app/(event)/[event-code]/edit/loading.tsx b/frontend/src/app/(event)/[event-code]/edit/loading.tsx index 122b9bbb2..6ce716f29 100644 --- a/frontend/src/app/(event)/[event-code]/edit/loading.tsx +++ b/frontend/src/app/(event)/[event-code]/edit/loading.tsx @@ -6,7 +6,7 @@ export default function Loading() {
-
+
@@ -15,11 +15,12 @@ export default function Loading() {
-
+
-
-
-
+
+
+
+
); diff --git a/frontend/src/app/(event)/[event-code]/loading.tsx b/frontend/src/app/(event)/[event-code]/loading.tsx index 9f8912bc4..835d0486e 100644 --- a/frontend/src/app/(event)/[event-code]/loading.tsx +++ b/frontend/src/app/(event)/[event-code]/loading.tsx @@ -8,16 +8,15 @@ export default function Loading() {
-
+
-
+
-
diff --git a/frontend/src/app/(event)/[event-code]/painting/loading.tsx b/frontend/src/app/(event)/[event-code]/painting/loading.tsx index 28a1cea40..948551c5c 100644 --- a/frontend/src/app/(event)/[event-code]/painting/loading.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/loading.tsx @@ -8,20 +8,21 @@ export default function Loading() {
-
+
-
+
-
-
+
+ +
); diff --git a/frontend/src/app/dashboard/loading.tsx b/frontend/src/app/dashboard/loading.tsx index a0abdf5b1..d71e5b2d9 100644 --- a/frontend/src/app/dashboard/loading.tsx +++ b/frontend/src/app/dashboard/loading.tsx @@ -8,7 +8,7 @@ export default function Loading() {
-
+
{/* 2. Skeleton for the Event Grid */} @@ -17,7 +17,7 @@ export default function Loading() { {[1, 2, 3].map((i) => (
))}
diff --git a/frontend/src/app/settings/layout.tsx b/frontend/src/app/settings/(submenus)/layout.tsx similarity index 100% rename from frontend/src/app/settings/layout.tsx rename to frontend/src/app/settings/(submenus)/layout.tsx diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/(submenus)/page.tsx similarity index 100% rename from frontend/src/app/settings/page.tsx rename to frontend/src/app/settings/(submenus)/page.tsx diff --git a/frontend/src/app/settings/remove/page.tsx b/frontend/src/app/settings/(submenus)/remove/page.tsx similarity index 100% rename from frontend/src/app/settings/remove/page.tsx rename to frontend/src/app/settings/(submenus)/remove/page.tsx diff --git a/frontend/src/app/settings/security/page.tsx b/frontend/src/app/settings/(submenus)/security/page.tsx similarity index 100% rename from frontend/src/app/settings/security/page.tsx rename to frontend/src/app/settings/(submenus)/security/page.tsx diff --git a/frontend/src/app/settings/loading.tsx b/frontend/src/app/settings/loading.tsx new file mode 100644 index 000000000..5df2a7350 --- /dev/null +++ b/frontend/src/app/settings/loading.tsx @@ -0,0 +1,21 @@ +import HeaderSpacer from "@/features/header/components/header-spacer"; + +export default function Loading() { + return ( +
+ + +
+

Account Settings

+

+ Manage your account settings and preferences! +

+
+ +
+
+
+
+
+ ); +} diff --git a/frontend/src/components/text-input-field.tsx b/frontend/src/components/text-input-field.tsx index 9bc27d9c4..b6df1de8d 100644 --- a/frontend/src/components/text-input-field.tsx +++ b/frontend/src/components/text-input-field.tsx @@ -1,6 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import * as Collapsible from "@radix-ui/react-collapsible"; import { EyeClosedIcon, EyeIcon, TriangleAlertIcon } from "lucide-react"; import PasswordCriteria from "@/features/auth/components/password-criteria"; @@ -192,14 +191,10 @@ export default function TextInputField(props: TextInputFieldProps) { )}
- {/* Password Criteria */} - - -
- -
-
-
+
); } diff --git a/frontend/src/features/auth/components/password-criteria.tsx b/frontend/src/features/auth/components/password-criteria.tsx index 932d13ae8..153b58a80 100644 --- a/frontend/src/features/auth/components/password-criteria.tsx +++ b/frontend/src/features/auth/components/password-criteria.tsx @@ -1,44 +1,69 @@ +import { AnimatePresence, motion } from "framer-motion"; import { CheckIcon, XIcon } from "lucide-react"; import { cn } from "@/lib/utils/classname"; type PasswordCriteriaProps = { + open: boolean; criteria: { [key: string]: boolean }; }; -export default function PasswordCriteria(props: PasswordCriteriaProps) { - const allCriteriaMet = Object.values(props.criteria).every((value) => value); +export default function PasswordCriteria({ + open, + criteria, +}: PasswordCriteriaProps) { + const allCriteriaMet = Object.values(criteria).every((value) => value); const iconClass = "w-4 h-4"; return ( -
- {allCriteriaMet ? ( -
- - Password is strong! -
- ) : ( - <> - Your password must: - {Object.entries(props.criteria).map(([key, value], index) => ( -
- {value ? ( - - ) : ( - - )} - {key} -
- ))} - + + {open && ( + +
+ + + {allCriteriaMet ? ( +
+ + Your password is strong! +
+ ) : ( + <> + Your password must: + {Object.entries(criteria).map(([key, value], index) => ( +
+ {value ? ( + + ) : ( + + )} + {key} +
+ ))} + + )} +
+
+
+
)} -
+ ); } diff --git a/frontend/src/features/drawer/components/base.tsx b/frontend/src/features/drawer/components/base.tsx index d323b5f82..04d03d6ce 100644 --- a/frontend/src/features/drawer/components/base.tsx +++ b/frontend/src/features/drawer/components/base.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { XIcon } from "lucide-react"; import { Drawer } from "vaul"; @@ -35,11 +35,21 @@ export default function BaseDrawer({ const contentRef = useRef(null); const wasDraggingRef = useRef(false); + const animatingTimeoutRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const [isAnimating, setIsAnimating] = useState(false); useVaulStickyFooter(contentRef, isDragging || isAnimating); + // Animating timeout cleanup + useEffect(() => { + return () => { + if (animatingTimeoutRef.current) { + clearTimeout(animatingTimeoutRef.current); + } + }; + }, []); + /** * CONDITIONAL PROPS BASED ON VARIANT * @@ -188,9 +198,17 @@ export default function BaseDrawer({ onClick={() => { if (wasDraggingRef.current) return; if (isPill) { + if (animatingTimeoutRef.current) { + clearTimeout(animatingTimeoutRef.current); + } + setIsAnimating(true); setSnap(snapPoints?.[1] ?? null); - setIsAnimating(false); + // Let the footer observer update before setting isAnimating to false + animatingTimeoutRef.current = setTimeout(() => { + setIsAnimating(false); + animatingTimeoutRef.current = null; + }, 0); } }} className={cn( diff --git a/frontend/src/features/event/editor/constants.ts b/frontend/src/features/event/editor/constants.ts new file mode 100644 index 000000000..f68f9c05d --- /dev/null +++ b/frontend/src/features/event/editor/constants.ts @@ -0,0 +1,4 @@ +const MAX_DAYS = 64; +export const MAX_DURATION_MS = MAX_DAYS * 24 * 60 * 60 * 1000; +export const MAX_DURATION = `${MAX_DAYS} days`; +export const MAX_TITLE_LENGTH = 50; diff --git a/frontend/src/features/event/editor/validate-data.ts b/frontend/src/features/event/editor/validate-data.ts index f201de55f..503c35f41 100644 --- a/frontend/src/features/event/editor/validate-data.ts +++ b/frontend/src/features/event/editor/validate-data.ts @@ -1,14 +1,12 @@ import { EventInformation, WeekdayRange } from "@/core/event/types"; import { findRangeFromWeekdayArray } from "@/core/event/weekday-utils"; +import { + MAX_DURATION_MS, + MAX_TITLE_LENGTH, +} from "@/features/event/editor/constants"; import { EventEditorType } from "@/features/event/editor/types"; import { MESSAGES } from "@/lib/messages"; -const MAX_DAYS = 64; -export const MAX_DURATION_MS = MAX_DAYS * 24 * 60 * 60 * 1000; -export const MAX_DURATION = `${MAX_DAYS} days`; - -export const MAX_TITLE_LENGTH = 50; - export async function validateEventData( editorType: EventEditorType, data: EventInformation, diff --git a/frontend/src/features/system-feedback/dialog/components/confirmation.tsx b/frontend/src/features/system-feedback/dialog/components/confirmation.tsx index 68fb0cb77..648a3db0c 100644 --- a/frontend/src/features/system-feedback/dialog/components/confirmation.tsx +++ b/frontend/src/features/system-feedback/dialog/components/confirmation.tsx @@ -4,6 +4,7 @@ import ActionButton from "@/features/button/components/action"; import BaseModal from "@/features/system-feedback/dialog/components/base"; import { DIALOG_CONFIG } from "@/features/system-feedback/dialog/config"; import { ConfirmationDialogProps } from "@/features/system-feedback/dialog/props"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; import { cn } from "@/lib/utils/classname"; export default function ConfirmationDialog({ @@ -26,6 +27,11 @@ export default function ConfirmationDialog({ const isControlled = controlledOpen !== undefined; const open = isControlled ? controlledOpen : uncontrolledOpen; + const isMobile = useCheckMobile(); + if (isMobile && !asNestedDrawer) { + asNestedDrawer = true; + } + const handleOpenChange = useCallback( (newOpen: boolean) => { if (!isControlled) { diff --git a/frontend/src/features/system-feedback/toast/base.tsx b/frontend/src/features/system-feedback/toast/base.tsx index ece3bf848..6c1542db6 100644 --- a/frontend/src/features/system-feedback/toast/base.tsx +++ b/frontend/src/features/system-feedback/toast/base.tsx @@ -107,7 +107,8 @@ export default function BaseToast({ } }} className={cn( - "z-10 col-start-3 row-span-2 flex h-6 w-6 items-center justify-center rounded-full", + "z-10 col-start-3 row-span-2 flex h-6 w-6", + "cursor-pointer items-center justify-center rounded-full", "hover:bg-black/20 focus:outline-none focus:ring-2 focus:ring-white/50", !isPersistent && "opacity-0 focus-visible:opacity-100 group-hover:opacity-100", diff --git a/frontend/src/features/version-history/data.ts b/frontend/src/features/version-history/data.ts index 78cdf38cd..d4f833d97 100644 --- a/frontend/src/features/version-history/data.ts +++ b/frontend/src/features/version-history/data.ts @@ -183,6 +183,17 @@ export function getVersionHistoryData(): VersionHistoryData { "Removed default dates and times from the new event page", ], }, + { + version: "v0.4.5", + releaseDate: { year: 2026, month: 5, day: 29 }, + changes: [ + "Added animations to the password strength criteria", + "Updated confirmation dialogs to display as drawers on mobile", + "Updated loading page skeletons to be consistent with new page layouts", + "Updated the authentication code checking rate limit to prevent lockouts", + "Fixed an issue where the mobile results page drawer would not display the footer when opened", + ], + }, ], }, ]; diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts new file mode 100644 index 000000000..eb19293fc --- /dev/null +++ b/frontend/src/global.d.ts @@ -0,0 +1,2 @@ +// Silences an error when importing globals.css in layout.tsx +declare module "*.css"; diff --git a/frontend/src/lib/messages.ts b/frontend/src/lib/messages.ts index cef5a4f48..f7f4c9b84 100644 --- a/frontend/src/lib/messages.ts +++ b/frontend/src/lib/messages.ts @@ -2,7 +2,7 @@ import { MAX_DEFAULT_NAME_LENGTH } from "@/features/account/constants"; import { MAX_DURATION, MAX_TITLE_LENGTH, -} from "@/features/event/editor/validate-data"; +} from "@/features/event/editor/constants"; export const MESSAGES = { // generic errors