Harden platform security, reliability, and CI after full audit.
Close deployment IDOR and gate stub payment endpoints, add production secret validation, health probes, Redis-backed build progress, GitHub Actions CI, expanded tests, billing/k8s refactors, and ops runbooks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '@/lib/api';
|
||||
import { queryKeys } from '@/lib/query-keys';
|
||||
import { notify } from '@/lib/notify';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
import { useLocalizedRouter } from '@/i18n/navigation';
|
||||
@@ -114,7 +115,7 @@ export function ManagedServiceResourcesPanel({
|
||||
const isDatabase = app.productType === 'managed_database';
|
||||
|
||||
const { data: walletData } = useQuery<{ balance: number }>({
|
||||
queryKey: ['wallet'],
|
||||
queryKey: queryKeys.walletBalance,
|
||||
queryFn: () => api.get('/billing/wallet').then((r) => r.data),
|
||||
});
|
||||
|
||||
@@ -181,7 +182,7 @@ export function ManagedServiceResourcesPanel({
|
||||
queryClient.invalidateQueries({ queryKey: ['resources', serviceId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['db-storage', serviceId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['storage-usage', serviceId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['wallet'] });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.walletBalance });
|
||||
setShowUpgradeConfirm(false);
|
||||
setUpgradeCostData(null);
|
||||
setPendingUpgradePayload(null);
|
||||
|
||||
@@ -29,7 +29,13 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
loadUser();
|
||||
}, [loadUser]);
|
||||
|
||||
if (!mounted) return null;
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-950">
|
||||
<div className="w-8 h-8 border-2 border-primary-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
type BadgeTone = 'gray' | 'green' | 'yellow' | 'red' | 'blue' | 'purple';
|
||||
|
||||
const toneClass: Record<BadgeTone, string> = {
|
||||
gray: 'badge-gray',
|
||||
green: 'badge-green',
|
||||
yellow: 'badge-yellow',
|
||||
red: 'badge-red',
|
||||
blue: 'badge-blue',
|
||||
purple: 'badge-purple',
|
||||
};
|
||||
|
||||
export function Badge({
|
||||
tone = 'gray',
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
tone?: BadgeTone;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <span className={clsx('badge', toneClass[tone], className)}>{children}</span>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import clsx from 'clsx';
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
|
||||
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
||||
|
||||
const variantClass: Record<ButtonVariant, string> = {
|
||||
primary: 'btn-primary',
|
||||
secondary: 'btn-secondary',
|
||||
danger: 'btn-danger',
|
||||
ghost: 'btn-ghost',
|
||||
};
|
||||
|
||||
export function Button({
|
||||
variant = 'primary',
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ButtonHTMLAttributes<HTMLButtonElement> & { variant?: ButtonVariant }) {
|
||||
return (
|
||||
<button type="button" className={clsx(variantClass[variant], className)} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import clsx from 'clsx';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export function Card({ className, children, ...props }: HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div className={clsx('card', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function PageHeader({
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
}: {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">{title}</h1>
|
||||
{description ? (
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user