feat: 添加许可证停用功能及确认弹窗
- 添加反激活许可证的确认弹窗功能,包含二次确认提示和操作反馈。 - 添加停用许可证的接口合约定义。 - 添加许可证停用功能,确保激活记录存在后将其许可证信息和激活时间清空。
This commit is contained in:
@@ -14,6 +14,7 @@ export const Route = createFileRoute('/license')({
|
|||||||
function License() {
|
function License() {
|
||||||
const [licenseInput, setLicenseInput] = useState('')
|
const [licenseInput, setLicenseInput] = useState('')
|
||||||
const [copySuccess, setCopySuccess] = useState(false)
|
const [copySuccess, setCopySuccess] = useState(false)
|
||||||
|
const [showDeactivateConfirm, setShowDeactivateConfirm] = useState(false)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
// 获取激活状态
|
// 获取激活状态
|
||||||
@@ -32,11 +33,26 @@ function License() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 反激活 mutation
|
||||||
|
const deactivateMutation = useMutation({
|
||||||
|
...orpc.license.deactivate.mutationOptions(),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: orpc.license.getActivation.key(),
|
||||||
|
})
|
||||||
|
setShowDeactivateConfirm(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const handleActivate = () => {
|
const handleActivate = () => {
|
||||||
if (!licenseInput.trim()) return
|
if (!licenseInput.trim()) return
|
||||||
activateMutation.mutate({ license: licenseInput.trim() })
|
activateMutation.mutate({ license: licenseInput.trim() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeactivate = () => {
|
||||||
|
deactivateMutation.mutate()
|
||||||
|
}
|
||||||
|
|
||||||
const handleCopyFingerprint = async () => {
|
const handleCopyFingerprint = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(data.fingerprint)
|
await navigator.clipboard.writeText(data.fingerprint)
|
||||||
@@ -352,6 +368,62 @@ function License() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!showDeactivateConfirm ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowDeactivateConfirm(true)}
|
||||||
|
className="mt-4 px-4 py-2 bg-red-500 text-white rounded-lg text-sm font-medium hover:bg-red-600 transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
反激活
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className="mt-4 p-4 bg-red-50 rounded-lg border border-red-200">
|
||||||
|
<p className="text-red-700 text-sm mb-3 flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||||||
|
<path d="M12 9v4" />
|
||||||
|
<path d="M12 17h.01" />
|
||||||
|
</svg>
|
||||||
|
确定要反激活吗?此操作会清除当前 License。
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleDeactivate}
|
||||||
|
disabled={deactivateMutation.isPending}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 disabled:bg-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
{deactivateMutation.isPending
|
||||||
|
? '反激活中...'
|
||||||
|
: '确认反激活'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowDeactivateConfirm(false)}
|
||||||
|
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-md text-sm font-medium hover:bg-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{deactivateMutation.isError && (
|
||||||
|
<p className="text-red-500 mt-2 text-xs">
|
||||||
|
反激活失败,请重试
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
|||||||
@@ -12,3 +12,7 @@ export const getActivation = oc.input(z.void()).output(
|
|||||||
export const activate = oc
|
export const activate = oc
|
||||||
.input(z.object({ license: z.string().min(1) }))
|
.input(z.object({ license: z.string().min(1) }))
|
||||||
.output(z.object({ success: z.boolean() }))
|
.output(z.object({ success: z.boolean() }))
|
||||||
|
|
||||||
|
export const deactivate = oc
|
||||||
|
.input(z.void())
|
||||||
|
.output(z.object({ success: z.boolean() }))
|
||||||
|
|||||||
@@ -43,3 +43,25 @@ export const activate = os.license.activate
|
|||||||
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const deactivate = os.license.deactivate
|
||||||
|
.use(dbProvider)
|
||||||
|
.handler(async ({ context }) => {
|
||||||
|
await ensureLicenseActivationInitialized()
|
||||||
|
|
||||||
|
const record = await context.db.query.licenseActivationTable.findFirst()
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new Error('License activation record not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.db
|
||||||
|
.update(licenseActivationTable)
|
||||||
|
.set({
|
||||||
|
license: null,
|
||||||
|
licenseActivatedAt: null,
|
||||||
|
})
|
||||||
|
.where(eq(licenseActivationTable.id, record.id))
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user