@nightkatana/kronosys-app 1.0.0-beta.0
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/README.md +81 -0
- package/app/api/action/route.ts +16 -0
- package/app/api/backup/route.ts +84 -0
- package/app/api/health/route.ts +22 -0
- package/app/api/state/route.ts +27 -0
- package/app/apple-icon.png +0 -0
- package/app/changelog/page.tsx +122 -0
- package/app/globals.css +210 -0
- package/app/guide/layout.tsx +11 -0
- package/app/guide/page.tsx +278 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +77 -0
- package/app/licenses/layout.tsx +11 -0
- package/app/licenses/page.tsx +246 -0
- package/app/manifest.ts +32 -0
- package/app/page.tsx +1610 -0
- package/app/reporting/page.tsx +2943 -0
- package/app/settings/layout.tsx +10 -0
- package/app/settings/page.tsx +3518 -0
- package/bin/kronosys.mjs +46 -0
- package/components/KronosysPackageVersionProvider.tsx +19 -0
- package/components/KronosysPayloadProvider.tsx +109 -0
- package/components/PwaRegister.tsx +25 -0
- package/components/SiteLegalFooter.tsx +21 -0
- package/components/ThemeProvider.tsx +78 -0
- package/components/dashboard/AppShellLiveSessionDrawer.tsx +394 -0
- package/components/dashboard/AppShellRouteNav.tsx +131 -0
- package/components/dashboard/AppVersionStamp.tsx +16 -0
- package/components/dashboard/DashboardCollapsibleSection.tsx +57 -0
- package/components/dashboard/DashboardColumnHintsBanner.tsx +159 -0
- package/components/dashboard/DashboardCommandCenter.tsx +470 -0
- package/components/dashboard/DashboardLangGateModal.tsx +118 -0
- package/components/dashboard/DashboardLoadingOverlay.tsx +42 -0
- package/components/dashboard/DashboardSimpleModal.tsx +337 -0
- package/components/dashboard/DashboardSuspenseFallback.tsx +52 -0
- package/components/dashboard/DashboardToastProvider.tsx +64 -0
- package/components/dashboard/DashboardTour.tsx +435 -0
- package/components/dashboard/DeferredDescriptionPopoverWrap.tsx +39 -0
- package/components/dashboard/DeleteSessionModal.tsx +130 -0
- package/components/dashboard/DescriptionTooltipPortaled.tsx +31 -0
- package/components/dashboard/GitIdentityQuickSetupModal.tsx +211 -0
- package/components/dashboard/HeaderIntegrationBadges.tsx +69 -0
- package/components/dashboard/InlineMetricHelpTrigger.tsx +102 -0
- package/components/dashboard/IssuePickerModal.tsx +168 -0
- package/components/dashboard/KronoFocusPanel.tsx +834 -0
- package/components/dashboard/KronosysDatetimePopoverField.tsx +357 -0
- package/components/dashboard/KronosysTimePopoverField.tsx +233 -0
- package/components/dashboard/LanguageMenu.tsx +123 -0
- package/components/dashboard/MongoMirrorSyncLine.tsx +57 -0
- package/components/dashboard/NewSessionScopeModal.tsx +410 -0
- package/components/dashboard/PageRefreshButton.tsx +130 -0
- package/components/dashboard/PlainHelpPopover.tsx +97 -0
- package/components/dashboard/ReportingPageToc.tsx +68 -0
- package/components/dashboard/ReportingTour.tsx +342 -0
- package/components/dashboard/SavedProjectPicker.tsx +92 -0
- package/components/dashboard/SavedTagPicker.tsx +115 -0
- package/components/dashboard/ScrollToTopFab.tsx +41 -0
- package/components/dashboard/SelectedSessionSidebarBlock.tsx +630 -0
- package/components/dashboard/SessionEndReasonEditor.tsx +114 -0
- package/components/dashboard/SessionListPanel.tsx +320 -0
- package/components/dashboard/SessionLocMetricsSection.tsx +128 -0
- package/components/dashboard/SettingsTagsProjectsSection.tsx +993 -0
- package/components/dashboard/SettingsTour.tsx +332 -0
- package/components/dashboard/TagPills.tsx +149 -0
- package/components/dashboard/TagsHelpTrigger.tsx +84 -0
- package/components/dashboard/TaskFocusPanel.tsx +1261 -0
- package/components/dashboard/TaskSessionLiveCard.tsx +832 -0
- package/components/dashboard/TaskSubtasksBlock.tsx +748 -0
- package/components/dashboard/ThemeToggle.test.tsx +26 -0
- package/components/dashboard/ThemeToggle.tsx +36 -0
- package/components/dashboard/UserGuideBodyText.tsx +62 -0
- package/components/dashboard/WorkspaceGitRepoCard.tsx +191 -0
- package/components/dashboard/taskFieldStyles.ts +139 -0
- package/components/dashboard/useAnchoredFloatingPortalStyle.ts +71 -0
- package/components/dashboard/useDescriptionPopoverAfterMs.ts +220 -0
- package/components/dashboard/useKronoFocusLiveSeconds.ts +36 -0
- package/components/dashboard/useSmoothStopwatchMs.ts +25 -0
- package/lib/appShellHeaderClasses.ts +12 -0
- package/lib/backupCsvExport.test.ts +149 -0
- package/lib/backupCsvExport.ts +392 -0
- package/lib/changelogCopy.ts +34 -0
- package/lib/concurrentTaskStartPreference.ts +29 -0
- package/lib/dashboardClockFormat.ts +13 -0
- package/lib/dashboardColumnChrome.ts +3 -0
- package/lib/dashboardColumnHintsStorage.ts +57 -0
- package/lib/dashboardCopy.ts +1831 -0
- package/lib/dashboardDetachedUrlHintStorage.ts +24 -0
- package/lib/dashboardGitIdentityBannerStorage.ts +36 -0
- package/lib/dashboardLangStorage.ts +72 -0
- package/lib/dashboardQuickSearch.ts +476 -0
- package/lib/dashboardQuickSearchQuery.test.ts +63 -0
- package/lib/dashboardQuickSearchQuery.ts +179 -0
- package/lib/dashboardSessionNav.ts +33 -0
- package/lib/dashboardShortcuts.ts +268 -0
- package/lib/dashboardTimeZone.ts +91 -0
- package/lib/dashboardTourStorage.ts +68 -0
- package/lib/dataDir.test.ts +87 -0
- package/lib/dataDir.ts +83 -0
- package/lib/devDataPreferenceFile.ts +55 -0
- package/lib/devDataRuntimeInfo.ts +34 -0
- package/lib/formatIsoShort.test.ts +46 -0
- package/lib/formatIsoShort.ts +29 -0
- package/lib/generatedUserChangelog.ts +34 -0
- package/lib/gitlabIssueSearch.ts +8 -0
- package/lib/kronoFocusDurationHistory.ts +71 -0
- package/lib/kronoFocusRhythm.test.ts +130 -0
- package/lib/kronoFocusRhythm.ts +46 -0
- package/lib/kronoFocusTimerUrgency.test.ts +74 -0
- package/lib/kronoFocusTimerUrgency.ts +24 -0
- package/lib/kronosysApi.ts +143 -0
- package/lib/legacyEditorPayloadKeys.ts +52 -0
- package/lib/legacyKronoFocusStorageKeys.test.ts +29 -0
- package/lib/legacyKronoFocusStorageKeys.ts +32 -0
- package/lib/licensesCopy.ts +128 -0
- package/lib/openPlainTextInNewTab.ts +49 -0
- package/lib/readKronosysPackageVersion.ts +10 -0
- package/lib/reportingAggregate.test.ts +325 -0
- package/lib/reportingAggregate.ts +819 -0
- package/lib/reportingDatePresets.ts +41 -0
- package/lib/reportingMetricHelp.ts +430 -0
- package/lib/reportingNonFinalIndicators.test.ts +157 -0
- package/lib/reportingNonFinalIndicators.ts +102 -0
- package/lib/reportingStrings.ts +491 -0
- package/lib/reportingTagWeekBreakdown.test.ts +141 -0
- package/lib/reportingTagWeekBreakdown.ts +181 -0
- package/lib/reportingWeekLayout.test.ts +239 -0
- package/lib/reportingWeekLayout.ts +313 -0
- package/lib/sessionAssiduity.test.ts +25 -0
- package/lib/sessionAssiduity.ts +33 -0
- package/lib/sessionEndReason.ts +55 -0
- package/lib/sessionEndWarnings.test.ts +200 -0
- package/lib/sessionEndWarnings.ts +125 -0
- package/lib/sessionListMerge.test.ts +101 -0
- package/lib/sessionListMerge.ts +70 -0
- package/lib/sessionTaskSidebarStats.test.ts +24 -0
- package/lib/sessionTaskSidebarStats.ts +54 -0
- package/lib/settingsCopy.ts +1276 -0
- package/lib/taskParsing.test.ts +153 -0
- package/lib/taskParsing.ts +737 -0
- package/lib/theme.ts +15 -0
- package/lib/translucentButtonClasses.ts +34 -0
- package/lib/usageProfile.test.ts +84 -0
- package/lib/usageProfile.ts +52 -0
- package/lib/userGuideCopy.ts +464 -0
- package/lib/workspaceLocDefaults.ts +21 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +15 -0
- package/package.json +87 -0
- package/postcss.config.mjs +12 -0
- package/public/apple-icon.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon.png +0 -0
- package/public/next.svg +1 -0
- package/public/sw.js +13 -0
- package/public/traceback.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/server/actionDispatch.test.ts +723 -0
- package/server/actionDispatch.ts +1476 -0
- package/server/actionTaskSession.test.ts +713 -0
- package/server/actionTaskSession.ts +717 -0
- package/server/db.ts +42 -0
- package/server/defaultCfg.ts +87 -0
- package/server/gitlabTokenStore.ts +34 -0
- package/server/kronoFocusHydrate.test.ts +142 -0
- package/server/kronoFocusHydrate.ts +69 -0
- package/server/kronoFocusMigrate.test.ts +53 -0
- package/server/kronoFocusMigrate.ts +78 -0
- package/server/mainTimerHydrate.test.ts +65 -0
- package/server/mainTimerHydrate.ts +53 -0
- package/server/payloadStore.test.ts +78 -0
- package/server/payloadStore.ts +83 -0
- package/server/sessionWallHydrate.test.ts +46 -0
- package/server/sessionWallHydrate.ts +88 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { formatProjectDisplay, normalizeProjectKey } from "@/lib/taskParsing";
|
|
5
|
+
import {
|
|
6
|
+
PROJECT_INLINE_SUGGEST,
|
|
7
|
+
PROJECT_INLINE_SUGGEST_SELECTED,
|
|
8
|
+
} from "./taskFieldStyles";
|
|
9
|
+
import { useDescriptionPopoverAfterMs } from "./useDescriptionPopoverAfterMs";
|
|
10
|
+
|
|
11
|
+
function SuggestedProjectButton({
|
|
12
|
+
project,
|
|
13
|
+
selected,
|
|
14
|
+
onPick,
|
|
15
|
+
description,
|
|
16
|
+
}: {
|
|
17
|
+
project: string;
|
|
18
|
+
selected: boolean;
|
|
19
|
+
onPick: () => void;
|
|
20
|
+
description: string | undefined;
|
|
21
|
+
}) {
|
|
22
|
+
const { hasDescription, triggerProps, popoverLayer, anchorWrapperProps } =
|
|
23
|
+
useDescriptionPopoverAfterMs(description);
|
|
24
|
+
|
|
25
|
+
const chipClass = selected
|
|
26
|
+
? `${PROJECT_INLINE_SUGGEST_SELECTED} cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/35 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-900`
|
|
27
|
+
: `${PROJECT_INLINE_SUGGEST} cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/30 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-900`;
|
|
28
|
+
|
|
29
|
+
const btn = (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={chipClass}
|
|
33
|
+
aria-pressed={selected ? "true" : "false"}
|
|
34
|
+
{...triggerProps}
|
|
35
|
+
onClick={onPick}
|
|
36
|
+
>
|
|
37
|
+
{formatProjectDisplay(project)}
|
|
38
|
+
</button>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!hasDescription) {
|
|
42
|
+
return btn;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Fragment>
|
|
47
|
+
<span className="relative inline-flex max-w-full shrink-0" {...anchorWrapperProps}>
|
|
48
|
+
{btn}
|
|
49
|
+
</span>
|
|
50
|
+
{popoverLayer}
|
|
51
|
+
</Fragment>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function SavedProjectPicker({
|
|
56
|
+
knownProjects,
|
|
57
|
+
selectedProject,
|
|
58
|
+
onPickProject,
|
|
59
|
+
projectDescriptions,
|
|
60
|
+
noTopMargin,
|
|
61
|
+
}: {
|
|
62
|
+
knownProjects: string[];
|
|
63
|
+
selectedProject?: string | null;
|
|
64
|
+
/** `null` : désactiver le projet (clic sur la pastille déjà active). */
|
|
65
|
+
onPickProject: (name: string | null) => void;
|
|
66
|
+
projectDescriptions?: Record<string, string>;
|
|
67
|
+
noTopMargin?: boolean;
|
|
68
|
+
}) {
|
|
69
|
+
const sel = selectedProject?.trim() ? normalizeProjectKey(selectedProject) : "";
|
|
70
|
+
const selLower = sel.toLowerCase();
|
|
71
|
+
if (knownProjects.length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
<div className={`min-w-0 shrink-0 ${noTopMargin ? "" : "mt-1.5"}`}>
|
|
76
|
+
<div className="flex flex-nowrap items-baseline gap-x-2 gap-y-1 overflow-x-auto pb-0.5 [scrollbar-width:thin]">
|
|
77
|
+
{knownProjects.map((p) => {
|
|
78
|
+
const on = normalizeProjectKey(p).toLowerCase() === selLower;
|
|
79
|
+
return (
|
|
80
|
+
<SuggestedProjectButton
|
|
81
|
+
key={p}
|
|
82
|
+
project={p}
|
|
83
|
+
selected={on}
|
|
84
|
+
description={projectDescriptions?.[normalizeProjectKey(p).toLowerCase()]}
|
|
85
|
+
onPick={() => onPickProject(on ? null : p)}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import {
|
|
5
|
+
filterKnownTagsForProject,
|
|
6
|
+
formatTagDisplay,
|
|
7
|
+
isProjectScopedTag,
|
|
8
|
+
normalizeTagKey,
|
|
9
|
+
} from "@/lib/taskParsing";
|
|
10
|
+
import {
|
|
11
|
+
TAG_INLINE_SUGGEST,
|
|
12
|
+
TAG_INLINE_SUGGEST_SCOPED_SELECTED,
|
|
13
|
+
TAG_INLINE_SUGGEST_SELECTED,
|
|
14
|
+
} from "./taskFieldStyles";
|
|
15
|
+
import { useDescriptionPopoverAfterMs } from "./useDescriptionPopoverAfterMs";
|
|
16
|
+
|
|
17
|
+
function SuggestedTagButton({
|
|
18
|
+
tag,
|
|
19
|
+
selected,
|
|
20
|
+
scopedSelectedOutline,
|
|
21
|
+
onPick,
|
|
22
|
+
description,
|
|
23
|
+
}: {
|
|
24
|
+
tag: string;
|
|
25
|
+
selected: boolean;
|
|
26
|
+
scopedSelectedOutline: boolean;
|
|
27
|
+
onPick: () => void;
|
|
28
|
+
description: string | undefined;
|
|
29
|
+
}) {
|
|
30
|
+
const { hasDescription, triggerProps, popoverLayer, anchorWrapperProps } =
|
|
31
|
+
useDescriptionPopoverAfterMs(description);
|
|
32
|
+
|
|
33
|
+
const chipClass = selected
|
|
34
|
+
? scopedSelectedOutline
|
|
35
|
+
? TAG_INLINE_SUGGEST_SCOPED_SELECTED
|
|
36
|
+
: TAG_INLINE_SUGGEST_SELECTED
|
|
37
|
+
: TAG_INLINE_SUGGEST;
|
|
38
|
+
|
|
39
|
+
const btn = (
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
className={chipClass}
|
|
43
|
+
aria-pressed={selected ? "true" : "false"}
|
|
44
|
+
{...triggerProps}
|
|
45
|
+
onClick={onPick}
|
|
46
|
+
>
|
|
47
|
+
{formatTagDisplay(tag)}
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (!hasDescription) {
|
|
52
|
+
return btn;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Fragment>
|
|
57
|
+
<span className="relative inline-flex max-w-full shrink-0" {...anchorWrapperProps}>
|
|
58
|
+
{btn}
|
|
59
|
+
</span>
|
|
60
|
+
{popoverLayer}
|
|
61
|
+
</Fragment>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function SavedTagPicker({
|
|
66
|
+
knownTags,
|
|
67
|
+
selectedTags,
|
|
68
|
+
onPickTag,
|
|
69
|
+
/** Projet courant : les étiquettes `@projet#code` (stockées `projet#code`) d’un autre projet sont masquées. */
|
|
70
|
+
project,
|
|
71
|
+
tagDescriptions,
|
|
72
|
+
/** Désactive la marge du haut quand le parent gère déjà l’espacement (ex. bloc étiquettes). */
|
|
73
|
+
noTopMargin,
|
|
74
|
+
}: {
|
|
75
|
+
knownTags: string[];
|
|
76
|
+
selectedTags: string[];
|
|
77
|
+
/** Clic sur une pastille : activer ou désactiver l’étiquette sur la tâche / le brouillon. */
|
|
78
|
+
onPickTag: (tag: string) => void;
|
|
79
|
+
project?: string | null;
|
|
80
|
+
tagDescriptions?: Record<string, string>;
|
|
81
|
+
noTopMargin?: boolean;
|
|
82
|
+
}) {
|
|
83
|
+
const selectedKeys = new Set(selectedTags.map((t) => normalizeTagKey(t).toLowerCase()));
|
|
84
|
+
const scopedPool = filterKnownTagsForProject(knownTags, project);
|
|
85
|
+
const describe = (tag: string) => {
|
|
86
|
+
if (!tagDescriptions || Object.keys(tagDescriptions).length === 0) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const k = normalizeTagKey(tag).toLowerCase();
|
|
90
|
+
return tagDescriptions[k] ?? tagDescriptions[normalizeTagKey(tag)];
|
|
91
|
+
};
|
|
92
|
+
if (knownTags.length === 0 || scopedPool.length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return (
|
|
96
|
+
<div className={`min-w-0 shrink-0 ${noTopMargin ? "" : "mt-1.5"}`}>
|
|
97
|
+
<div className="flex flex-nowrap items-baseline gap-x-2 gap-y-1 overflow-x-auto pb-0.5 [scrollbar-width:thin]">
|
|
98
|
+
{scopedPool.map((tag) => {
|
|
99
|
+
const nk = normalizeTagKey(tag).toLowerCase();
|
|
100
|
+
const selected = selectedKeys.has(nk);
|
|
101
|
+
return (
|
|
102
|
+
<SuggestedTagButton
|
|
103
|
+
key={tag}
|
|
104
|
+
tag={tag}
|
|
105
|
+
selected={selected}
|
|
106
|
+
scopedSelectedOutline={selected && isProjectScopedTag(normalizeTagKey(tag))}
|
|
107
|
+
description={describe(tag)}
|
|
108
|
+
onPick={() => onPickTag(tag)}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronUp } from "lucide-react";
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
const SHOW_AFTER_PX = 320;
|
|
7
|
+
|
|
8
|
+
export function ScrollToTopFab({ ariaLabel }: Readonly<{ ariaLabel: string }>) {
|
|
9
|
+
const [visible, setVisible] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const onScroll = () => {
|
|
13
|
+
setVisible(globalThis.scrollY > SHOW_AFTER_PX);
|
|
14
|
+
};
|
|
15
|
+
onScroll();
|
|
16
|
+
globalThis.addEventListener("scroll", onScroll, { passive: true });
|
|
17
|
+
return () => globalThis.removeEventListener("scroll", onScroll);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const goTop = useCallback(() => {
|
|
21
|
+
const reduce =
|
|
22
|
+
typeof globalThis.matchMedia === "function" &&
|
|
23
|
+
globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
24
|
+
globalThis.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
if (!visible) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
onClick={goTop}
|
|
35
|
+
aria-label={ariaLabel}
|
|
36
|
+
className="fixed bottom-6 right-4 z-40 flex h-11 w-11 items-center justify-center rounded-full border border-zinc-300 bg-white/95 text-zinc-700 shadow-lg shadow-zinc-900/10 backdrop-blur-sm transition-colors hover:border-violet-500/60 hover:bg-violet-50 hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-500 dark:border-zinc-600 dark:bg-zinc-900/95 dark:text-zinc-200 dark:shadow-black/40 dark:hover:border-violet-500/70 dark:hover:bg-zinc-800 dark:hover:text-violet-100 sm:right-6"
|
|
37
|
+
>
|
|
38
|
+
<ChevronUp className="h-6 w-6" strokeWidth={2.25} aria-hidden />
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
}
|