heartraite 1.0.214 → 1.0.216

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.
@@ -43,10 +43,6 @@ export declare enum CloudFunction {
43
43
  CREATE_REPORT = "report/create-report",
44
44
  GET_SELF_AWARENESS = "sa/get-sa",
45
45
  ADD_IMAGES = "storage/add-images",
46
- GET_PRODUCTS = "stripe/get-products",
47
- STRIPE_WEBWOOK = "stripe/webhook",
48
- CREATE_BILLING_PORTAL = "stripe/create-billing-portal",
49
- CREATE_CHECKOUT_SESSION = "stripe/create-checkout-session",
50
46
  GET_USER_SURVEYS = "survey/get-user-surveys",
51
47
  SUBMIT_USER_SURVEY = "survey/submit-user-survey",
52
48
  REGISTER_ONBOARDING = "user/register-onboarding",
@@ -47,10 +47,6 @@ var CloudFunction;
47
47
  CloudFunction["CREATE_REPORT"] = "report/create-report";
48
48
  CloudFunction["GET_SELF_AWARENESS"] = "sa/get-sa";
49
49
  CloudFunction["ADD_IMAGES"] = "storage/add-images";
50
- CloudFunction["GET_PRODUCTS"] = "stripe/get-products";
51
- CloudFunction["STRIPE_WEBWOOK"] = "stripe/webhook";
52
- CloudFunction["CREATE_BILLING_PORTAL"] = "stripe/create-billing-portal";
53
- CloudFunction["CREATE_CHECKOUT_SESSION"] = "stripe/create-checkout-session";
54
50
  CloudFunction["GET_USER_SURVEYS"] = "survey/get-user-surveys";
55
51
  CloudFunction["SUBMIT_USER_SURVEY"] = "survey/submit-user-survey";
56
52
  CloudFunction["REGISTER_ONBOARDING"] = "user/register-onboarding";
@@ -8,7 +8,7 @@
8
8
  * PENDING → user expressed interest, team hasn't reached
9
9
  * out yet. Initial state on submit.
10
10
  * FOLLOWED_UP → team emailed the user. v1 manual; v2 may be
11
- * automated after a Stripe charge.
11
+ * automated after payment.
12
12
  * BOOKED → consultation session scheduled (v2 only — v1
13
13
  * tracks this only via team-internal notes).
14
14
  * COMPLETED → session happened.
@@ -11,7 +11,7 @@ exports.ConsultationInterestStatus = void 0;
11
11
  * PENDING → user expressed interest, team hasn't reached
12
12
  * out yet. Initial state on submit.
13
13
  * FOLLOWED_UP → team emailed the user. v1 manual; v2 may be
14
- * automated after a Stripe charge.
14
+ * automated after payment.
15
15
  * BOOKED → consultation session scheduled (v2 only — v1
16
16
  * tracks this only via team-internal notes).
17
17
  * COMPLETED → session happened.
@@ -6,7 +6,6 @@ export declare enum FirestoreCollection {
6
6
  MESSAGES = "messages",
7
7
  USERS = "users",
8
8
  RATING = "ratings",
9
- STRIPE_SUBSCRIPTIONS = "stripe_subscriptions",
10
9
  REPORTS = "reports",
11
10
  MATCHES = "matches",
12
11
  REFERRALS = "referrals",
@@ -10,7 +10,6 @@ var FirestoreCollection;
10
10
  FirestoreCollection["MESSAGES"] = "messages";
11
11
  FirestoreCollection["USERS"] = "users";
12
12
  FirestoreCollection["RATING"] = "ratings";
13
- FirestoreCollection["STRIPE_SUBSCRIPTIONS"] = "stripe_subscriptions";
14
13
  FirestoreCollection["REPORTS"] = "reports";
15
14
  FirestoreCollection["MATCHES"] = "matches";
16
15
  FirestoreCollection["REFERRALS"] = "referrals";
@@ -8,5 +8,6 @@ export declare const useTypewriter: ({ text, typingSpeed, initialDelay, blinking
8
8
  displayedText: string;
9
9
  showDot: boolean;
10
10
  isComplete: boolean;
11
+ revealAll: () => void;
11
12
  };
12
13
  export {};
@@ -6,56 +6,63 @@ const useTypewriter = ({ text, typingSpeed = 50, initialDelay = 1000, blinkingSp
6
6
  const [isComplete, setIsComplete] = (0, react_1.useState)(false);
7
7
  const [displayedText, setDisplayedText] = (0, react_1.useState)("");
8
8
  const [showDot, setShowDot] = (0, react_1.useState)(false);
9
+ const timersRef = (0, react_1.useRef)({ blinking: null, timeouts: new Set() });
10
+ const clearTimers = (0, react_1.useCallback)(() => {
11
+ const timers = timersRef.current;
12
+ if (timers.blinking)
13
+ clearInterval(timers.blinking);
14
+ timers.blinking = null;
15
+ timers.timeouts.forEach(clearTimeout);
16
+ timers.timeouts.clear();
17
+ }, []);
9
18
  (0, react_1.useEffect)(() => {
10
19
  if (!text || text.length === 0) {
11
- setDisplayedText(""); // No text to display
12
- setIsComplete(true); // Mark typing as complete immediately
13
- setShowDot(false); // No blinking dot for empty text
14
- return; // Exit early if text is empty
20
+ setDisplayedText("");
21
+ setIsComplete(true);
22
+ setShowDot(false);
23
+ return;
15
24
  }
16
- let blinkingInterval = null;
17
- let typingTimeout = null;
18
- // Initialize state
19
- setDisplayedText(""); // Clear the displayed text
20
- setShowDot(true); // Start with a blinking dot
21
- setIsComplete(false); // Reset completion flag
22
- // Start blinking dot effect
23
- blinkingInterval = setInterval(() => {
25
+ const timers = timersRef.current;
26
+ const schedule = (fn, ms) => {
27
+ const id = setTimeout(() => {
28
+ timers.timeouts.delete(id);
29
+ fn();
30
+ }, ms);
31
+ timers.timeouts.add(id);
32
+ };
33
+ setDisplayedText("");
34
+ setShowDot(true);
35
+ setIsComplete(false);
36
+ timers.blinking = setInterval(() => {
24
37
  setShowDot((prev) => !prev);
25
38
  }, blinkingSpeed);
26
- // Start typing after the initial delay
27
- typingTimeout = setTimeout(() => {
28
- if (blinkingInterval) {
29
- clearInterval(blinkingInterval); // Stop blinking before typing
30
- setShowDot(false); // Hide blinking dot
31
- }
32
- // Typing effect
33
- let currentIndex = 0;
39
+ schedule(() => {
40
+ if (timers.blinking)
41
+ clearInterval(timers.blinking);
42
+ timers.blinking = null;
43
+ setShowDot(false);
44
+ let currentIndex = 1;
34
45
  const typeNextChar = () => {
35
46
  if (currentIndex < text.length) {
36
- const charToAdd = text[currentIndex];
37
- setDisplayedText((prev) => prev + charToAdd); // Append character
38
47
  currentIndex++;
39
- setTimeout(typeNextChar, typingSpeed); // Schedule next character
48
+ setDisplayedText(text.slice(0, currentIndex));
49
+ schedule(typeNextChar, typingSpeed);
40
50
  }
41
51
  else {
42
- setIsComplete(true); // Mark as complete
52
+ setIsComplete(true);
43
53
  }
44
54
  };
45
- // Trigger the first character manually
46
55
  setDisplayedText(text.charAt(0));
47
- currentIndex = 1;
48
- // Schedule the rest of the characters
49
- setTimeout(typeNextChar, typingSpeed);
56
+ schedule(typeNextChar, typingSpeed);
50
57
  }, initialDelay);
51
- // Cleanup on unmount or dependencies change
52
- return () => {
53
- if (blinkingInterval)
54
- clearInterval(blinkingInterval);
55
- if (typingTimeout)
56
- clearTimeout(typingTimeout);
57
- };
58
- }, [text, typingSpeed, initialDelay, blinkingSpeed]);
59
- return { displayedText, showDot, isComplete };
58
+ return clearTimers;
59
+ }, [text, typingSpeed, initialDelay, blinkingSpeed, clearTimers]);
60
+ const revealAll = (0, react_1.useCallback)(() => {
61
+ clearTimers();
62
+ setDisplayedText(text ?? "");
63
+ setShowDot(false);
64
+ setIsComplete(true);
65
+ }, [text, clearTimers]);
66
+ return { displayedText, showDot, isComplete, revealAll };
60
67
  };
61
68
  exports.useTypewriter = useTypewriter;
@@ -68,4 +68,43 @@ describe("useTypewriter", () => {
68
68
  });
69
69
  expect(result.current.displayedText).toBe(sampleText.slice(0, 2));
70
70
  });
71
+ it("reveals the full text immediately when revealAll is called mid-typing", () => {
72
+ const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)(defaultOptions));
73
+ (0, react_1.act)(() => {
74
+ jest.advanceTimersByTime(defaultOptions.initialDelay + 100);
75
+ });
76
+ expect(result.current.isComplete).toBe(false);
77
+ (0, react_1.act)(() => {
78
+ result.current.revealAll();
79
+ });
80
+ expect(result.current.displayedText).toBe(sampleText);
81
+ expect(result.current.isComplete).toBe(true);
82
+ expect(result.current.showDot).toBe(false);
83
+ (0, react_1.act)(() => {
84
+ jest.advanceTimersByTime(sampleText.length * defaultOptions.typingSpeed);
85
+ });
86
+ expect(result.current.displayedText).toBe(sampleText);
87
+ });
88
+ it("reveals the full text when revealAll is called before typing starts", () => {
89
+ const { result } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)(defaultOptions));
90
+ (0, react_1.act)(() => {
91
+ result.current.revealAll();
92
+ });
93
+ expect(result.current.displayedText).toBe(sampleText);
94
+ expect(result.current.isComplete).toBe(true);
95
+ expect(result.current.showDot).toBe(false);
96
+ (0, react_1.act)(() => {
97
+ jest.advanceTimersByTime(defaultOptions.initialDelay * 2);
98
+ });
99
+ expect(result.current.displayedText).toBe(sampleText);
100
+ expect(result.current.showDot).toBe(false);
101
+ });
102
+ it("stops all timers on unmount", () => {
103
+ const { unmount } = (0, react_1.renderHook)(() => (0, useTypewriter_1.useTypewriter)(defaultOptions));
104
+ (0, react_1.act)(() => {
105
+ jest.advanceTimersByTime(defaultOptions.initialDelay + 100);
106
+ });
107
+ unmount();
108
+ expect(jest.getTimerCount()).toBe(0);
109
+ });
71
110
  });
@@ -1,6 +1,6 @@
1
1
  import { CloudFunction } from "../enum";
2
- import { AddImagesRequest, AuthenticateBankIDRequest, CollectBankIDRequest, CreateBillingPortalRequest, CreateCheckoutSessionRequest, CreateLikeRequest, CreatePLSInputRequest, CreatePLSSubmissionRequest, CreateReportRequest, DeleteUserRequest, GetCARequest, GetMatchableUserRequest, GetMatchCheckRequest, GetMatchesRequest, GetAdvancedMatchRequest, GetMessagesRequest, GetPLSSubmissionRequest, GetProductsRequest, GetSelfAwarenessRequest, GetUserRequest, HandleLastMessageSeenRequest, HandleLikeSeenRequest, HandleMatchSeenRequest, RegisterOnboardingRequest, RegisterUserRequest, RemoveMatchRequest, SendMessageRequest, SubmitAnswerRequest, SubmitCategoryFeedbackRequest, SubmitPLSCategoryFeedbackRequest, SubmitPLSDemographicsRequest, UpdatePLSAnswerRequest, UpdateQuestionsRequest, UpdateUserRequest, SubmitFeedbackRequest, ExpressConsultationInterestRequest, GetMyConsultationInterestRequest, GetConsultationInterestsRequest, UpdateConsultationInterestRequest, SyncAffiliateRequest, JoinEventRequest, GetEventRequest, GetEventMatchRequest, SendEventFeedbackRequest, GetEventParticipantsRequest } from "./request.types";
3
- import { AddImagesResponse, AuthenticateBankIDResponse, CollectBankIDResponse, CreateBillingPortalResponse, CreateCheckoutSessionResponse, CreateLikeResponse, CreatePLSInputResponse, CreatePLSSubmissionResponse, CreateReportResponse, DeleteUserResponse, GetCAResponse, GetMatchableUserResponse, GetMatchableUsersResponse, GetMatchCheckResponse, GetMatchesResponse, GetAdvancedMatchResponse, GetMessagesResponse, GetPLSSubmissionResponse, GetProductsResponse, GetSelfAwarenessReponse, GetUserResponse, HandleLastMessageSeenResponse, HandleLikeSeenResponse, HandleMatchSeenResponse, RegisterOnboardingResponse, RegisterUserResponse, RemoveMatchResponse, SendMessageResponse, SubmitAnswerResponse, SubmitCategoryFeedbackResponse, SubmitPLSCategoryFeedbackResponse, SubmitPLSDemographicsResponse, UpdatePLSAnswerResponse, UpdateQuestionsResponse, UpdateUserResponse, SubmitFeedbackResponse, ExpressConsultationInterestResponse, GetMyConsultationInterestResponse, GetConsultationInterestsResponse, UpdateConsultationInterestResponse, SyncAffiliateResponse, GetUserEventsResponse, JoinEventResponse, GetEventResponse, GetEventMatchResponse, SendEventFeedbackResponse, GetEventParticipantsResponse } from "./response.types";
2
+ import { AddImagesRequest, AuthenticateBankIDRequest, CollectBankIDRequest, CreateLikeRequest, CreatePLSInputRequest, CreatePLSSubmissionRequest, CreateReportRequest, DeleteUserRequest, GetCARequest, GetMatchableUserRequest, GetMatchCheckRequest, GetMatchesRequest, GetAdvancedMatchRequest, GetMessagesRequest, GetPLSSubmissionRequest, GetSelfAwarenessRequest, GetUserRequest, HandleLastMessageSeenRequest, HandleLikeSeenRequest, HandleMatchSeenRequest, RegisterOnboardingRequest, RegisterUserRequest, RemoveMatchRequest, SendMessageRequest, SubmitAnswerRequest, SubmitCategoryFeedbackRequest, SubmitPLSCategoryFeedbackRequest, SubmitPLSDemographicsRequest, UpdatePLSAnswerRequest, UpdateQuestionsRequest, UpdateUserRequest, SubmitFeedbackRequest, ExpressConsultationInterestRequest, GetMyConsultationInterestRequest, GetConsultationInterestsRequest, UpdateConsultationInterestRequest, SyncAffiliateRequest, JoinEventRequest, GetEventRequest, GetEventMatchRequest, SendEventFeedbackRequest, GetEventParticipantsRequest } from "./request.types";
3
+ import { AddImagesResponse, AuthenticateBankIDResponse, CollectBankIDResponse, CreateLikeResponse, CreatePLSInputResponse, CreatePLSSubmissionResponse, CreateReportResponse, DeleteUserResponse, GetCAResponse, GetMatchableUserResponse, GetMatchableUsersResponse, GetMatchCheckResponse, GetMatchesResponse, GetAdvancedMatchResponse, GetMessagesResponse, GetPLSSubmissionResponse, GetSelfAwarenessReponse, GetUserResponse, HandleLastMessageSeenResponse, HandleLikeSeenResponse, HandleMatchSeenResponse, RegisterOnboardingResponse, RegisterUserResponse, RemoveMatchResponse, SendMessageResponse, SubmitAnswerResponse, SubmitCategoryFeedbackResponse, SubmitPLSCategoryFeedbackResponse, SubmitPLSDemographicsResponse, UpdatePLSAnswerResponse, UpdateQuestionsResponse, UpdateUserResponse, SubmitFeedbackResponse, ExpressConsultationInterestResponse, GetMyConsultationInterestResponse, GetConsultationInterestsResponse, UpdateConsultationInterestResponse, SyncAffiliateResponse, GetUserEventsResponse, JoinEventResponse, GetEventResponse, GetEventMatchResponse, SendEventFeedbackResponse, GetEventParticipantsResponse } from "./response.types";
4
4
  /**
5
5
  * @deprecated This function is deprecated.
6
6
  * Use `CloudFunctionTypes` inside application code instead.
@@ -167,18 +167,6 @@ export type CloudFunctionTypes = {
167
167
  payload: AddImagesRequest;
168
168
  response: AddImagesResponse;
169
169
  };
170
- [CloudFunction.GET_PRODUCTS]: {
171
- payload: GetProductsRequest;
172
- response: GetProductsResponse;
173
- };
174
- [CloudFunction.CREATE_BILLING_PORTAL]: {
175
- payload: CreateBillingPortalRequest;
176
- response: CreateBillingPortalResponse;
177
- };
178
- [CloudFunction.CREATE_CHECKOUT_SESSION]: {
179
- payload: CreateCheckoutSessionRequest;
180
- response: CreateCheckoutSessionResponse;
181
- };
182
170
  [CloudFunction.REGISTER_ONBOARDING]: {
183
171
  payload: RegisterOnboardingRequest;
184
172
  response: RegisterOnboardingResponse;
@@ -184,9 +184,6 @@ export type CreateReportRequest = {
184
184
  reason: ReportReason;
185
185
  description?: string;
186
186
  };
187
- export type CreateCheckoutSessionRequest = {
188
- priceId: string;
189
- };
190
187
  export type RegisterOnboardingRequest = {
191
188
  firstName: string;
192
189
  state: string;
@@ -233,5 +230,3 @@ export type GetPLSScoresRequest = undefined;
233
230
  export type AddImagesRequest = {
234
231
  images: string[];
235
232
  };
236
- export type GetProductsRequest = undefined;
237
- export type CreateBillingPortalRequest = undefined;
@@ -4,7 +4,6 @@ import { Event, FullParticipant, FullParticipantWithCS } from "./event.types";
4
4
  import { UserMatch } from "./match.types";
5
5
  import { MatchableUser, MatchCheck } from "./matchmaking.types";
6
6
  import { Message } from "./message.types";
7
- import { StripeProduct } from "./stripe.types";
8
7
  import { User } from "./user.types";
9
8
  import { OutgoingLike } from "./like.types";
10
9
  import { CA } from "./ca.types";
@@ -82,13 +81,6 @@ export type GetSelfAwarenessReponse = SelfAwareness | {
82
81
  processing: true;
83
82
  };
84
83
  export type GetPLSScoresResponse = PLSScore[];
85
- export type CreateBillingPortalResponse = {
86
- url: string;
87
- };
88
- export type CreateCheckoutSessionResponse = {
89
- url: string;
90
- };
91
- export type GetProductsResponse = StripeProduct[];
92
84
  export type RegisterOnboardingResponse = void;
93
85
  export type GetUserResponse = User;
94
86
  export type UpdateUserResponse = User;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heartraite",
3
- "version": "1.0.214",
3
+ "version": "1.0.216",
4
4
  "description": "Heartraite npm package for common functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -43,10 +43,6 @@ export enum CloudFunction {
43
43
  CREATE_REPORT = "report/create-report",
44
44
  GET_SELF_AWARENESS = "sa/get-sa",
45
45
  ADD_IMAGES = "storage/add-images",
46
- GET_PRODUCTS = "stripe/get-products",
47
- STRIPE_WEBWOOK = "stripe/webhook",
48
- CREATE_BILLING_PORTAL = "stripe/create-billing-portal",
49
- CREATE_CHECKOUT_SESSION = "stripe/create-checkout-session",
50
46
  GET_USER_SURVEYS = "survey/get-user-surveys",
51
47
  SUBMIT_USER_SURVEY = "survey/submit-user-survey",
52
48
  REGISTER_ONBOARDING = "user/register-onboarding",
@@ -8,7 +8,7 @@
8
8
  * PENDING → user expressed interest, team hasn't reached
9
9
  * out yet. Initial state on submit.
10
10
  * FOLLOWED_UP → team emailed the user. v1 manual; v2 may be
11
- * automated after a Stripe charge.
11
+ * automated after payment.
12
12
  * BOOKED → consultation session scheduled (v2 only — v1
13
13
  * tracks this only via team-internal notes).
14
14
  * COMPLETED → session happened.
@@ -6,7 +6,6 @@ export enum FirestoreCollection {
6
6
  MESSAGES = "messages",
7
7
  USERS = "users",
8
8
  RATING = "ratings",
9
- STRIPE_SUBSCRIPTIONS = "stripe_subscriptions",
10
9
  REPORTS = "reports",
11
10
  MATCHES = "matches",
12
11
  REFERRALS = "referrals",
@@ -83,4 +83,51 @@ describe("useTypewriter", () => {
83
83
  });
84
84
  expect(result.current.displayedText).toBe(sampleText.slice(0, 2));
85
85
  });
86
+
87
+ it("reveals the full text immediately when revealAll is called mid-typing", () => {
88
+ const { result } = renderHook(() => useTypewriter(defaultOptions));
89
+
90
+ act(() => {
91
+ jest.advanceTimersByTime(defaultOptions.initialDelay + 100);
92
+ });
93
+ expect(result.current.isComplete).toBe(false);
94
+
95
+ act(() => {
96
+ result.current.revealAll();
97
+ });
98
+ expect(result.current.displayedText).toBe(sampleText);
99
+ expect(result.current.isComplete).toBe(true);
100
+ expect(result.current.showDot).toBe(false);
101
+
102
+ act(() => {
103
+ jest.advanceTimersByTime(sampleText.length * defaultOptions.typingSpeed);
104
+ });
105
+ expect(result.current.displayedText).toBe(sampleText);
106
+ });
107
+
108
+ it("reveals the full text when revealAll is called before typing starts", () => {
109
+ const { result } = renderHook(() => useTypewriter(defaultOptions));
110
+
111
+ act(() => {
112
+ result.current.revealAll();
113
+ });
114
+ expect(result.current.displayedText).toBe(sampleText);
115
+ expect(result.current.isComplete).toBe(true);
116
+ expect(result.current.showDot).toBe(false);
117
+
118
+ act(() => {
119
+ jest.advanceTimersByTime(defaultOptions.initialDelay * 2);
120
+ });
121
+ expect(result.current.displayedText).toBe(sampleText);
122
+ expect(result.current.showDot).toBe(false);
123
+ });
124
+
125
+ it("stops all timers on unmount", () => {
126
+ const { unmount } = renderHook(() => useTypewriter(defaultOptions));
127
+ act(() => {
128
+ jest.advanceTimersByTime(defaultOptions.initialDelay + 100);
129
+ });
130
+ unmount();
131
+ expect(jest.getTimerCount()).toBe(0);
132
+ });
86
133
  });
@@ -1,10 +1,10 @@
1
- import { useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  interface UseTypewriterOptions {
4
4
  text: string;
5
- typingSpeed?: number; // Time in ms between each character
6
- initialDelay?: number; // Time in ms before typing starts
7
- blinkingSpeed?: number; // Time in ms for the blinking dot toggle
5
+ typingSpeed?: number;
6
+ initialDelay?: number;
7
+ blinkingSpeed?: number;
8
8
  }
9
9
 
10
10
  export const useTypewriter = ({
@@ -16,63 +16,73 @@ export const useTypewriter = ({
16
16
  const [isComplete, setIsComplete] = useState(false);
17
17
  const [displayedText, setDisplayedText] = useState("");
18
18
  const [showDot, setShowDot] = useState(false);
19
+ const timersRef = useRef<{
20
+ blinking: ReturnType<typeof setInterval> | null;
21
+ timeouts: Set<ReturnType<typeof setTimeout>>;
22
+ }>({ blinking: null, timeouts: new Set() });
23
+
24
+ const clearTimers = useCallback(() => {
25
+ const timers = timersRef.current;
26
+ if (timers.blinking) clearInterval(timers.blinking);
27
+ timers.blinking = null;
28
+ timers.timeouts.forEach(clearTimeout);
29
+ timers.timeouts.clear();
30
+ }, []);
19
31
 
20
32
  useEffect(() => {
21
33
  if (!text || text.length === 0) {
22
- setDisplayedText(""); // No text to display
23
- setIsComplete(true); // Mark typing as complete immediately
24
- setShowDot(false); // No blinking dot for empty text
25
- return; // Exit early if text is empty
34
+ setDisplayedText("");
35
+ setIsComplete(true);
36
+ setShowDot(false);
37
+ return;
26
38
  }
27
39
 
28
- let blinkingInterval: NodeJS.Timeout | null = null;
29
- let typingTimeout: NodeJS.Timeout | null = null;
40
+ const timers = timersRef.current;
41
+ const schedule = (fn: () => void, ms: number) => {
42
+ const id = setTimeout(() => {
43
+ timers.timeouts.delete(id);
44
+ fn();
45
+ }, ms);
46
+ timers.timeouts.add(id);
47
+ };
30
48
 
31
- // Initialize state
32
- setDisplayedText(""); // Clear the displayed text
33
- setShowDot(true); // Start with a blinking dot
34
- setIsComplete(false); // Reset completion flag
49
+ setDisplayedText("");
50
+ setShowDot(true);
51
+ setIsComplete(false);
35
52
 
36
- // Start blinking dot effect
37
- blinkingInterval = setInterval(() => {
53
+ timers.blinking = setInterval(() => {
38
54
  setShowDot((prev) => !prev);
39
55
  }, blinkingSpeed);
40
56
 
41
- // Start typing after the initial delay
42
- typingTimeout = setTimeout(() => {
43
- if (blinkingInterval) {
44
- clearInterval(blinkingInterval); // Stop blinking before typing
45
- setShowDot(false); // Hide blinking dot
46
- }
47
-
48
- // Typing effect
49
- let currentIndex = 0;
57
+ schedule(() => {
58
+ if (timers.blinking) clearInterval(timers.blinking);
59
+ timers.blinking = null;
60
+ setShowDot(false);
50
61
 
62
+ let currentIndex = 1;
51
63
  const typeNextChar = () => {
52
64
  if (currentIndex < text.length) {
53
- const charToAdd = text[currentIndex];
54
- setDisplayedText((prev) => prev + charToAdd); // Append character
55
65
  currentIndex++;
56
- setTimeout(typeNextChar, typingSpeed); // Schedule next character
66
+ setDisplayedText(text.slice(0, currentIndex));
67
+ schedule(typeNextChar, typingSpeed);
57
68
  } else {
58
- setIsComplete(true); // Mark as complete
69
+ setIsComplete(true);
59
70
  }
60
71
  };
61
72
 
62
- // Trigger the first character manually
63
73
  setDisplayedText(text.charAt(0));
64
- currentIndex = 1;
65
-
66
- // Schedule the rest of the characters
67
- setTimeout(typeNextChar, typingSpeed);
74
+ schedule(typeNextChar, typingSpeed);
68
75
  }, initialDelay);
69
76
 
70
- // Cleanup on unmount or dependencies change
71
- return () => {
72
- if (blinkingInterval) clearInterval(blinkingInterval);
73
- if (typingTimeout) clearTimeout(typingTimeout);
74
- };
75
- }, [text, typingSpeed, initialDelay, blinkingSpeed]);
77
+ return clearTimers;
78
+ }, [text, typingSpeed, initialDelay, blinkingSpeed, clearTimers]);
79
+
80
+ const revealAll = useCallback(() => {
81
+ clearTimers();
82
+ setDisplayedText(text ?? "");
83
+ setShowDot(false);
84
+ setIsComplete(true);
85
+ }, [text, clearTimers]);
76
86
 
77
- return { displayedText, showDot, isComplete };
87
+ return { displayedText, showDot, isComplete, revealAll };
78
88
  };
@@ -3,8 +3,6 @@ import {
3
3
  AddImagesRequest,
4
4
  AuthenticateBankIDRequest,
5
5
  CollectBankIDRequest,
6
- CreateBillingPortalRequest,
7
- CreateCheckoutSessionRequest,
8
6
  CreateLikeRequest,
9
7
  CreatePLSInputRequest,
10
8
  CreatePLSSubmissionRequest,
@@ -17,7 +15,6 @@ import {
17
15
  GetAdvancedMatchRequest,
18
16
  GetMessagesRequest,
19
17
  GetPLSSubmissionRequest,
20
- GetProductsRequest,
21
18
  GetSelfAwarenessRequest,
22
19
  GetUserRequest,
23
20
  HandleLastMessageSeenRequest,
@@ -50,8 +47,6 @@ import {
50
47
  AddImagesResponse,
51
48
  AuthenticateBankIDResponse,
52
49
  CollectBankIDResponse,
53
- CreateBillingPortalResponse,
54
- CreateCheckoutSessionResponse,
55
50
  CreateLikeResponse,
56
51
  CreatePLSInputResponse,
57
52
  CreatePLSSubmissionResponse,
@@ -65,7 +60,6 @@ import {
65
60
  GetAdvancedMatchResponse,
66
61
  GetMessagesResponse,
67
62
  GetPLSSubmissionResponse,
68
- GetProductsResponse,
69
63
  GetSelfAwarenessReponse,
70
64
  GetUserResponse,
71
65
  HandleLastMessageSeenResponse,
@@ -293,20 +287,6 @@ export type CloudFunctionTypes = {
293
287
  response: AddImagesResponse;
294
288
  };
295
289
 
296
- // stripe
297
- [CloudFunction.GET_PRODUCTS]: {
298
- payload: GetProductsRequest;
299
- response: GetProductsResponse;
300
- };
301
- [CloudFunction.CREATE_BILLING_PORTAL]: {
302
- payload: CreateBillingPortalRequest;
303
- response: CreateBillingPortalResponse;
304
- };
305
- [CloudFunction.CREATE_CHECKOUT_SESSION]: {
306
- payload: CreateCheckoutSessionRequest;
307
- response: CreateCheckoutSessionResponse;
308
- };
309
-
310
290
  // user
311
291
  [CloudFunction.REGISTER_ONBOARDING]: {
312
292
  payload: RegisterOnboardingRequest;
@@ -214,11 +214,6 @@ export type CreateReportRequest = {
214
214
  description?: string;
215
215
  };
216
216
 
217
- // stripe
218
- export type CreateCheckoutSessionRequest = {
219
- priceId: string;
220
- };
221
-
222
217
  // user
223
218
  export type RegisterOnboardingRequest = {
224
219
  firstName: string;
@@ -264,7 +259,3 @@ export type GetPLSScoresRequest = undefined;
264
259
 
265
260
  // storage
266
261
  export type AddImagesRequest = { images: string[] };
267
-
268
- // stripe
269
- export type GetProductsRequest = undefined;
270
- export type CreateBillingPortalRequest = undefined;
@@ -7,7 +7,6 @@ import { Event, FullParticipant, FullParticipantWithCS } from "./event.types";
7
7
  import { UserMatch } from "./match.types";
8
8
  import { MatchableUser, MatchCheck } from "./matchmaking.types";
9
9
  import { Message } from "./message.types";
10
- import { StripeProduct } from "./stripe.types";
11
10
  import { User } from "./user.types";
12
11
  import { OutgoingLike } from "./like.types";
13
12
  import { CA } from "./ca.types";
@@ -123,11 +122,6 @@ export type GetSelfAwarenessReponse = SelfAwareness | { processing: true };
123
122
  // score
124
123
  export type GetPLSScoresResponse = PLSScore[];
125
124
 
126
- // stripe
127
- export type CreateBillingPortalResponse = { url: string };
128
- export type CreateCheckoutSessionResponse = { url: string };
129
- export type GetProductsResponse = StripeProduct[];
130
-
131
125
  // user
132
126
  export type RegisterOnboardingResponse = void;
133
127
  export type GetUserResponse = User;