@revenuecat/purchases-ui-js 2.2.1 → 3.0.1
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/dist/components/countdown/Countdown.stories.svelte +365 -0
- package/dist/components/countdown/Countdown.stories.svelte.d.ts +19 -0
- package/dist/components/countdown/Countdown.svelte +55 -0
- package/dist/components/countdown/Countdown.svelte.d.ts +4 -0
- package/dist/components/countdown/countdown-utils.d.ts +13 -0
- package/dist/components/countdown/countdown-utils.js +66 -0
- package/dist/components/countdown/fixtures/countdown-paywall.d.ts +2 -0
- package/dist/components/countdown/fixtures/countdown-paywall.js +5362 -0
- package/dist/components/package/Package.stories.svelte +1 -0
- package/dist/components/paywall/Node.svelte +2 -0
- package/dist/components/paywall/Paywall.stories.svelte +51 -4
- package/dist/components/paywall/Paywall.svelte +7 -4
- package/dist/components/paywall/Paywall.svelte.d.ts +5 -2
- package/dist/components/purchase-button/PurchaseButton.svelte +147 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/stores/paywall.d.ts +5 -2
- package/dist/stories/fixtures.d.ts +4 -0
- package/dist/stories/fixtures.js +4 -0
- package/dist/stories/paywall-decorator.d.ts +4 -1
- package/dist/types/component.d.ts +2 -1
- package/dist/types/components/countdown.d.ts +13 -0
- package/dist/types/components/countdown.js +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import Countdown from "./Countdown.svelte";
|
|
3
|
+
import type { CountdownProps } from "../../types/components/countdown";
|
|
4
|
+
import { componentDecorator } from "../../stories/component-decorator";
|
|
5
|
+
import { mockDateDecorator } from "storybook-mock-date-decorator";
|
|
6
|
+
import { localizations } from "../../stories/fixtures";
|
|
7
|
+
import { localizationDecorator } from "../../stories/localization-decorator";
|
|
8
|
+
import { variablesDecorator } from "../../stories/variables-decorator";
|
|
9
|
+
import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
|
|
10
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
11
|
+
|
|
12
|
+
const defaultLocale = Object.keys(localizations)[0];
|
|
13
|
+
|
|
14
|
+
// Freeze date for consistent Chromatic snapshots
|
|
15
|
+
// Using a fixed date: 2024-01-15T12:00:00Z
|
|
16
|
+
const frozenDate = new Date("2024-01-15T12:00:00Z");
|
|
17
|
+
|
|
18
|
+
// Create dates relative to frozen date for consistent stories
|
|
19
|
+
// Target: 1 day, 23 hours, 45 minutes, 56 seconds from frozen date
|
|
20
|
+
// This equals 47 hours, 45 minutes, 56 seconds = 2024-01-17T11:45:56Z
|
|
21
|
+
const futureDate = new Date(frozenDate);
|
|
22
|
+
futureDate.setDate(futureDate.getDate() + 1); // +1 day = 24 hours
|
|
23
|
+
futureDate.setHours(futureDate.getHours() + 23); // +23 hours (total 47 hours from start)
|
|
24
|
+
futureDate.setMinutes(futureDate.getMinutes() + 45); // +45 minutes
|
|
25
|
+
futureDate.setSeconds(futureDate.getSeconds() + 56); // +56 seconds
|
|
26
|
+
|
|
27
|
+
// 3 days from frozen date (for multi-day story)
|
|
28
|
+
const threeDaysFromNow = new Date(frozenDate);
|
|
29
|
+
threeDaysFromNow.setDate(threeDaysFromNow.getDate() + 3);
|
|
30
|
+
threeDaysFromNow.setHours(threeDaysFromNow.getHours() + 5); // Add 5 hours to show hours too
|
|
31
|
+
|
|
32
|
+
// Create a date in the past for ended countdown
|
|
33
|
+
const pastDate = new Date(frozenDate);
|
|
34
|
+
pastDate.setHours(pastDate.getHours() - 1);
|
|
35
|
+
|
|
36
|
+
const { Story } = defineMeta({
|
|
37
|
+
title: "Components/Countdown",
|
|
38
|
+
component: Countdown,
|
|
39
|
+
decorators: [
|
|
40
|
+
mockDateDecorator,
|
|
41
|
+
componentDecorator(),
|
|
42
|
+
localizationDecorator({
|
|
43
|
+
defaultLocale,
|
|
44
|
+
localizations,
|
|
45
|
+
}),
|
|
46
|
+
variablesDecorator(undefined),
|
|
47
|
+
],
|
|
48
|
+
parameters: {
|
|
49
|
+
date: frozenDate,
|
|
50
|
+
},
|
|
51
|
+
args: {
|
|
52
|
+
type: "countdown",
|
|
53
|
+
id: "countdown",
|
|
54
|
+
name: "Countdown",
|
|
55
|
+
style: {
|
|
56
|
+
type: "date",
|
|
57
|
+
date: futureDate.toISOString(),
|
|
58
|
+
},
|
|
59
|
+
countdown_stack: {
|
|
60
|
+
type: "stack",
|
|
61
|
+
id: "countdown-stack",
|
|
62
|
+
name: "Countdown Stack",
|
|
63
|
+
components: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
id: "countdown-text",
|
|
67
|
+
name: "Countdown Text",
|
|
68
|
+
text_lid: "countdown",
|
|
69
|
+
color: {
|
|
70
|
+
light: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
71
|
+
},
|
|
72
|
+
font_size: "heading_xl",
|
|
73
|
+
font_weight: "bold",
|
|
74
|
+
horizontal_alignment: "center",
|
|
75
|
+
size: {
|
|
76
|
+
width: { type: "fit" },
|
|
77
|
+
height: { type: "fit" },
|
|
78
|
+
},
|
|
79
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
80
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
81
|
+
background_color: null,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
size: {
|
|
85
|
+
width: { type: "fit" },
|
|
86
|
+
height: { type: "fit" },
|
|
87
|
+
},
|
|
88
|
+
dimension: {
|
|
89
|
+
type: "vertical",
|
|
90
|
+
alignment: "center",
|
|
91
|
+
distribution: "center",
|
|
92
|
+
},
|
|
93
|
+
spacing: 8,
|
|
94
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
95
|
+
padding: { top: 16, trailing: 16, bottom: 16, leading: 16 },
|
|
96
|
+
background_color: {
|
|
97
|
+
light: { type: "hex", value: "#F0F0F0" },
|
|
98
|
+
},
|
|
99
|
+
background: null,
|
|
100
|
+
border: null,
|
|
101
|
+
shape: {
|
|
102
|
+
type: "rectangle",
|
|
103
|
+
corners: {
|
|
104
|
+
top_leading: 8,
|
|
105
|
+
top_trailing: 8,
|
|
106
|
+
bottom_leading: 8,
|
|
107
|
+
bottom_trailing: 8,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
shadow: null,
|
|
111
|
+
badge: null,
|
|
112
|
+
},
|
|
113
|
+
end_stack: {
|
|
114
|
+
type: "stack",
|
|
115
|
+
id: "end-stack",
|
|
116
|
+
name: "End Stack",
|
|
117
|
+
components: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
id: "end-text",
|
|
121
|
+
name: "End Text",
|
|
122
|
+
text_lid: "ended",
|
|
123
|
+
color: {
|
|
124
|
+
light: { type: "hex", value: "#FF0000" },
|
|
125
|
+
},
|
|
126
|
+
font_size: "heading_xl",
|
|
127
|
+
font_weight: "bold",
|
|
128
|
+
horizontal_alignment: "center",
|
|
129
|
+
size: {
|
|
130
|
+
width: { type: "fit" },
|
|
131
|
+
height: { type: "fit" },
|
|
132
|
+
},
|
|
133
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
134
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
135
|
+
background_color: null,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
size: {
|
|
139
|
+
width: { type: "fit" },
|
|
140
|
+
height: { type: "fit" },
|
|
141
|
+
},
|
|
142
|
+
dimension: {
|
|
143
|
+
type: "vertical",
|
|
144
|
+
alignment: "center",
|
|
145
|
+
distribution: "center",
|
|
146
|
+
},
|
|
147
|
+
spacing: 8,
|
|
148
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
149
|
+
padding: { top: 16, trailing: 16, bottom: 16, leading: 16 },
|
|
150
|
+
background_color: {
|
|
151
|
+
light: { type: "hex", value: "#FFE0E0" },
|
|
152
|
+
},
|
|
153
|
+
background: null,
|
|
154
|
+
border: null,
|
|
155
|
+
shape: {
|
|
156
|
+
type: "rectangle",
|
|
157
|
+
corners: {
|
|
158
|
+
top_leading: 8,
|
|
159
|
+
top_trailing: 8,
|
|
160
|
+
bottom_leading: 8,
|
|
161
|
+
bottom_trailing: 8,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
shadow: null,
|
|
165
|
+
badge: null,
|
|
166
|
+
},
|
|
167
|
+
} satisfies CountdownProps,
|
|
168
|
+
});
|
|
169
|
+
</script>
|
|
170
|
+
|
|
171
|
+
<Story
|
|
172
|
+
name="Active Countdown"
|
|
173
|
+
args={{
|
|
174
|
+
style: {
|
|
175
|
+
type: "date",
|
|
176
|
+
date: futureDate.toISOString(),
|
|
177
|
+
},
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
<Story
|
|
182
|
+
name="Ended Countdown"
|
|
183
|
+
args={{
|
|
184
|
+
style: {
|
|
185
|
+
type: "date",
|
|
186
|
+
date: pastDate.toISOString(),
|
|
187
|
+
},
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
|
|
191
|
+
<Story
|
|
192
|
+
name="Countdown with Variables"
|
|
193
|
+
args={{
|
|
194
|
+
style: {
|
|
195
|
+
type: "date",
|
|
196
|
+
date: futureDate.toISOString(),
|
|
197
|
+
},
|
|
198
|
+
countdown_stack: {
|
|
199
|
+
type: "stack",
|
|
200
|
+
id: "countdown-stack-vars",
|
|
201
|
+
name: "Countdown Stack with Variables",
|
|
202
|
+
components: [
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
id: "days",
|
|
206
|
+
name: "Days",
|
|
207
|
+
text_lid: "days",
|
|
208
|
+
color: {
|
|
209
|
+
light: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
210
|
+
},
|
|
211
|
+
font_size: "heading_xxl",
|
|
212
|
+
font_weight: "bold",
|
|
213
|
+
horizontal_alignment: "center",
|
|
214
|
+
size: {
|
|
215
|
+
width: { type: "fit" },
|
|
216
|
+
height: { type: "fit" },
|
|
217
|
+
},
|
|
218
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
219
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
220
|
+
background_color: null,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: "text",
|
|
224
|
+
id: "time",
|
|
225
|
+
name: "Time",
|
|
226
|
+
text_lid: "time",
|
|
227
|
+
color: {
|
|
228
|
+
light: { type: "hex", value: "#666666" },
|
|
229
|
+
},
|
|
230
|
+
font_size: "heading_l",
|
|
231
|
+
font_weight: "regular",
|
|
232
|
+
horizontal_alignment: "center",
|
|
233
|
+
size: {
|
|
234
|
+
width: { type: "fit" },
|
|
235
|
+
height: { type: "fit" },
|
|
236
|
+
},
|
|
237
|
+
margin: { top: 8, trailing: 0, bottom: 0, leading: 0 },
|
|
238
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
239
|
+
background_color: null,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
size: {
|
|
243
|
+
width: { type: "fit" },
|
|
244
|
+
height: { type: "fit" },
|
|
245
|
+
},
|
|
246
|
+
dimension: {
|
|
247
|
+
type: "vertical",
|
|
248
|
+
alignment: "center",
|
|
249
|
+
distribution: "center",
|
|
250
|
+
},
|
|
251
|
+
spacing: 0,
|
|
252
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
253
|
+
padding: { top: 32, trailing: 32, bottom: 32, leading: 32 },
|
|
254
|
+
background_color: {
|
|
255
|
+
light: { type: "hex", value: "#E8F5E9" },
|
|
256
|
+
},
|
|
257
|
+
background: null,
|
|
258
|
+
border: {
|
|
259
|
+
width: 2,
|
|
260
|
+
color: {
|
|
261
|
+
light: { type: "hex", value: "#4CAF50" },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
shape: {
|
|
265
|
+
type: "rectangle",
|
|
266
|
+
corners: {
|
|
267
|
+
top_leading: 16,
|
|
268
|
+
top_trailing: 16,
|
|
269
|
+
bottom_leading: 16,
|
|
270
|
+
bottom_trailing: 16,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
shadow: null,
|
|
274
|
+
badge: null,
|
|
275
|
+
},
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
|
|
279
|
+
<Story
|
|
280
|
+
name="Countdown with Multiple Days"
|
|
281
|
+
args={{
|
|
282
|
+
style: {
|
|
283
|
+
type: "date",
|
|
284
|
+
date: threeDaysFromNow.toISOString(),
|
|
285
|
+
},
|
|
286
|
+
countdown_stack: {
|
|
287
|
+
type: "stack",
|
|
288
|
+
id: "countdown-stack-multi-day",
|
|
289
|
+
name: "Countdown Stack with Multiple Days",
|
|
290
|
+
components: [
|
|
291
|
+
{
|
|
292
|
+
type: "text",
|
|
293
|
+
id: "days",
|
|
294
|
+
name: "Days",
|
|
295
|
+
text_lid: "days",
|
|
296
|
+
color: {
|
|
297
|
+
light: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
298
|
+
},
|
|
299
|
+
font_size: "heading_xxl",
|
|
300
|
+
font_weight: "bold",
|
|
301
|
+
horizontal_alignment: "center",
|
|
302
|
+
size: {
|
|
303
|
+
width: { type: "fit" },
|
|
304
|
+
height: { type: "fit" },
|
|
305
|
+
},
|
|
306
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
307
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
308
|
+
background_color: null,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
id: "time",
|
|
313
|
+
name: "Time",
|
|
314
|
+
text_lid: "time",
|
|
315
|
+
color: {
|
|
316
|
+
light: { type: "hex", value: "#666666" },
|
|
317
|
+
},
|
|
318
|
+
font_size: "heading_l",
|
|
319
|
+
font_weight: "regular",
|
|
320
|
+
horizontal_alignment: "center",
|
|
321
|
+
size: {
|
|
322
|
+
width: { type: "fit" },
|
|
323
|
+
height: { type: "fit" },
|
|
324
|
+
},
|
|
325
|
+
margin: { top: 8, trailing: 0, bottom: 0, leading: 0 },
|
|
326
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
327
|
+
background_color: null,
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
size: {
|
|
331
|
+
width: { type: "fit" },
|
|
332
|
+
height: { type: "fit" },
|
|
333
|
+
},
|
|
334
|
+
dimension: {
|
|
335
|
+
type: "vertical",
|
|
336
|
+
alignment: "center",
|
|
337
|
+
distribution: "center",
|
|
338
|
+
},
|
|
339
|
+
spacing: 0,
|
|
340
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
341
|
+
padding: { top: 32, trailing: 32, bottom: 32, leading: 32 },
|
|
342
|
+
background_color: {
|
|
343
|
+
light: { type: "hex", value: "#E3F2FD" },
|
|
344
|
+
},
|
|
345
|
+
background: null,
|
|
346
|
+
border: {
|
|
347
|
+
width: 2,
|
|
348
|
+
color: {
|
|
349
|
+
light: { type: "hex", value: "#2196F3" },
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
shape: {
|
|
353
|
+
type: "rectangle",
|
|
354
|
+
corners: {
|
|
355
|
+
top_leading: 16,
|
|
356
|
+
top_trailing: 16,
|
|
357
|
+
bottom_leading: 16,
|
|
358
|
+
bottom_trailing: 16,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
shadow: null,
|
|
362
|
+
badge: null,
|
|
363
|
+
},
|
|
364
|
+
}}
|
|
365
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Countdown from "./Countdown.svelte";
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const Countdown: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type Countdown = InstanceType<typeof Countdown>;
|
|
19
|
+
export default Countdown;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Stack from "../stack/Stack.svelte";
|
|
3
|
+
import {
|
|
4
|
+
getVariablesContext,
|
|
5
|
+
setVariablesContext,
|
|
6
|
+
} from "../../stores/variables";
|
|
7
|
+
import type { CountdownProps } from "../../types/components/countdown";
|
|
8
|
+
import { derived, writable } from "svelte/store";
|
|
9
|
+
import {
|
|
10
|
+
calculateCountdownValues,
|
|
11
|
+
hasCountdownEnded,
|
|
12
|
+
} from "./countdown-utils";
|
|
13
|
+
|
|
14
|
+
const props: CountdownProps = $props();
|
|
15
|
+
const { countdown_stack, end_stack } = props;
|
|
16
|
+
|
|
17
|
+
// State for countdown values that updates every second
|
|
18
|
+
let countdownValues = $state(calculateCountdownValues(props.style.date));
|
|
19
|
+
let isEnded = $state(hasCountdownEnded(props.style.date));
|
|
20
|
+
|
|
21
|
+
// Provide countdown variables to child Text components
|
|
22
|
+
const fallbackVariables = getVariablesContext();
|
|
23
|
+
const countdownValuesStore = writable(countdownValues);
|
|
24
|
+
const variables = derived(
|
|
25
|
+
[fallbackVariables, countdownValuesStore],
|
|
26
|
+
([fallback, countdown]) => ({
|
|
27
|
+
...fallback,
|
|
28
|
+
...countdown,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
setVariablesContext(variables);
|
|
32
|
+
|
|
33
|
+
// Setup interval to update countdown every second
|
|
34
|
+
$effect(() => {
|
|
35
|
+
const interval = setInterval(() => {
|
|
36
|
+
countdownValues = calculateCountdownValues(props.style.date);
|
|
37
|
+
isEnded = hasCountdownEnded(props.style.date);
|
|
38
|
+
countdownValuesStore.set(countdownValues);
|
|
39
|
+
}, 1000);
|
|
40
|
+
|
|
41
|
+
return () => clearInterval(interval);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Determine which stack to show
|
|
45
|
+
const activeStack = $derived.by(() => {
|
|
46
|
+
// If countdown has ended and end_stack exists, show end_stack
|
|
47
|
+
if (isEnded && end_stack) {
|
|
48
|
+
return end_stack;
|
|
49
|
+
}
|
|
50
|
+
// Otherwise show countdown_stack
|
|
51
|
+
return countdown_stack;
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<Stack {...activeStack} />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates countdown values from a target date
|
|
3
|
+
* Returns 8 countdown variables for use in Text components:
|
|
4
|
+
* - count_days_with_zero / count_days_without_zero
|
|
5
|
+
* - count_hours_with_zero / count_hours_without_zero
|
|
6
|
+
* - count_minutes_with_zero / count_minutes_without_zero
|
|
7
|
+
* - count_seconds_with_zero / count_seconds_without_zero
|
|
8
|
+
*/
|
|
9
|
+
export declare function calculateCountdownValues(targetDate: string): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a countdown has ended (target date is in the past)
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasCountdownEnded(targetDate: string): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a number with leading zero if it's less than 10
|
|
3
|
+
*/
|
|
4
|
+
function formatWithZero(value) {
|
|
5
|
+
return value < 10 ? `0${value}` : `${value}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Formats a number without leading zero
|
|
9
|
+
*/
|
|
10
|
+
function formatWithoutZero(value) {
|
|
11
|
+
return `${value}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Calculates countdown values from a target date
|
|
15
|
+
* Returns 8 countdown variables for use in Text components:
|
|
16
|
+
* - count_days_with_zero / count_days_without_zero
|
|
17
|
+
* - count_hours_with_zero / count_hours_without_zero
|
|
18
|
+
* - count_minutes_with_zero / count_minutes_without_zero
|
|
19
|
+
* - count_seconds_with_zero / count_seconds_without_zero
|
|
20
|
+
*/
|
|
21
|
+
export function calculateCountdownValues(targetDate) {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const target = new Date(targetDate);
|
|
24
|
+
const diffInMs = target.getTime() - now.getTime();
|
|
25
|
+
// If past target date, return all zeros
|
|
26
|
+
if (diffInMs <= 0) {
|
|
27
|
+
return {
|
|
28
|
+
count_days_with_zero: "00",
|
|
29
|
+
count_days_without_zero: "0",
|
|
30
|
+
count_hours_with_zero: "00",
|
|
31
|
+
count_hours_without_zero: "0",
|
|
32
|
+
count_minutes_with_zero: "00",
|
|
33
|
+
count_minutes_without_zero: "0",
|
|
34
|
+
count_seconds_with_zero: "00",
|
|
35
|
+
count_seconds_without_zero: "0",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Calculate time units
|
|
39
|
+
const totalSeconds = Math.floor(diffInMs / 1000);
|
|
40
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
41
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
42
|
+
const totalDays = Math.floor(totalHours / 24);
|
|
43
|
+
// Calculate remaining partial units (hours 0-23, minutes 0-59, seconds 0-59)
|
|
44
|
+
const days = totalDays;
|
|
45
|
+
const hours = totalHours % 24;
|
|
46
|
+
const minutes = totalMinutes % 60;
|
|
47
|
+
const seconds = totalSeconds % 60;
|
|
48
|
+
return {
|
|
49
|
+
count_days_with_zero: formatWithZero(days),
|
|
50
|
+
count_days_without_zero: formatWithoutZero(days),
|
|
51
|
+
count_hours_with_zero: formatWithZero(hours),
|
|
52
|
+
count_hours_without_zero: formatWithoutZero(hours),
|
|
53
|
+
count_minutes_with_zero: formatWithZero(minutes),
|
|
54
|
+
count_minutes_without_zero: formatWithoutZero(minutes),
|
|
55
|
+
count_seconds_with_zero: formatWithZero(seconds),
|
|
56
|
+
count_seconds_without_zero: formatWithoutZero(seconds),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Checks if a countdown has ended (target date is in the past)
|
|
61
|
+
*/
|
|
62
|
+
export function hasCountdownEnded(targetDate) {
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const target = new Date(targetDate);
|
|
65
|
+
return target.getTime() <= now.getTime();
|
|
66
|
+
}
|