mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +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:
@@ -1,4 +1,10 @@
|
||||
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { SaaSStripeCheckout } from "@app/components/shared/billing/SaaSStripeCheckout";
|
||||
import { useSaaSBilling } from "@app/contexts/SaasBillingContext";
|
||||
|
||||
@@ -9,7 +15,9 @@ interface SaaSCheckoutContextType {
|
||||
closeCheckout: () => void;
|
||||
}
|
||||
|
||||
const SaaSCheckoutContext = createContext<SaaSCheckoutContextType | undefined>(undefined);
|
||||
const SaaSCheckoutContext = createContext<SaaSCheckoutContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const useSaaSCheckout = () => {
|
||||
const context = useContext(SaaSCheckoutContext);
|
||||
@@ -23,7 +31,9 @@ interface SaaSCheckoutProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const SaaSCheckoutProvider: React.FC<SaaSCheckoutProviderProps> = ({ children }) => {
|
||||
export const SaaSCheckoutProvider: React.FC<SaaSCheckoutProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
|
||||
|
||||
@@ -48,7 +58,10 @@ export const SaaSCheckoutProvider: React.FC<SaaSCheckoutProviderProps> = ({ chil
|
||||
try {
|
||||
await refreshBilling();
|
||||
} catch (error) {
|
||||
console.error("[SaaSCheckoutContext] Failed to refresh billing after checkout:", error);
|
||||
console.error(
|
||||
"[SaaSCheckoutContext] Failed to refresh billing after checkout:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}, [refreshBilling]);
|
||||
|
||||
@@ -62,7 +75,12 @@ export const SaaSCheckoutProvider: React.FC<SaaSCheckoutProviderProps> = ({ chil
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<SaaSStripeCheckout opened={opened} onClose={closeCheckout} planId={selectedPlan} onSuccess={handleCheckoutSuccess} />
|
||||
<SaaSStripeCheckout
|
||||
opened={opened}
|
||||
onClose={closeCheckout}
|
||||
planId={selectedPlan}
|
||||
onSuccess={handleCheckoutSuccess}
|
||||
/>
|
||||
</SaaSCheckoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import { authService } from "@app/services/authService";
|
||||
import { connectionModeService } from "@app/services/connectionModeService";
|
||||
@@ -81,7 +88,9 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||
const [teamInvitations, setTeamInvitations] = useState<TeamInvitation[]>([]);
|
||||
const [receivedInvitations, setReceivedInvitations] = useState<TeamInvitation[]>([]);
|
||||
const [receivedInvitations, setReceivedInvitations] = useState<
|
||||
TeamInvitation[]
|
||||
>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaasMode, setIsSaasMode] = useState(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
@@ -98,7 +107,8 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
checkAccess();
|
||||
|
||||
// Subscribe to connection mode changes
|
||||
const unsubscribe = connectionModeService.subscribeToModeChanges(checkAccess);
|
||||
const unsubscribe =
|
||||
connectionModeService.subscribeToModeChanges(checkAccess);
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
@@ -113,12 +123,16 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
const fetchMyTeams = useCallback(async () => {
|
||||
// CRITICAL: Only fetch if in SaaS mode and authenticated
|
||||
if (!isSaasMode || !isAuthenticated) {
|
||||
console.log("[SaaSTeamContext] Skipping team fetch - not in SaaS mode or not authenticated");
|
||||
console.log(
|
||||
"[SaaSTeamContext] Skipping team fetch - not in SaaS mode or not authenticated",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get<Team[]>("/api/v1/team/my", { suppressErrorToast: true });
|
||||
const response = await apiClient.get<Team[]>("/api/v1/team/my", {
|
||||
suppressErrorToast: true,
|
||||
});
|
||||
setTeams(response.data);
|
||||
|
||||
const activeTeam = response.data[0];
|
||||
@@ -140,12 +154,17 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
async (teamId: number) => {
|
||||
// CRITICAL: Only fetch if in SaaS mode and authenticated
|
||||
if (!isSaasMode || !isAuthenticated) {
|
||||
console.log("[SaaSTeamContext] Skipping members fetch - not in SaaS mode or not authenticated");
|
||||
console.log(
|
||||
"[SaaSTeamContext] Skipping members fetch - not in SaaS mode or not authenticated",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get<TeamMember[]>(`/api/v1/team/${teamId}/members`, { suppressErrorToast: true });
|
||||
const response = await apiClient.get<TeamMember[]>(
|
||||
`/api/v1/team/${teamId}/members`,
|
||||
{ suppressErrorToast: true },
|
||||
);
|
||||
setTeamMembers(response.data);
|
||||
} catch (error) {
|
||||
console.error("[SaaSTeamContext] Failed to fetch team members:", error);
|
||||
@@ -162,12 +181,18 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get<TeamInvitation[]>(`/api/v1/team/${teamId}/invitations`, {
|
||||
suppressErrorToast: true,
|
||||
});
|
||||
const response = await apiClient.get<TeamInvitation[]>(
|
||||
`/api/v1/team/${teamId}/invitations`,
|
||||
{
|
||||
suppressErrorToast: true,
|
||||
},
|
||||
);
|
||||
setTeamInvitations(response.data);
|
||||
} catch (error) {
|
||||
console.error("[SaaSTeamContext] Failed to fetch team invitations:", error);
|
||||
console.error(
|
||||
"[SaaSTeamContext] Failed to fetch team invitations:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
},
|
||||
[isSaasMode, isAuthenticated],
|
||||
@@ -182,11 +207,20 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
console.log("[SaaSTeamContext] Fetching received team invitations");
|
||||
|
||||
try {
|
||||
const response = await apiClient.get<TeamInvitation[]>("/api/v1/team/invitations/pending", { suppressErrorToast: true });
|
||||
console.log("[SaaSTeamContext] Received invitations response:", response.data);
|
||||
const response = await apiClient.get<TeamInvitation[]>(
|
||||
"/api/v1/team/invitations/pending",
|
||||
{ suppressErrorToast: true },
|
||||
);
|
||||
console.log(
|
||||
"[SaaSTeamContext] Received invitations response:",
|
||||
response.data,
|
||||
);
|
||||
setReceivedInvitations(response.data);
|
||||
} catch (error) {
|
||||
console.error("[SaaSTeamContext] Failed to fetch received invitations:", error);
|
||||
console.error(
|
||||
"[SaaSTeamContext] Failed to fetch received invitations:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}, [isSaasMode, isAuthenticated]);
|
||||
|
||||
@@ -206,7 +240,12 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
}, [isSaasMode, isAuthenticated, fetchMyTeams, fetchReceivedInvitations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentTeam && !currentTeam.isPersonal && isSaasMode && isAuthenticated) {
|
||||
if (
|
||||
currentTeam &&
|
||||
!currentTeam.isPersonal &&
|
||||
isSaasMode &&
|
||||
isAuthenticated
|
||||
) {
|
||||
fetchTeamMembers(currentTeam.teamId);
|
||||
// Only fetch invitations if user is team leader
|
||||
if (currentTeam.isLeader) {
|
||||
@@ -219,7 +258,13 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
setTeamInvitations([]);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [currentTeam, isSaasMode, isAuthenticated, fetchTeamMembers, fetchTeamInvitations]);
|
||||
}, [
|
||||
currentTeam,
|
||||
isSaasMode,
|
||||
isAuthenticated,
|
||||
fetchTeamMembers,
|
||||
fetchTeamInvitations,
|
||||
]);
|
||||
|
||||
const inviteUser = async (email: string) => {
|
||||
if (!currentTeam) throw new Error("No current team");
|
||||
@@ -261,7 +306,9 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
if (!currentTeam) throw new Error("No current team");
|
||||
if (!isSaasMode) throw new Error("Not in SaaS mode");
|
||||
|
||||
await apiClient.delete(`/api/v1/team/${currentTeam.teamId}/members/${memberId}`);
|
||||
await apiClient.delete(
|
||||
`/api/v1/team/${currentTeam.teamId}/members/${memberId}`,
|
||||
);
|
||||
await refreshTeams();
|
||||
await fetchTeamMembers(currentTeam.teamId);
|
||||
};
|
||||
@@ -277,7 +324,9 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const refreshTeams = useCallback(async () => {
|
||||
if (!isSaasMode || !isAuthenticated) {
|
||||
console.log("[SaaSTeamContext] Skipping refresh - not in SaaS mode or not authenticated");
|
||||
console.log(
|
||||
"[SaaSTeamContext] Skipping refresh - not in SaaS mode or not authenticated",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,7 +339,14 @@ export function SaaSTeamProvider({ children }: { children: ReactNode }) {
|
||||
await fetchTeamInvitations(newCurrentTeam.teamId);
|
||||
}
|
||||
}
|
||||
}, [isSaasMode, isAuthenticated, fetchMyTeams, fetchReceivedInvitations, fetchTeamMembers, fetchTeamInvitations]);
|
||||
}, [
|
||||
isSaasMode,
|
||||
isAuthenticated,
|
||||
fetchMyTeams,
|
||||
fetchReceivedInvitations,
|
||||
fetchTeamMembers,
|
||||
fetchTeamInvitations,
|
||||
]);
|
||||
|
||||
const isTeamLeader = currentTeam?.isLeader ?? false;
|
||||
const isPersonalTeam = currentTeam?.isPersonal ?? true;
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback, useRef, useMemo } from "react";
|
||||
import { saasBillingService, BillingStatus, PlanPrice } from "@app/services/saasBillingService";
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useRef,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import {
|
||||
saasBillingService,
|
||||
BillingStatus,
|
||||
PlanPrice,
|
||||
} from "@app/services/saasBillingService";
|
||||
import { authService } from "@app/services/authService";
|
||||
import { connectionModeService } from "@app/services/connectionModeService";
|
||||
import { useSaaSTeam } from "@app/contexts/SaaSTeamContext";
|
||||
@@ -80,7 +93,9 @@ const SaasBillingContext = createContext<SaasBillingContextType>({
|
||||
});
|
||||
|
||||
export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
const [billingStatus, setBillingStatus] = useState<BillingStatus | null>(null);
|
||||
const [billingStatus, setBillingStatus] = useState<BillingStatus | null>(
|
||||
null,
|
||||
);
|
||||
const [plans, setPlans] = useState<Map<string, PlanPrice>>(new Map());
|
||||
const [plansLoading, setPlansLoading] = useState(false);
|
||||
const [plansError, setPlansError] = useState<string | null>(null);
|
||||
@@ -89,24 +104,35 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
// lastFetchTimeRef is the source of truth for cache logic (always current, no stale closure).
|
||||
// lastFetchTimeValue mirrors it as state purely to drive re-renders and expose through context.
|
||||
const lastFetchTimeRef = useRef<number | null>(null);
|
||||
const [lastFetchTimeValue, setLastFetchTimeValue] = useState<number | null>(null);
|
||||
const [lastFetchTimeValue, setLastFetchTimeValue] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
// billingStatusRef mirrors billingStatus so fetchBillingData can read the current value
|
||||
// without needing billingStatus in its useCallback dep array.
|
||||
const billingStatusRef = useRef<BillingStatus | null>(null);
|
||||
// plansLastFetchTimeRef is the source of truth for timing; plansLastFetchTimeValue
|
||||
// is the state mirror exposed via context so consumers can react to it.
|
||||
const plansLastFetchTimeRef = useRef<number | null>(null);
|
||||
const [plansLastFetchTimeValue, setPlansLastFetchTimeValue] = useState<number | null>(null);
|
||||
const [plansLastFetchTimeValue, setPlansLastFetchTimeValue] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
// In-flight deduplication — prevents concurrent duplicate network requests.
|
||||
const plansFetchInProgressRef = useRef(false);
|
||||
const [isSaasMode, setIsSaasMode] = useState(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
// Access team context for derived state
|
||||
const { currentTeam, isPersonalTeam, isTeamLeader, loading: teamLoading } = useSaaSTeam();
|
||||
const {
|
||||
currentTeam,
|
||||
isPersonalTeam,
|
||||
isTeamLeader,
|
||||
loading: teamLoading,
|
||||
} = useSaaSTeam();
|
||||
|
||||
// Compute derived state: user is managed member if in non-personal team but not leader
|
||||
const isManagedTeamMember = currentTeam ? !isPersonalTeam && !isTeamLeader : false;
|
||||
const isManagedTeamMember = currentTeam
|
||||
? !isPersonalTeam && !isTeamLeader
|
||||
: false;
|
||||
|
||||
// Check if in SaaS mode and authenticated (same pattern as SaaSTeamContext)
|
||||
useEffect(() => {
|
||||
@@ -120,7 +146,8 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
checkAccess();
|
||||
|
||||
// Subscribe to connection mode changes
|
||||
const unsubscribe = connectionModeService.subscribeToModeChanges(checkAccess);
|
||||
const unsubscribe =
|
||||
connectionModeService.subscribeToModeChanges(checkAccess);
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
@@ -151,7 +178,11 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
// Cache check: Skip if fresh data exists (<5 min old)
|
||||
const now = Date.now();
|
||||
if (billingStatusRef.current && lastFetchTimeRef.current && now - lastFetchTimeRef.current < 300000) {
|
||||
if (
|
||||
billingStatusRef.current &&
|
||||
lastFetchTimeRef.current &&
|
||||
now - lastFetchTimeRef.current < 300000
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,7 +230,9 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
setPlansLastFetchTimeValue(now);
|
||||
} catch (err) {
|
||||
console.error("[SaasBillingContext] Failed to fetch plans:", err);
|
||||
setPlansError(err instanceof Error ? err.message : "Failed to fetch plans");
|
||||
setPlansError(
|
||||
err instanceof Error ? err.message : "Failed to fetch plans",
|
||||
);
|
||||
} finally {
|
||||
plansFetchInProgressRef.current = false;
|
||||
setPlansLoading(false);
|
||||
@@ -237,7 +270,13 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
) {
|
||||
fetchBillingData();
|
||||
}
|
||||
}, [isSaasMode, isAuthenticated, teamLoading, isManagedTeamMember, fetchBillingData]);
|
||||
}, [
|
||||
isSaasMode,
|
||||
isAuthenticated,
|
||||
teamLoading,
|
||||
isManagedTeamMember,
|
||||
fetchBillingData,
|
||||
]);
|
||||
|
||||
// Public refresh methods
|
||||
const refreshBilling = useCallback(async () => {
|
||||
@@ -282,7 +321,10 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
if (newBalance <= 0 && (billingStatus.creditBalance ?? 0) > 0) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("credits:exhausted", {
|
||||
detail: { previousBalance: billingStatus.creditBalance ?? 0, currentBalance: newBalance },
|
||||
detail: {
|
||||
previousBalance: billingStatus.creditBalance ?? 0,
|
||||
currentBalance: newBalance,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -336,7 +378,11 @@ export function SaasBillingProvider({ children }: { children: ReactNode }) {
|
||||
],
|
||||
);
|
||||
|
||||
return <SaasBillingContext.Provider value={contextValue}>{children}</SaasBillingContext.Provider>;
|
||||
return (
|
||||
<SaasBillingContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</SaasBillingContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSaaSBilling() {
|
||||
@@ -349,12 +395,21 @@ export function useSaaSBilling() {
|
||||
// Note: context.loading includes teamLoading, so this waits for team to load
|
||||
useEffect(() => {
|
||||
const needsFetch =
|
||||
context.billingStatus === null && context.lastFetchTime === null && !context.loading && !context.isManagedTeamMember; // Managed members don't need billing data
|
||||
context.billingStatus === null &&
|
||||
context.lastFetchTime === null &&
|
||||
!context.loading &&
|
||||
!context.isManagedTeamMember; // Managed members don't need billing data
|
||||
|
||||
if (needsFetch) {
|
||||
context.refreshBilling();
|
||||
}
|
||||
}, [context.billingStatus, context.lastFetchTime, context.loading, context.isManagedTeamMember, context.refreshBilling]);
|
||||
}, [
|
||||
context.billingStatus,
|
||||
context.lastFetchTime,
|
||||
context.loading,
|
||||
context.isManagedTeamMember,
|
||||
context.refreshBilling,
|
||||
]);
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -365,10 +420,13 @@ export function useSaaSBilling() {
|
||||
* Safe to call from multiple components — in-flight deduplication is handled by fetchPlansData.
|
||||
*/
|
||||
export function usePlanPricing() {
|
||||
const { plans, plansLoading, plansError, plansLastFetchTime, refreshPlans } = useContext(SaasBillingContext);
|
||||
const { plans, plansLoading, plansError, plansLastFetchTime, refreshPlans } =
|
||||
useContext(SaasBillingContext);
|
||||
|
||||
useEffect(() => {
|
||||
const isFresh = plansLastFetchTime !== null && Date.now() - plansLastFetchTime < PLANS_CACHE_TTL_MS;
|
||||
const isFresh =
|
||||
plansLastFetchTime !== null &&
|
||||
Date.now() - plansLastFetchTime < PLANS_CACHE_TTL_MS;
|
||||
|
||||
if (!isFresh) {
|
||||
refreshPlans();
|
||||
|
||||
Reference in New Issue
Block a user