Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2602,6 +2602,11 @@ exports[`public API spec — operation naming guard > operation list (operationI
"operationId": "minigames.deleteMany",
"path": "/v1/minigames/bulk-delete",
},
{
"method": "POST",
"operationId": "minigames.duplicate",
"path": "/v1/minigames/{id}/duplicate",
},
{
"method": "GET",
"operationId": "minigames.get",
Expand Down
1 change: 1 addition & 0 deletions apps/builder/__tests__/api-parity-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"mini-apps/update-mini-app.action:updateMiniAppAction": "covered:mini-apps",
"minigames/create-minigame.action:createMinigameAction": "covered:minigames",
"minigames/delete-minigames.action:deleteMinigamesAction": "covered:minigames",
"minigames/duplicate-minigame.action:duplicateMinigameAction": "covered:minigames",
"minigames/enable-minigame.action:enableMinigameAction": "covered:minigames",
"minigames/find-contact-conversation.action:findContactConversationAction": "covered:minigames",
"minigames/get-minigame-plays.action:getMinigamePlaysAction": "covered:minigames",
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "بدون جائزة",
"empty": "لا توجد سجلات لعب بعد"
}
},
"duplicateDialog": {
"title": "تكرار اللعبة المصغرة",
"description": "سيتم إنشاء نسخة من اللعبة المصغرة بجميع إعداداتها. لا يتم نسخ الجوائز — أضفها إلى النسخة بعد ذلك.",
"success": "تم تكرار اللعبة المصغرة بنجاح"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/az.json
Original file line number Diff line number Diff line change
Expand Up @@ -9426,6 +9426,11 @@
"lose": "Mükafat yoxdur",
"empty": "Hələ oyun qeydi yoxdur"
}
},
"duplicateDialog": {
"title": "Mini oyunu dublikat et",
"description": "Bütün ayarları ilə mini oyunun surəti yaradılacaq. Mükafatlar köçürülmür — onları sonra surətə əlavə edin.",
"success": "Mini oyun uğurla dublikat edildi"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Ingen præmie",
"empty": "Ingen spiloptegnelser endnu"
}
},
"duplicateDialog": {
"title": "Dupliker minispil",
"description": "Dette opretter en kopi af minispillet med alle dets indstillinger. Præmier kopieres ikke — tilføj dem til kopien bagefter.",
"success": "Minispillet blev duplikeret"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Kein Gewinn",
"empty": "Noch keine Spielprotokolle"
}
},
"duplicateDialog": {
"title": "Minispiel duplizieren",
"description": "Dadurch wird eine Kopie des Minispiels mit allen Einstellungen erstellt. Preise werden nicht kopiert – füge sie anschließend zur Kopie hinzu.",
"success": "Minispiel erfolgreich dupliziert"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9426,6 +9426,11 @@
"lose": "No prize",
"empty": "No play records yet"
}
},
"duplicateDialog": {
"title": "Duplicate minigame",
"description": "This creates a copy of the minigame with all its settings. Prizes are not copied — add them to the copy afterwards.",
"success": "Minigame duplicated successfully"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Sin premio",
"empty": "Aún no hay registros de jugadas"
}
},
"duplicateDialog": {
"title": "Duplicar minijuego",
"description": "Se creará una copia del minijuego con toda su configuración. Los premios no se copian: añádelos a la copia después.",
"success": "Minijuego duplicado correctamente"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Ei palkintoa",
"empty": "Ei pelitapahtumia vielä"
}
},
"duplicateDialog": {
"title": "Monista minipeli",
"description": "Tämä luo minipelistä kopion kaikkine asetuksineen. Palkintoja ei kopioida – lisää ne kopioon jälkikäteen.",
"success": "Minipeli monistettu"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Pas de lot",
"empty": "Aucun registre de parties pour le moment"
}
},
"duplicateDialog": {
"title": "Dupliquer le mini-jeu",
"description": "Une copie du mini-jeu sera créée avec tous ses paramètres. Les lots ne sont pas copiés : ajoutez-les ensuite à la copie.",
"success": "Mini-jeu dupliqué avec succès"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "ללא פרס",
"empty": "אין עדיין רשומות משחק"
}
},
"duplicateDialog": {
"title": "שכפול מיני-משחק",
"description": "תיווצר העתקה של המיני-משחק עם כל ההגדרות שלו. הפרסים לא מועתקים — הוסיפו אותם להעתק לאחר מכן.",
"success": "המיני-משחק שוכפל בהצלחה"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Tidak menang",
"empty": "Belum ada catatan permainan"
}
},
"duplicateDialog": {
"title": "Duplikat minigame",
"description": "Ini akan membuat salinan minigame dengan semua pengaturannya. Hadiah tidak disalin — tambahkan ke salinan setelahnya.",
"success": "Minigame berhasil diduplikat"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Nessun premio",
"empty": "Ancora nessun registro di partite"
}
},
"duplicateDialog": {
"title": "Duplica minigioco",
"description": "Verrà creata una copia del minigioco con tutte le impostazioni. I premi non vengono copiati: aggiungili alla copia in seguito.",
"success": "Minigioco duplicato correttamente"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "ハズレ",
"empty": "プレイ記録はまだありません"
}
},
"duplicateDialog": {
"title": "ミニゲームを複製",
"description": "すべての設定を含むミニゲームのコピーを作成します。景品はコピーされません。後でコピーに追加してください。",
"success": "ミニゲームを複製しました"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Geen prijs",
"empty": "Nog geen speelrecords"
}
},
"duplicateDialog": {
"title": "Minigame dupliceren",
"description": "Hiermee maak je een kopie van de minigame met alle instellingen. Prijzen worden niet gekopieerd — voeg ze daarna toe aan de kopie.",
"success": "Minigame succesvol gedupliceerd"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Sem prêmio",
"empty": "Ainda não há registros de jogadas"
}
},
"duplicateDialog": {
"title": "Duplicar minigame",
"description": "Isso cria uma cópia do minigame com todas as configurações. Os prêmios não são copiados — adicione-os à cópia depois.",
"success": "Minigame duplicado com sucesso"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/pt-PT.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Sem prémio",
"empty": "Ainda não há registos de jogadas"
}
},
"duplicateDialog": {
"title": "Duplicar minijogo",
"description": "Isto cria uma cópia do minijogo com todas as definições. Os prémios não são copiados — adicione-os à cópia depois.",
"success": "Minijogo duplicado com sucesso"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/ro.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Fără premiu",
"empty": "Nu există încă înregistrări de joc"
}
},
"duplicateDialog": {
"title": "Duplică minijocul",
"description": "Se va crea o copie a minijocului cu toate setările. Premiile nu sunt copiate — adaugă-le ulterior în copie.",
"success": "Minijocul a fost duplicat cu succes"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Ingen vinst",
"empty": "Inga spelloggar ännu"
}
},
"duplicateDialog": {
"title": "Duplicera minispel",
"description": "Detta skapar en kopia av minispelet med alla inställningar. Priser kopieras inte – lägg till dem i kopian efteråt.",
"success": "Minispelet har duplicerats"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "Ödül yok",
"empty": "Henüz oyun kaydı yok"
}
},
"duplicateDialog": {
"title": "Mini oyunu çoğalt",
"description": "Tüm ayarlarıyla mini oyunun bir kopyası oluşturulur. Ödüller kopyalanmaz — bunları daha sonra kopyaya ekleyin.",
"success": "Mini oyun başarıyla çoğaltıldı"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9426,6 +9426,11 @@
"lose": "Không trúng",
"empty": "Chưa có bản ghi lượt chơi nào"
}
},
"duplicateDialog": {
"title": "Nhân bản minigame",
"description": "Tạo một bản sao của minigame với toàn bộ cài đặt. Giải thưởng không được sao chép — hãy thêm giải thưởng cho bản sao sau đó.",
"success": "Đã nhân bản minigame thành công"
}
},
"mediaLibrary": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -9360,6 +9360,11 @@
"lose": "未中奖",
"empty": "暂无游戏记录"
}
},
"duplicateDialog": {
"title": "复制小游戏",
"description": "将创建包含全部设置的小游戏副本。奖品不会被复制,请之后在副本中添加。",
"success": "小游戏已复制"
}
},
"userDataWebview": {
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -9384,6 +9384,11 @@
"lose": "未中獎",
"empty": "尚無遊戲紀錄"
}
},
"duplicateDialog": {
"title": "複製小遊戲",
"description": "將建立包含所有設定的小遊戲副本。獎品不會被複製,請之後在副本中新增。",
"success": "小遊戲已複製"
}
},
"mediaLibrary": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use server"

import { minigameService } from "@chatbotx.io/business/minigame"
import { workspaceIdAndIdRequestParams } from "@/features/common/schema"
import { workspaceActionClient } from "@/lib/safe-action"

export const duplicateMinigameAction = workspaceActionClient
.bindArgsSchemas(workspaceIdAndIdRequestParams)
.action(async ({ bindArgsParsedInputs: [workspaceId, id] }) => {
const minigame = await minigameService.duplicate({ workspaceId, id })
return { id: minigame.id }
})
28 changes: 28 additions & 0 deletions apps/builder/src/features/minigames/api/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,34 @@ export const minigamesPublicRouter = {
)
}),

duplicate: workspaceTokenAuthAPI
.route({
method: "POST",
path: "/v1/minigames/{id}/duplicate",
summary: "Duplicate minigame",
description:
"Copies a minigame's settings into a new minigame named `<name> - Copy` (`- Copy 2`, … when taken). Prizes are not copied: the copy starts with no prizes and `nonWinning.loseRate` 100, so add prizes with `minigames.updatePartial`. Plays, players and counters start from zero. Find the id with `minigames.list`.",
successStatus: 201,
tags,
})
.input(
z.object({
id: zodBigintAsString().describe(
"Minigame id to copy. Get it from `minigames.list`.",
),
}),
)
.output(minigamePublicResource)
.errors(possibleErrorsOnMutatingMinigame)
.handler(async ({ context, input }) =>
withPlayUrl(
await minigameService.duplicate({
workspaceId: context.workspace.id,
id: input.id,
}),
),
),

delete: workspaceTokenAuthAPI
.route({
method: "DELETE",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client"

import { Button } from "@chatbotx.io/ui/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@chatbotx.io/ui/components/ui/dialog"
import { CopyPlusIcon, Loader2Icon } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTranslations } from "next-intl"
import { useAction } from "next-safe-action/hooks"
import { toast } from "sonner"
import { duplicateMinigameAction } from "../actions/duplicate-minigame.action"

type DuplicateMinigameDialogProps = {
workspaceId: string
minigameId: string | null
open: boolean
onOpenChange: (open: boolean) => void
}

export function DuplicateMinigameDialog({
workspaceId,
minigameId,
open,
onOpenChange,
}: DuplicateMinigameDialogProps) {
const t = useTranslations()
const router = useRouter()
const { execute, isPending } = useAction(
duplicateMinigameAction.bind(null, workspaceId, minigameId ?? ""),
{
onSuccess: ({ data }) => {
toast.success(t("minigames.duplicateDialog.success"))
onOpenChange(false)
if (data?.id) {
router.push(`/space/${workspaceId}/minigames/${data.id}/edit`)
}
},
onError: ({ error }) => {
if (error.serverError) {
toast.error(error.serverError)
}
},
},
)

return (
<Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t("minigames.duplicateDialog.title")}</DialogTitle>
<DialogDescription>
{t("minigames.duplicateDialog.description")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => onOpenChange(false)} variant="ghost">
{t("actions.cancel")}
</Button>
<Button disabled={isPending || !minigameId} onClick={() => execute()}>
{isPending ? (
<Loader2Icon className="size-4 animate-spin" />
) : (
<CopyPlusIcon className="size-4" />
)}
{t("actions.duplicate")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Loading
Loading