* feat: add @coss/ui/icons package and migrate static icon usages
Create a new icon system in @coss/ui/icons that wraps lucide-react icons
with controlled props (size and className only). Migrate 44 component
files from <Icon name="xxx" /> to <XxxIcon /> pattern.
This is Phase 1 of the icon migration, covering only static icon names.
Dynamic icon usages (e.g., name={variable}) are skipped for now.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate more static icons to @coss/ui/icons (batch 2)
Migrated 18 files from <Icon name="xxx" /> to <XxxIcon /> pattern:
- Event meta, insights, shell, user table bulk actions
- Webhooks, blocklist, form-builder, form components
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate more static icons to @coss/ui/icons (batch 3)
Migrated 7 files:
- CancelBooking, AddGuestsDialog, ChargeCardDialog
- ReassignDialog, RescheduleDialog
- Categories, Slider
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate more static icons to @coss/ui/icons (batch 4)
Migrated 6 files:
- Organizations: AboutOrganizationForm, PaymentStatusView, profile
- Workflows: CallDetailsSheet, SkeletonLoaderList, TimeTimeUnitInput
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate more static icons to @coss/ui/icons (batch 5)
Migrated 4 files:
- TwoFactorModalHeader
- App setups: alby, make, btcpayserver
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 6)
- confirmDialog.tsx: clock icon
- ics-feedcalendar/Setup.tsx: trash, plus icons
- hitpay/Setup.tsx: info icon
- paypal/Setup.tsx: circle-alert icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 7)
- webhook-edit-view.tsx: external-link icon
- webhook-new-view.tsx: external-link icon
- UserDropdown.tsx: chevron-down, user, settings, moon, map, circle-help, download, blocks, log-out icons
- TopNav.tsx: settings icon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 8)
- EmailInviteForm.tsx: x icon
- InviteOptions.tsx: mail, upload, link icons
- SkeletonLoader.tsx: search, clock, user icons
- Embed.tsx: sun, arrow-left icons
- CalendarSwitch.tsx: arrow-left, rotate-cw icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 9)
- apps-view.tsx: search icon
- categories-view.tsx: arrow-left, arrow-right icons
- DatePicker.tsx: calendar icon
- EditLocationDialog.tsx: map-pin icon
- AppPage.tsx: circle-alert, book-open, external-link, mail, file, shield, flag icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 10)
- LicenseSelection.tsx: loader, check icons
- AppConnectionItem.tsx: circle-alert, arrow-right icons
- oauth-client-form/index.tsx: info icons (x2)
- auth/error/page.tsx: x icon
- troubleshoot/layout.tsx: loader icon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 11)
- UpgradeTipWrapper.tsx: users, refresh-ccw, user-plus icons
- PbacOptInModal.tsx: shield-check, lock, users, shield icons
- AdvancedPermissionGroup.tsx: chevron-right, info icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 12)
- AdminAppsList.tsx: pencil icon
- AvailableTimes.tsx: calendar-x-2 icon
- BookingDetailsSheet.tsx: external-link (x2), repeat icons
- BookingCalendarContainer.tsx: chevron-left, chevron-right icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 13)
- videos-single-view.tsx: chevron-right icon
- TroubleshooterSidebar.tsx: arrow-left icon
- RuleBuilder.tsx: filter icon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 14)
- FieldMappingBuilder.tsx: arrow-right, link icons
- IntegrationAttributeSyncCard.tsx: key icon
- onboarding-view.tsx: loader icon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static icons to @coss/ui/icons (batch 15)
- team-profile-view.tsx: copy icon
- DateRangeFilter.tsx: check icon
- ColumnVisibilityButton.tsx: check icon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static Icon to @coss/ui/icons (batch 16)
- EventWorkflowsTab.tsx: lock
- event-types-listing-view.tsx: search
- onboarding-migrate-members-browser-view.tsx: arrow-left, arrow-right, rotate-cw, lock, ellipsis-vertical, check, user-plus
- onboarding-migrate-teams-browser-view.tsx: arrow-left, arrow-right, rotate-cw, lock, ellipsis-vertical, check, arrow-right, users
- EventAvailabilityTab.tsx: globe, user
- onboarding-teams-browser-view.tsx: arrow-left, arrow-right, rotate-cw, lock, ellipsis-vertical, check, arrow-right, users
- FormBuilder.tsx: mail, phone, arrow-up, arrow-down
- AIEventController.tsx: info
- EventTeamAssignmentTab.tsx: x
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static Icon to @coss/ui/icons (batch 17)
- EventAdvancedTab.tsx: pencil, info
- HostLocations.tsx: triangle-alert, loader
- EventTypeLayout.tsx: loader
- Locations.tsx: x, corner-down-right, check
- EditWeightsForAllTeamMembers.tsx: upload, search, info, chevron-down
- WorkflowPage.tsx: pencil
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static Icon to @coss/ui/icons (batch 18)
- TestPhoneCallDialog.tsx: phone
- WorkflowListPage.tsx: link
- UsersTable.tsx: building, lock
- TeamList.tsx: building, paintbrush, chart-line, user-plus, users, pencil
- bookings-single-view.tsx: chevron-left, check, calendar, x, external-link
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate static Icon to @coss/ui/icons (batch 19)
- TeamsListing.tsx: users, refresh-ccw, user-plus, mail, video, eye-off, info
- ConnectionInfo.tsx: clipboard
- IntercomContactForm.tsx: message-circle, loader, send
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate static Icon to @coss/ui/icons in teams components
- EventTypesList.tsx: check → CheckIcon
- MemberInvitationModal.tsx: user, users, building → UserIcon, UsersIcon, BuildingIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate static Icon to @coss/ui/icons in workflow components
- ImportNumberDialog.tsx: info → InfoIcon
- VoiceSelector.tsx: user → UserIcon
- IncomingCallsTab.tsx: info, phone-incoming → InfoIcon, PhoneIncomingIcon
- PhoneNumberTab.tsx: loader, phone → LoaderIcon, PhoneIcon
- VoiceSelectionDialog.tsx: pause, play, user → PauseIcon, PlayIcon, UserIcon
- WebCallDialog.tsx: loader, phone, phone-off, triangle-alert, trash, message-circle → new icons (kept Icon import for dynamic mic icon)
- WorkflowDetailsPage.tsx: zap, arrow-right → ZapIcon, ArrowRightIcon
- AddActionDialog.tsx: info → InfoIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate static Icon to @coss/ui/icons in WorkflowStepContainer
- info → InfoIcon (2 instances)
- phone → PhoneIcon (2 instances)
- circle-help → CircleHelpIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate static Icon to @coss/ui/icons in data-table and org components
- attributes-list-view.tsx: tags → TagsIcon
- AddFilterButton.tsx: plus, eye-off → PlusIcon, EyeOffIcon
- BaseSelectFilterOptions.tsx: check → CheckIcon
- DataTable.tsx: arrow-up, arrow-down, check, chevrons-up-down, eye-off → new icons
- booking-dry-run-success-view.tsx: check → CheckIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate static Icon to @coss/ui/icons in username and routing components
- UsernameTextfield.tsx: check → CheckIcon
- PremiumTextfield.tsx: check, external-link → CheckIcon, ExternalLinkIcon (kept Icon for star with fill prop)
- Header.tsx (routing-forms): pencil, menu, waypoints → PencilIcon, MenuIcon, WaypointsIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in routing-forms pages
Batch 25: Migrate static Icon usages to @coss/ui/icons components.
Files changed:
- Forms.tsx: file-text, shuffle, chart-bar, circle-check, mail, download → FileTextIcon, ShuffleIcon, ChartBarIcon, CircleCheckIcon, MailIcon, DownloadIcon
- FormEdit.tsx: chevron-down, menu → ChevronDownIcon, MenuIcon
- IncompleteBooking.tsx: globe → GlobeIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in UI package
Batch 26: Migrate static Icon usages to @coss/ui/icons components.
Files changed:
- ProgressToast.tsx: file-down, x → FileDownIcon, XIcon
- showToast.tsx: check, info, x → CheckIcon, InfoIcon, XIcon
- InfoBadge.tsx: info → InfoIcon
- MeetingTimeInTimezones.tsx: globe → GlobeIcon
- InputError.tsx: info → InfoIcon
- ConfirmationDialogContent.tsx: circle-alert, check → CircleAlertIcon, CheckIcon
- CalendarSwitch.tsx: arrow-left, rotate-cw → ArrowLeftIcon, RotateCwIcon
- ArrowButton.tsx: arrow-up, arrow-down → ArrowUpIcon, ArrowDownIcon
- FileUploader.tsx: upload → UploadIcon (kept Icon for dynamic)
- Checkbox.tsx: check → CheckIcon
- EditableHeading.tsx: pencil → PencilIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in UI and app-store packages
Batch 27: Migrate static Icon usages to @coss/ui/icons components.
Files changed:
- MultiEmail.tsx: x → XIcon (converted width to size)
- AppListCard.tsx: circle-alert → CircleAlertIcon
- SplitButton.tsx: chevron-down → ChevronDownIcon (kept Icon for dynamic)
- Calendar.tsx: chevron-left, chevron-right → ChevronLeftIcon, ChevronRightIcon
- AppDependencyComponent.tsx: check, circle-x, arrow-right → CheckIcon, CircleXIcon, ArrowRightIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in form input components
Batch 28: Migrate static Icon usages to @coss/ui/icons components.
Files changed:
- TextField.tsx: info → InfoIcon (kept Icon for x with onClick)
- Input.tsx: eye, eye-off, search → EyeIcon, EyeOffIcon, SearchIcon
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use @coss/ui/src/icons in packages with legacy moduleResolution
Packages like packages/ui and packages/app-store use a legacy moduleResolution
setting that doesn't support subpath exports. This changes the import path from
@coss/ui/icons to @coss/ui/src/icons for these packages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in UI form and editor components
Migrates static Icon usages:
- MultiOptionInput: x (2x), arrow-up, arrow-down
- components (select): check
- AddVariablesDropdown: chevron-down (3x)
- ToolbarPlugin: chevron-down
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: migrate Icon to component icons in settings and platform atoms
Migrates static Icon usages:
- SettingsLayoutAppDirClient: chevron-down (2x), chevron-right (2x), arrow-left
- platform/atoms dialog: x
- platform/atoms toast: x
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve @coss/ui/icons module resolution for legacy moduleResolution
Add typesVersions to @coss/ui package.json to help TypeScript resolve
subpath exports even with legacy moduleResolution: "node" setting.
Change imports from @coss/ui/src/icons to @coss/ui/icons to avoid
double-src path issue when Next.js applies import maps.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: localize 'Select field type' fallback label in FormEdit
Address Cubic AI review feedback (confidence 9/10) by using t() for
the hardcoded 'Select field type' string to ensure proper i18n support.
Co-Authored-By: unknown <>
* fix: add typesVersions wildcards for @coss/ui subpath imports
Add lib/*, components/*, and hooks/* to typesVersions to support
legacy moduleResolution with all package subpath patterns.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve missing Icon references in settings and workflows
- Add Icon import for dynamic icon usages in SettingsLayoutAppDirClient
- Replace static Icon usages with component icons (InfoIcon, PhoneIcon)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve icon component type errors
- Add onClick prop support to IconProps
- Fix size prop type (use number instead of string)
- Rename local MailOpenIcon to MailOpenIconWrapper to avoid shadowing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add data-testid to icon components for test compatibility
- Add testId parameter to createIcon function
- Auto-generate data-testid for all icons (e.g., "check-icon", "search-icon")
- Remove outdated mock for @calcom/ui/components/icon in tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update package.json
* Update package.json
* fix: migrate Icon to CalendarDaysIcon in videos-single-view.tsx
Co-Authored-By: unknown <>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
227 lines
8.3 KiB
TypeScript
227 lines
8.3 KiB
TypeScript
"use client";
|
|
|
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
|
import classNames from "@calcom/ui/classNames";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
import { MailOpenIcon, TriangleAlertIcon } from "@coss/ui/icons";
|
|
import { motion } from "framer-motion";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { signIn } from "next-auth/react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import z from "zod";
|
|
|
|
async function sendVerificationLogin(email: string, username: string, t: (key: string) => string) {
|
|
await signIn("email", {
|
|
email: email.toLowerCase(),
|
|
username: username.toLowerCase(),
|
|
redirect: false,
|
|
callbackUrl: WEBAPP_URL || "https://app.cal.com",
|
|
})
|
|
.then(() => {
|
|
showToast(t("verification_email_sent"), "success");
|
|
})
|
|
.catch((err) => {
|
|
showToast(err, "error");
|
|
});
|
|
}
|
|
|
|
function useSendFirstVerificationLogin({
|
|
email,
|
|
username,
|
|
}: {
|
|
email: string | undefined;
|
|
username: string | undefined;
|
|
}) {
|
|
const sent = useRef(false);
|
|
const { t } = useLocale();
|
|
useEffect(() => {
|
|
if (!email || !username || sent.current) {
|
|
return;
|
|
}
|
|
(async () => {
|
|
await sendVerificationLogin(email, username, t);
|
|
sent.current = true;
|
|
})();
|
|
}, [email, username, t]);
|
|
}
|
|
|
|
const querySchema = z.object({
|
|
email: z.string().optional(),
|
|
username: z.string().optional(),
|
|
paymentStatus: z.string().optional(),
|
|
t: z.string().optional(),
|
|
});
|
|
|
|
const PaymentFailedIcon = () => (
|
|
<div className="rounded-full bg-orange-900 p-3">
|
|
<TriangleAlertIcon className="h-6 w-6 shrink-0 p-0.5 font-extralight text-orange-100" />
|
|
</div>
|
|
);
|
|
|
|
const PaymentSuccess = () => (
|
|
<div
|
|
className="rounded-full"
|
|
style={{
|
|
padding: "6px",
|
|
border: "0.6px solid rgba(0, 0, 0, 0.02)",
|
|
background: "rgba(123, 203, 197, 0.10)",
|
|
}}>
|
|
<motion.div
|
|
className="rounded-full"
|
|
style={{
|
|
padding: "6px",
|
|
border: "0.6px solid rgba(0, 0, 0, 0.04)",
|
|
background: "rgba(123, 203, 197, 0.16)",
|
|
}}
|
|
animate={{ scale: [1, 1.1, 1] }} // Define the pulsing animation for the second ring
|
|
transition={{
|
|
duration: 1.5,
|
|
repeat: Infinity,
|
|
repeatType: "reverse",
|
|
delay: 0.2, // Delay the start of animation for the second ring
|
|
}}>
|
|
<motion.div
|
|
className="rounded-full p-3"
|
|
style={{
|
|
border: "1px solid rgba(255, 255, 255, 0.40)",
|
|
background: "linear-gradient(180deg, #66C9CF 0%, #9CCCB2 100%)",
|
|
}}>
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path
|
|
d="M2.69185 10.6919L2.9297 10.9297L2.69185 10.6919C1.96938 11.4143 1.96938 12.5857 2.69185 13.3081L7.69185 18.3081C8.41432 19.0306 9.58568 19.0306 10.3081 18.3081L21.3081 7.30815C22.0306 6.58568 22.0306 5.41432 21.3081 4.69185C20.5857 3.96938 19.4143 3.96938 18.6919 4.69185L9 14.3837L5.30815 10.6919C4.58568 9.96938 3.41432 9.96938 2.69185 10.6919Z"
|
|
fill="white"
|
|
stroke="#48BAAE"
|
|
strokeWidth="0.7"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
</motion.div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
|
|
const MailOpenIconWrapper = () => (
|
|
<div className="bg-default rounded-full p-3">
|
|
<MailOpenIcon className="text-emphasis h-12 w-12 shrink-0 p-0.5 font-extralight" />
|
|
</div>
|
|
);
|
|
|
|
export default function Verify({ EMAIL_FROM }: { EMAIL_FROM?: string }) {
|
|
const searchParams = useCompatSearchParams();
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const routerQuery = useRouterQuery();
|
|
const { email, username, paymentStatus } = querySchema.parse(routerQuery);
|
|
const { t } = useLocale();
|
|
const [secondsLeft, setSecondsLeft] = useState(30);
|
|
|
|
// Derive payment failed status from payment status
|
|
const hasPaymentFailed = paymentStatus !== undefined && paymentStatus !== "paid";
|
|
const isPremiumUsername = !!paymentStatus; // If paymentStatus exists, it's from premium username flow
|
|
|
|
// Only send verification login if we DON'T have the email yet (for resend button)
|
|
// The email is already sent server-side in paymentCallback
|
|
useSendFirstVerificationLogin({
|
|
email: !isPremiumUsername ? email : undefined,
|
|
username: !isPremiumUsername ? username : undefined,
|
|
});
|
|
// @note: check for t=timestamp and apply disabled state and secondsLeft accordingly
|
|
// to avoid refresh to skip waiting 30 seconds to re-send email
|
|
useEffect(() => {
|
|
const lastSent = new Date(parseInt(`${t}`));
|
|
// @note: This double round() looks ugly but it's the only way I came up to get the time difference in seconds
|
|
const difference = Math.round(Math.round(new Date().getTime() - lastSent.getTime()) / 1000);
|
|
if (difference < 30) {
|
|
// If less than 30 seconds, set the seconds left to 30 - difference
|
|
setSecondsLeft(30 - difference);
|
|
} else {
|
|
// else set the seconds left to 0 and disabled false
|
|
setSecondsLeft(0);
|
|
}
|
|
}, [t]);
|
|
// @note: here we make sure each second is decremented if disabled up to 0.
|
|
useEffect(() => {
|
|
if (secondsLeft > 0) {
|
|
const interval = setInterval(() => {
|
|
if (secondsLeft > 0) {
|
|
setSecondsLeft(secondsLeft - 1);
|
|
}
|
|
}, 1000);
|
|
return () => clearInterval(interval);
|
|
}
|
|
}, [secondsLeft]);
|
|
|
|
if (!email) {
|
|
return <div>{t("invalid_link")}</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="text-default bg-cal-muted/90 backdrop-blur-md backdrop-grayscale backdrop-filter">
|
|
<div className="flex min-h-screen flex-col items-center justify-center px-6">
|
|
<div className="border-subtle bg-default m-10 flex max-w-2xl flex-col items-center rounded-xl border px-8 py-14 text-left">
|
|
{hasPaymentFailed ? (
|
|
<PaymentFailedIcon />
|
|
) : isPremiumUsername ? (
|
|
<PaymentSuccess />
|
|
) : (
|
|
<MailOpenIconWrapper />
|
|
)}
|
|
<h3 className="font-cal text-emphasis my-6 text-2xl font-normal leading-none">
|
|
{hasPaymentFailed
|
|
? t("your_payment_failed")
|
|
: isPremiumUsername
|
|
? t("payment_successful")
|
|
: t("check_your_inbox")}
|
|
</h3>
|
|
{hasPaymentFailed && <p className="my-6">{t("account_created_premium_not_reserved")}</p>}
|
|
<p className="text-muted dark:text-subtle text-base font-normal">
|
|
{t("email_sent_with_activation_link", { email })}{" "}
|
|
{hasPaymentFailed && t("activate_account_to_purchase_username")}
|
|
</p>
|
|
<div className="mt-7">
|
|
<Button
|
|
color="secondary"
|
|
href={
|
|
EMAIL_FROM
|
|
? encodeURIComponent(`https://mail.google.com/mail/u/0/#search/from:${EMAIL_FROM}`)
|
|
: "https://mail.google.com/mail/u/0/"
|
|
}
|
|
target="_blank"
|
|
EndIcon="external-link">
|
|
{t("open_in_gmail")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-subtle text-base font-normal ">{t("dont_see_email")}</p>
|
|
<button
|
|
className={classNames(
|
|
"font-light",
|
|
secondsLeft > 0 ? "text-muted" : "underline underline-offset-2 hover:font-normal"
|
|
)}
|
|
disabled={secondsLeft > 0}
|
|
onClick={async (e) => {
|
|
if (!email || !username) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
setSecondsLeft(30);
|
|
// Update query params with t:timestamp, shallow: true doesn't re-render the page
|
|
const _searchParams = new URLSearchParams(searchParams?.toString());
|
|
_searchParams.set("t", `${Date.now()}`);
|
|
router.replace(`${pathname}?${_searchParams.toString()}`);
|
|
return await sendVerificationLogin(email, username, t);
|
|
}}>
|
|
{secondsLeft > 0 ? t("resend_in_seconds", { seconds: secondsLeft }) : t("resend")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|