mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-09-13 04:08:22 +02:00
Prettier 2: Electric Boogaloo (#6113)
# Description of Changes When I added Prettier formatting in #6052, my aim was to use just the default settings in Prettier. Turns out, Prettier looks _really hard_ for any config files if it's not explicitly given one, which means that if a developer has some sort of Prettier config file lying around on their system, Prettier might find it and use it. Also, Prettier changes its defaults based on stuff in `.editorconfig` without any good way of disabling that behaviour explicitly in its config file. To solve both of these issues, I've introduced a `.prettierrc` file which sets Prettier's defaults explicitly, and then reformatted all our code _again_ in Prettier's actual default settings. This should achieve the aim of #6052 and remove the possibility for it breaking on different dev computers.
This commit is contained in:
@@ -24,14 +24,25 @@ export default function SaasOnboardingModal(props: SaasOnboardingModalProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { currentStep, totalSteps, currentSlide, slideDefinition, flowState, handleButtonAction } = flow;
|
||||
const {
|
||||
currentStep,
|
||||
totalSteps,
|
||||
currentSlide,
|
||||
slideDefinition,
|
||||
flowState,
|
||||
handleButtonAction,
|
||||
} = flow;
|
||||
|
||||
const renderHero = () => {
|
||||
if (slideDefinition.hero.type === "dual-icon") {
|
||||
return (
|
||||
<div className={styles.heroIconsContainer}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<img src={`${BASE_PATH}/modern-logo/logo512.png`} alt="Stirling icon" className={styles.downloadIcon} />
|
||||
<img
|
||||
src={`${BASE_PATH}/modern-logo/logo512.png`}
|
||||
alt="Stirling icon"
|
||||
className={styles.downloadIcon}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -40,9 +51,16 @@ export default function SaasOnboardingModal(props: SaasOnboardingModalProps) {
|
||||
return (
|
||||
<div className={styles.heroLogoCircle}>
|
||||
{slideDefinition.hero.type === "rocket" && (
|
||||
<LocalIcon icon="rocket-launch" width={64} height={64} className={styles.heroIcon} />
|
||||
<LocalIcon
|
||||
icon="rocket-launch"
|
||||
width={64}
|
||||
height={64}
|
||||
className={styles.heroIcon}
|
||||
/>
|
||||
)}
|
||||
{slideDefinition.hero.type === "diamond" && (
|
||||
<DiamondOutlinedIcon sx={{ fontSize: 64, color: "#000000" }} />
|
||||
)}
|
||||
{slideDefinition.hero.type === "diamond" && <DiamondOutlinedIcon sx={{ fontSize: 64, color: "#000000" }} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -72,7 +90,12 @@ export default function SaasOnboardingModal(props: SaasOnboardingModalProps) {
|
||||
<Stack
|
||||
gap={0}
|
||||
className={styles.modalContent}
|
||||
style={{ height: "100%", maxHeight: "90vh", display: "flex", flexDirection: "column" }}
|
||||
style={{
|
||||
height: "100%",
|
||||
maxHeight: "90vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div className={styles.heroWrapper} style={{ flexShrink: 0 }}>
|
||||
<AnimatedSlideBackground
|
||||
@@ -96,18 +119,27 @@ export default function SaasOnboardingModal(props: SaasOnboardingModalProps) {
|
||||
}}
|
||||
>
|
||||
<Stack gap={16}>
|
||||
<div key={`title-${currentSlide.key}`} className={`${styles.title} ${styles.titleText}`}>
|
||||
<div
|
||||
key={`title-${currentSlide.key}`}
|
||||
className={`${styles.title} ${styles.titleText}`}
|
||||
>
|
||||
{currentSlide.title}
|
||||
</div>
|
||||
|
||||
<div className={styles.bodyText}>
|
||||
<div key={`body-${currentSlide.key}`} className={`${styles.bodyCopy} ${styles.bodyCopyInner}`}>
|
||||
<div
|
||||
key={`body-${currentSlide.key}`}
|
||||
className={`${styles.bodyCopy} ${styles.bodyCopyInner}`}
|
||||
>
|
||||
{currentSlide.body}
|
||||
</div>
|
||||
<style>{`div strong{color: var(--onboarding-title); font-weight: 600;}`}</style>
|
||||
</div>
|
||||
|
||||
<OnboardingStepper totalSteps={totalSteps} activeStep={currentStep} />
|
||||
<OnboardingStepper
|
||||
totalSteps={totalSteps}
|
||||
activeStep={currentStep}
|
||||
/>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
{renderButtons({
|
||||
|
||||
@@ -2,7 +2,11 @@ import React from "react";
|
||||
import { Button, Group, ActionIcon } from "@mantine/core";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import { TFunction } from "i18next";
|
||||
import { ButtonDefinition, type FlowState, type ButtonAction } from "@app/components/onboarding/saasOnboardingFlowConfig";
|
||||
import {
|
||||
ButtonDefinition,
|
||||
type FlowState,
|
||||
type ButtonAction,
|
||||
} from "@app/components/onboarding/saasOnboardingFlowConfig";
|
||||
|
||||
interface RenderButtonsProps {
|
||||
slideDefinition: {
|
||||
@@ -14,9 +18,18 @@ interface RenderButtonsProps {
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
export function renderButtons({ slideDefinition, flowState, onAction, t }: RenderButtonsProps) {
|
||||
const leftButtons = slideDefinition.buttons.filter((btn) => btn.group === "left");
|
||||
const rightButtons = slideDefinition.buttons.filter((btn) => btn.group === "right");
|
||||
export function renderButtons({
|
||||
slideDefinition,
|
||||
flowState,
|
||||
onAction,
|
||||
t,
|
||||
}: RenderButtonsProps) {
|
||||
const leftButtons = slideDefinition.buttons.filter(
|
||||
(btn) => btn.group === "left",
|
||||
);
|
||||
const rightButtons = slideDefinition.buttons.filter(
|
||||
(btn) => btn.group === "right",
|
||||
);
|
||||
|
||||
const buttonStyles = (variant: ButtonDefinition["variant"]) =>
|
||||
variant === "primary"
|
||||
@@ -63,7 +76,9 @@ export function renderButtons({ slideDefinition, flowState, onAction, t }: Rende
|
||||
},
|
||||
}}
|
||||
>
|
||||
{button.icon === "chevron-left" && <ChevronLeftIcon fontSize="small" />}
|
||||
{button.icon === "chevron-left" && (
|
||||
<ChevronLeftIcon fontSize="small" />
|
||||
)}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
@@ -72,7 +87,12 @@ export function renderButtons({ slideDefinition, flowState, onAction, t }: Rende
|
||||
const label = resolveButtonLabel(button);
|
||||
|
||||
return (
|
||||
<Button key={button.key} onClick={() => onAction(button.action)} disabled={disabled} styles={buttonStyles(variant)}>
|
||||
<Button
|
||||
key={button.key}
|
||||
onClick={() => onAction(button.action)}
|
||||
disabled={disabled}
|
||||
styles={buttonStyles(variant)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { TrialStatus } from "@app/auth/UseSession";
|
||||
import { FLOW_SEQUENCES, SlideId } from "@app/components/onboarding/saasOnboardingFlowConfig";
|
||||
import {
|
||||
FLOW_SEQUENCES,
|
||||
SlideId,
|
||||
} from "@app/components/onboarding/saasOnboardingFlowConfig";
|
||||
|
||||
export interface FlowConfig {
|
||||
type: "saas-trial" | "saas-paid";
|
||||
@@ -13,12 +16,16 @@ export interface FlowConfig {
|
||||
* @param _isPro - Whether user has Pro subscription
|
||||
* @returns FlowConfig with the appropriate slide sequence
|
||||
*/
|
||||
export function resolveSaasFlow(trialStatus: TrialStatus | null, _isPro: boolean | null): FlowConfig {
|
||||
export function resolveSaasFlow(
|
||||
trialStatus: TrialStatus | null,
|
||||
_isPro: boolean | null,
|
||||
): FlowConfig {
|
||||
// Show free trial card if:
|
||||
// 1. User has active trial (isTrialing = true)
|
||||
// 2. Trial has not expired (daysRemaining > 0)
|
||||
// 3. User is not paid Pro (or Pro is from trial)
|
||||
const hasActiveTrial = trialStatus?.isTrialing === true && trialStatus.daysRemaining > 0;
|
||||
const hasActiveTrial =
|
||||
trialStatus?.isTrialing === true && trialStatus.daysRemaining > 0;
|
||||
|
||||
if (hasActiveTrial) {
|
||||
return {
|
||||
|
||||
@@ -11,24 +11,35 @@ interface FreeTrialSlideProps {
|
||||
function FreeTrialSlideTitle() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <span>{t("onboarding.freeTrial.title", "Your 30-Day Pro Trial")}</span>;
|
||||
return (
|
||||
<span>{t("onboarding.freeTrial.title", "Your 30-Day Pro Trial")}</span>
|
||||
);
|
||||
}
|
||||
|
||||
const FreeTrialSlideBody = ({ trialStatus }: { trialStatus: TrialStatus }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Format the trial end date
|
||||
const trialEndDate = new Date(trialStatus.trialEnd).toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
const trialEndDate = new Date(trialStatus.trialEnd).toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
},
|
||||
);
|
||||
|
||||
// Determine which message to show based on payment method
|
||||
const afterTrialMessage = trialStatus.hasScheduledSub
|
||||
? t("onboarding.freeTrial.afterTrialWithPayment", "Your Pro subscription will start automatically when the trial ends.")
|
||||
? t(
|
||||
"onboarding.freeTrial.afterTrialWithPayment",
|
||||
"Your Pro subscription will start automatically when the trial ends.",
|
||||
)
|
||||
: trialStatus.hasPaymentMethod
|
||||
? t("onboarding.freeTrial.afterTrialWithPayment", "Your Pro subscription will start automatically when the trial ends.")
|
||||
? t(
|
||||
"onboarding.freeTrial.afterTrialWithPayment",
|
||||
"Your Pro subscription will start automatically when the trial ends.",
|
||||
)
|
||||
: t(
|
||||
"onboarding.freeTrial.afterTrialWithoutPayment",
|
||||
"After your trial ends, you'll continue with our free tier. Add a payment method to keep Pro access.",
|
||||
@@ -37,8 +48,14 @@ const FreeTrialSlideBody = ({ trialStatus }: { trialStatus: TrialStatus }) => {
|
||||
// Pluralize days remaining
|
||||
const daysText =
|
||||
trialStatus.daysRemaining === 1
|
||||
? t("onboarding.freeTrial.daysRemainingSingular", "{{days}} day remaining", { days: trialStatus.daysRemaining })
|
||||
: t("onboarding.freeTrial.daysRemaining", "{{days}} days remaining", { days: trialStatus.daysRemaining });
|
||||
? t(
|
||||
"onboarding.freeTrial.daysRemainingSingular",
|
||||
"{{days}} day remaining",
|
||||
{ days: trialStatus.daysRemaining },
|
||||
)
|
||||
: t("onboarding.freeTrial.daysRemaining", "{{days}} days remaining", {
|
||||
days: trialStatus.daysRemaining,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
||||
@@ -56,9 +73,19 @@ const FreeTrialSlideBody = ({ trialStatus }: { trialStatus: TrialStatus }) => {
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: "1.25rem", fontWeight: "bold", marginBottom: "0.5rem" }}>{daysText}</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{daysText}
|
||||
</div>
|
||||
<div style={{ fontSize: "0.9rem", opacity: 0.9 }}>
|
||||
{t("onboarding.freeTrial.trialEnds", "Trial ends {{date}}", { date: trialEndDate })}
|
||||
{t("onboarding.freeTrial.trialEnds", "Trial ends {{date}}", {
|
||||
date: trialEndDate,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontSize: "0.9rem", opacity: 0.9 }}>{afterTrialMessage}</p>
|
||||
@@ -66,7 +93,9 @@ const FreeTrialSlideBody = ({ trialStatus }: { trialStatus: TrialStatus }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default function FreeTrialSlide({ trialStatus }: FreeTrialSlideProps): SlideConfig {
|
||||
export default function FreeTrialSlide({
|
||||
trialStatus,
|
||||
}: FreeTrialSlideProps): SlideConfig {
|
||||
return {
|
||||
key: "free-trial",
|
||||
title: <FreeTrialSlideTitle />,
|
||||
|
||||
@@ -24,7 +24,10 @@ interface UseSaasOnboardingStateProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingStateProps): UseSaasOnboardingStateResult | null {
|
||||
export function useSaasOnboardingState({
|
||||
opened,
|
||||
onClose,
|
||||
}: UseSaasOnboardingStateProps): UseSaasOnboardingStateResult | null {
|
||||
const { trialStatus, isPro, loading } = useAuth();
|
||||
const osType = useOs();
|
||||
const selectedDownloadUrlRef = useRef<string>("");
|
||||
@@ -44,7 +47,10 @@ export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingSta
|
||||
case "windows":
|
||||
return { label: "Windows", url: DOWNLOAD_URLS.WINDOWS };
|
||||
case "mac-apple":
|
||||
return { label: "Mac (Apple Silicon)", url: DOWNLOAD_URLS.MAC_APPLE_SILICON };
|
||||
return {
|
||||
label: "Mac (Apple Silicon)",
|
||||
url: DOWNLOAD_URLS.MAC_APPLE_SILICON,
|
||||
};
|
||||
case "mac-intel":
|
||||
return { label: "Mac (Intel)", url: DOWNLOAD_URLS.MAC_INTEL };
|
||||
case "linux-x64":
|
||||
@@ -58,8 +64,16 @@ export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingSta
|
||||
const osOptions = useMemo(() => {
|
||||
const options = [
|
||||
{ label: "Windows", url: DOWNLOAD_URLS.WINDOWS, value: "windows" },
|
||||
{ label: "Mac (Apple Silicon)", url: DOWNLOAD_URLS.MAC_APPLE_SILICON, value: "mac-apple" },
|
||||
{ label: "Mac (Intel)", url: DOWNLOAD_URLS.MAC_INTEL, value: "mac-intel" },
|
||||
{
|
||||
label: "Mac (Apple Silicon)",
|
||||
url: DOWNLOAD_URLS.MAC_APPLE_SILICON,
|
||||
value: "mac-apple",
|
||||
},
|
||||
{
|
||||
label: "Mac (Intel)",
|
||||
url: DOWNLOAD_URLS.MAC_INTEL,
|
||||
value: "mac-intel",
|
||||
},
|
||||
{ label: "Linux", url: DOWNLOAD_URLS.LINUX_DOCS, value: "linux" },
|
||||
];
|
||||
return options.filter((opt) => opt.url);
|
||||
@@ -71,7 +85,10 @@ export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingSta
|
||||
}, []);
|
||||
|
||||
// Resolve flow based on trial status
|
||||
const resolvedFlow = useMemo(() => resolveSaasFlow(trialStatus, isPro), [trialStatus, isPro]);
|
||||
const resolvedFlow = useMemo(
|
||||
() => resolveSaasFlow(trialStatus, isPro),
|
||||
[trialStatus, isPro],
|
||||
);
|
||||
|
||||
const flowSlideIds = resolvedFlow.ids;
|
||||
const totalSteps = flowSlideIds.length;
|
||||
@@ -84,7 +101,8 @@ export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingSta
|
||||
}
|
||||
}, [flowSlideIds.length, currentStep]);
|
||||
|
||||
const currentSlideId = flowSlideIds[currentStep] ?? flowSlideIds[flowSlideIds.length - 1];
|
||||
const currentSlideId =
|
||||
flowSlideIds[currentStep] ?? flowSlideIds[flowSlideIds.length - 1];
|
||||
const slideDefinition = SLIDE_DEFINITIONS[currentSlideId];
|
||||
|
||||
// Create slide with appropriate params - must be called before any early returns
|
||||
@@ -97,7 +115,14 @@ export function useSaasOnboardingState({ opened, onClose }: UseSaasOnboardingSta
|
||||
onDownloadUrlChange: handleDownloadUrlChange,
|
||||
trialStatus: trialStatus ?? undefined,
|
||||
});
|
||||
}, [slideDefinition, os.label, os.url, osOptions, handleDownloadUrlChange, trialStatus]);
|
||||
}, [
|
||||
slideDefinition,
|
||||
os.label,
|
||||
os.url,
|
||||
osOptions,
|
||||
handleDownloadUrlChange,
|
||||
trialStatus,
|
||||
]);
|
||||
|
||||
// Navigation functions
|
||||
const goNext = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user