@djangocfg/layouts 2.1.548 → 2.1.549

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.548",
3
+ "version": "2.1.549",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -90,11 +90,11 @@
90
90
  "check": "tsc --noEmit"
91
91
  },
92
92
  "peerDependencies": {
93
- "@djangocfg/analytics": "^2.1.548",
94
- "@djangocfg/api": "^2.1.548",
95
- "@djangocfg/devtools": "^2.1.548",
96
- "@djangocfg/i18n": "^2.1.548",
97
- "@djangocfg/ui-core": "^2.1.548",
93
+ "@djangocfg/analytics": "^2.1.549",
94
+ "@djangocfg/api": "^2.1.549",
95
+ "@djangocfg/devtools": "^2.1.549",
96
+ "@djangocfg/i18n": "^2.1.549",
97
+ "@djangocfg/ui-core": "^2.1.549",
98
98
  "@hookform/resolvers": "^5.2.2",
99
99
  "consola": "^3.4.2",
100
100
  "lucide-react": "^0.545.0",
@@ -124,12 +124,12 @@
124
124
  "uuid": "^11.1.1"
125
125
  },
126
126
  "devDependencies": {
127
- "@djangocfg/analytics": "^2.1.548",
128
- "@djangocfg/api": "^2.1.548",
129
- "@djangocfg/devtools": "^2.1.548",
130
- "@djangocfg/i18n": "^2.1.548",
131
- "@djangocfg/typescript-config": "^2.1.548",
132
- "@djangocfg/ui-core": "^2.1.548",
127
+ "@djangocfg/analytics": "^2.1.549",
128
+ "@djangocfg/api": "^2.1.549",
129
+ "@djangocfg/devtools": "^2.1.549",
130
+ "@djangocfg/i18n": "^2.1.549",
131
+ "@djangocfg/typescript-config": "^2.1.549",
132
+ "@djangocfg/ui-core": "^2.1.549",
133
133
  "@storybook/react-vite": "^10.5.0",
134
134
  "@types/node": "^25.9.5",
135
135
  "@types/react": "19.2.15",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { memo, useCallback, useMemo, useState } from 'react';
3
+ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
 
5
5
  import { useAppT } from '@djangocfg/i18n';
6
6
 
@@ -78,6 +78,15 @@ function OTPStepRaw() {
78
78
  return `${local[0]}${'*'.repeat(Math.min(local.length - 1, 5))}@${domain}`;
79
79
  }, [identifier]);
80
80
 
81
+ const autoSubmitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
82
+
83
+ useEffect(
84
+ () => () => {
85
+ if (autoSubmitTimerRef.current) clearTimeout(autoSubmitTimerRef.current);
86
+ },
87
+ []
88
+ );
89
+
81
90
  // Auto-submit on complete (state machine approach)
82
91
  const handleOTPComplete = useCallback(
83
92
  async (completedValue: string) => {
@@ -87,7 +96,11 @@ function OTPStepRaw() {
87
96
  setSubmitState('submitting');
88
97
  const fakeEvent = { preventDefault: () => {} } as React.FormEvent;
89
98
 
90
- setTimeout(async () => {
99
+ // Tracked so leaving the step cancels it. This component unmounts when
100
+ // the user goes back to the identifier, and an uncancelled timer would
101
+ // submit the code they just abandoned.
102
+ autoSubmitTimerRef.current = setTimeout(async () => {
103
+ autoSubmitTimerRef.current = null;
91
104
  try {
92
105
  await handleOTPSubmit(fakeEvent);
93
106
  } finally {
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useState } from 'react';
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
4
 
5
5
  import { AUTH } from '../constants';
6
6
 
@@ -18,13 +18,27 @@ export const useCopyToClipboard = (
18
18
  duration = AUTH.COPY_FEEDBACK_DURATION
19
19
  ): UseCopyToClipboardResult => {
20
20
  const [copied, setCopied] = useState(false);
21
+ // One timer, cleared on unmount. Copying twice in quick succession stacked
22
+ // two, so the first cleared the flag while the second copy was still fresh.
23
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
24
+
25
+ useEffect(
26
+ () => () => {
27
+ if (timerRef.current) clearTimeout(timerRef.current);
28
+ },
29
+ []
30
+ );
21
31
 
22
32
  const copy = useCallback(
23
33
  async (text: string) => {
24
34
  try {
25
35
  await navigator.clipboard.writeText(text);
26
36
  setCopied(true);
27
- setTimeout(() => setCopied(false), duration);
37
+ if (timerRef.current) clearTimeout(timerRef.current);
38
+ timerRef.current = setTimeout(() => {
39
+ timerRef.current = null;
40
+ setCopied(false);
41
+ }, duration);
28
42
  } catch {
29
43
  // Clipboard API not available
30
44
  console.warn('Clipboard API not available');
@@ -77,6 +77,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
77
77
  }) => {
78
78
  const [step, setStep] = useState<AuthStep>(initialStep);
79
79
  const [identifier, setIdentifier] = useState(initialIdentifier);
80
+ const [password, setPassword] = useState('');
80
81
  const [otp, setOtp] = useState(initialOtp);
81
82
  const [isLoading, setIsLoading] = useState(initialLoading);
82
83
  const [acceptedTerms, setAcceptedTerms] = useState(initialAcceptedTerms);
@@ -100,6 +101,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
100
101
  return {
101
102
  // State
102
103
  identifier,
104
+ password,
103
105
  otp,
104
106
  isLoading,
105
107
  acceptedTerms,
@@ -118,6 +120,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
118
120
 
119
121
  // State handlers
120
122
  setIdentifier,
123
+ setPassword,
121
124
  setOtp,
122
125
  setAcceptedTerms,
123
126
  setRememberMe,
@@ -137,6 +140,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
137
140
  // step never changes from under the storyteller. To preview a
138
141
  // different step, render a new story with `step="..."`.
139
142
  handleIdentifierSubmit: async (e) => stop(e),
143
+ handlePasswordSubmit: async (e) => stop(e),
140
144
  handleOTPSubmit: async (e) => stop(e),
141
145
  handleResendOTP: async () => {},
142
146
  handleBackToIdentifier: () => setStep('identifier'),