merge: 同步 upstream/main 新功能到定制版本

- feat: Add slider verification code (bd67997)
- fix bug: Inventory cannot be zero (1f7a6ee)
- fix: resolve merge conflicts and lint errors
This commit is contained in:
2026-03-23 21:50:10 -07:00
parent 3806264343
commit d6616c5859
63 changed files with 3111 additions and 1130 deletions
+61 -51
View File
@@ -1,22 +1,5 @@
"use client";
import { Button } from "@workspace/ui/components/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import {
getGroupConfig,
updateGroupConfig,
resetGroups,
} from "@workspace/ui/services/admin/group";
import {
AlertDialog,
AlertDialogAction,
@@ -28,6 +11,23 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from "@workspace/ui/components/alert-dialog";
import { Button } from "@workspace/ui/components/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card";
import {
getGroupConfig,
resetGroups,
updateGroupConfig,
} from "@workspace/ui/services/admin/group";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export default function GroupConfig() {
const { t } = useTranslation("group");
@@ -47,8 +47,11 @@ export default function GroupConfig() {
const { data } = await getGroupConfig();
if (data.data) {
setConfig({
enabled: data.data.enabled || false,
mode: (data.data.mode || "average") as "average" | "subscribe" | "traffic",
enabled: data.data.enabled,
mode: (data.data.mode || "average") as
| "average"
| "subscribe"
| "traffic",
});
}
} catch (error) {
@@ -79,7 +82,9 @@ export default function GroupConfig() {
}
};
const handleUpdateMode = async (mode: "average" | "subscribe" | "traffic") => {
const handleUpdateMode = async (
mode: "average" | "subscribe" | "traffic"
) => {
setSaving(true);
try {
const payload: any = {
@@ -101,7 +106,9 @@ export default function GroupConfig() {
setResetting(true);
try {
await resetGroups({ confirm: true });
toast.success(t("resetSuccess", "All groups have been reset successfully"));
toast.success(
t("resetSuccess", "All groups have been reset successfully")
);
setShowResetDialog(false);
// Reload config after reset
await loadConfig();
@@ -129,10 +136,10 @@ export default function GroupConfig() {
{/* Enable/Disable */}
<div className="flex items-center justify-between">
<div>
<label htmlFor="enabled" className="font-medium">
<label className="font-medium" htmlFor="enabled">
{t("enableGrouping", "Enable Grouping")}
</label>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
{t(
"enableGroupingDescription",
"When enabled, users will only see nodes from their assigned group"
@@ -140,36 +147,36 @@ export default function GroupConfig() {
</p>
</div>
<input
id="enabled"
type="checkbox"
checked={config.enabled}
onChange={(e) => handleUpdateEnabled(e.target.checked)}
disabled={saving}
className="h-4 w-4"
disabled={saving}
id="enabled"
onChange={(e) => handleUpdateEnabled(e.target.checked)}
type="checkbox"
/>
</div>
{/* Mode Selection */}
{config.enabled && (
<div className="space-y-2">
<label className="font-medium">
<p className="font-medium">
{t("groupingMode", "Grouping Mode")}
</label>
</p>
<div className="grid grid-cols-3 gap-4">
<button
type="button"
onClick={() => handleUpdateMode("average")}
disabled={saving}
className={`rounded-lg border p-4 text-left transition-colors ${
config.mode === "average"
? "border-primary bg-primary/10"
: "border-border hover:bg-muted"
} ${saving ? "opacity-50 cursor-not-allowed" : ""}`}
} ${saving ? "cursor-not-allowed opacity-50" : ""}`}
disabled={saving}
onClick={() => handleUpdateMode("average")}
type="button"
>
<div className="font-medium">
{t("averageMode", "Average Mode")}
</div>
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
{t(
"averageModeDescription",
"Distribute users evenly across groups"
@@ -178,19 +185,19 @@ export default function GroupConfig() {
</button>
<button
type="button"
onClick={() => handleUpdateMode("subscribe")}
disabled={saving}
className={`rounded-lg border p-4 text-left transition-colors ${
config.mode === "subscribe"
? "border-primary bg-primary/10"
: "border-border hover:bg-muted"
} ${saving ? "opacity-50 cursor-not-allowed" : ""}`}
} ${saving ? "cursor-not-allowed opacity-50" : ""}`}
disabled={saving}
onClick={() => handleUpdateMode("subscribe")}
type="button"
>
<div className="font-medium">
{t("subscribeMode", "Subscribe Mode")}
</div>
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
{t(
"subscribeModeDescription",
"Group users by their subscription plan"
@@ -199,19 +206,19 @@ export default function GroupConfig() {
</button>
<button
type="button"
onClick={() => handleUpdateMode("traffic")}
disabled={saving}
className={`rounded-lg border p-4 text-left transition-colors ${
config.mode === "traffic"
? "border-primary bg-primary/10"
: "border-border hover:bg-muted"
} ${saving ? "opacity-50 cursor-not-allowed" : ""}`}
} ${saving ? "cursor-not-allowed opacity-50" : ""}`}
disabled={saving}
onClick={() => handleUpdateMode("traffic")}
type="button"
>
<div className="font-medium">
{t("trafficMode", "Traffic Mode")}
</div>
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
{t(
"trafficModeDescription",
"Group users by their traffic usage"
@@ -223,8 +230,11 @@ export default function GroupConfig() {
)}
{/* Reset Button */}
<div className="flex justify-end pt-4 border-t">
<AlertDialog open={showResetDialog} onOpenChange={setShowResetDialog}>
<div className="flex justify-end border-t pt-4">
<AlertDialog
onOpenChange={setShowResetDialog}
open={showResetDialog}
>
<AlertDialogTrigger asChild>
<Button variant="destructive">
{t("resetGroups", "Reset All Groups")}
@@ -243,14 +253,14 @@ export default function GroupConfig() {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("cancel", "Cancel")}
</AlertDialogCancel>
<AlertDialogCancel>{t("cancel", "Cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={handleResetGroups}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={handleResetGroups}
>
{resetting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{resetting && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
{t("confirm", "Confirm")}
</AlertDialogAction>
</AlertDialogFooter>