Skip to content

Commit

Permalink
feat: support copy/delete actions on gift code management
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Mar 10, 2024
1 parent d4015c3 commit 6d92ff7
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 52 deletions.
22 changes: 22 additions & 0 deletions admin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ type GenerateInvitationForm struct {
Number int `json:"number"`
}

type DeleteInvitationForm struct {
Code string `json:"code"`
}

type GenerateRedeemForm struct {
Quota float32 `json:"quota"`
Number int `json:"number"`
Expand Down Expand Up @@ -139,6 +143,24 @@ func InvitationPaginationAPI(c *gin.Context) {
c.JSON(http.StatusOK, GetInvitationPagination(db, int64(page)))
}

func DeleteInvitationAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)

var form DeleteInvitationForm
if err := c.ShouldBindJSON(&form); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": false,
"error": err.Error(),
})
return
}

err := DeleteInvitationCode(db, form.Code)
c.JSON(http.StatusOK, gin.H{
"status": err == nil,
"error": err,
})
}
func GenerateInvitationAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)

Expand Down
7 changes: 7 additions & 0 deletions admin/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {
}
}

func DeleteInvitationCode(db *sql.DB, code string) error {
_, err := globals.ExecDb(db, `
DELETE FROM invitation WHERE code = ?
`, code)
return err
}

func NewInvitationCode(db *sql.DB, code string, quota float32, t string) error {
_, err := globals.ExecDb(db, `
INSERT INTO invitation (code, quota, type)
Expand Down
1 change: 1 addition & 0 deletions admin/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Register(app *gin.RouterGroup) {

app.GET("/admin/invitation/list", InvitationPaginationAPI)
app.POST("/admin/invitation/generate", GenerateInvitationAPI)
app.POST("/admin/invitation/delete", DeleteInvitationAPI)

app.GET("/admin/redeem/list", RedeemListAPI)
app.POST("/admin/redeem/generate", GenerateRedeemAPI)
Expand Down
9 changes: 9 additions & 0 deletions app/src/admin/api/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ export async function getInvitationList(
}
}

export async function deleteInvitation(code: string): Promise<CommonResponse> {
try {
const response = await axios.post("/admin/invitation/delete", { code });
return response.data as CommonResponse;
} catch (e) {
return { status: false, message: getErrorMessage(e) };
}
}

export async function generateInvitation(
type: string,
quota: number,
Expand Down
18 changes: 15 additions & 3 deletions app/src/components/OperationAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import {
PopoverTrigger,
} from "@/components/ui/popover.tsx";
import { Button } from "@/components/ui/button.tsx";
import { cn } from "@/components/ui/lib/utils.ts";

type ActionProps = {
tooltip?: string;
children: React.ReactNode;
onClick?: () => any;
native?: boolean;
variant?:
| "secondary"
| "default"
Expand All @@ -26,15 +28,25 @@ type ActionProps = {
| null
| undefined;
};
function OperationAction({ tooltip, children, onClick, variant }: ActionProps) {
function OperationAction({
tooltip,
children,
onClick,
variant,
native,
}: ActionProps) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
{variant === "destructive" ? (
<Popover>
<PopoverTrigger asChild>
<Button size={`icon`} className={`w-8 h-8`} variant={variant}>
<Button
size={`icon`}
className={cn(!native && `w-8 h-8`)}
variant={variant}
>
{children}
</Button>
</PopoverTrigger>
Expand All @@ -52,7 +64,7 @@ function OperationAction({ tooltip, children, onClick, variant }: ActionProps) {
) : (
<Button
size={`icon`}
className={`w-8 h-8`}
className={cn(!native && `w-8 h-8`)}
onClick={onClick}
variant={variant}
>
Expand Down
52 changes: 43 additions & 9 deletions app/src/components/admin/InvitationTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@ import {
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
import { Button } from "@/components/ui/button.tsx";
import { Download, Loader2, RotateCw } from "lucide-react";
import { Button, TemporaryButton } from "@/components/ui/button.tsx";
import { Copy, Download, Loader2, RotateCw, Trash } from "lucide-react";
import { useEffectAsync } from "@/utils/hook.ts";
import { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
import {
deleteInvitation,
generateInvitation,
getInvitationList,
} from "@/admin/api/chart.ts";
import { Input } from "@/components/ui/input.tsx";
import { useToast } from "@/components/ui/use-toast.ts";
import { Textarea } from "@/components/ui/textarea.tsx";
import { saveAsFile } from "@/utils/dom.ts";
import { copyClipboard, saveAsFile } from "@/utils/dom.ts";
import { PaginationAction } from "@/components/ui/pagination.tsx";
import { Badge } from "@/components/ui/badge.tsx";
import OperationAction from "@/components/OperationAction.tsx";
import { toastState } from "@/api/common.ts";

function GenerateDialog() {
function GenerateDialog({ update }: { update: () => void }) {
const { t } = useTranslation();
const { toast } = useToast();
const [open, setOpen] = useState<boolean>(false);
Expand All @@ -43,8 +50,10 @@ function GenerateDialog() {

async function generateCode() {
const data = await generateInvitation(type, Number(quota), Number(number));
if (data.status) setData(data.data.join("\n"));
else
if (data.status) {
setData(data.data.join("\n"));
update();
} else
toast({
title: t("admin.error"),
description: data.message,
Expand Down Expand Up @@ -167,16 +176,41 @@ function InvitationTable() {
<TableHead>{t("admin.type")}</TableHead>
<TableHead>{t("admin.used")}</TableHead>
<TableHead>{t("admin.updated-at")}</TableHead>
<TableHead>{t("admin.action")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(data.data || []).map((invitation, idx) => (
<TableRow key={idx} className={`whitespace-nowrap`}>
<TableCell>{invitation.code}</TableCell>
<TableCell>{invitation.quota}</TableCell>
<TableCell>{invitation.type}</TableCell>
<TableCell>
<Badge>{invitation.type}</Badge>
</TableCell>
<TableCell>{t(`admin.used-${invitation.used}`)}</TableCell>
<TableCell>{invitation.updated_at}</TableCell>
<TableCell className={`flex gap-2`}>
<TemporaryButton
size={`icon`}
variant={`outline`}
onClick={() => copyClipboard(invitation.code)}
>
<Copy className={`h-4 w-4`} />
</TemporaryButton>
<OperationAction
native
tooltip={t("delete")}
variant={`destructive`}
onClick={async () => {
const resp = await deleteInvitation(invitation.code);
toastState(toast, t, resp, true);

resp.status && (await update());
}}
>
<Trash className={`h-4 w-4`} />
</OperationAction>
</TableCell>
</TableRow>
))}
</TableBody>
Expand All @@ -202,7 +236,7 @@ function InvitationTable() {
<Button variant={`outline`} size={`icon`} onClick={update}>
<RotateCw className={`h-4 w-4`} />
</Button>
<GenerateDialog />
<GenerateDialog update={update} />
</div>
</div>
);
Expand Down
27 changes: 25 additions & 2 deletions app/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "./lib/utils";
import { useEffect, useMemo, useState } from "react";
import { Loader2 } from "lucide-react";
import { Check, Loader2 } from "lucide-react";
import { useTemporaryState } from "@/utils/hook.ts";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
Expand Down Expand Up @@ -119,4 +120,26 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
Button.displayName = "Button";

export { Button, buttonVariants };
type TemporaryButtonProps = ButtonProps & {
interval?: number;
};

const TemporaryButton = React.forwardRef<
HTMLButtonElement,
TemporaryButtonProps
>(({ interval, children, onClick, ...props }, ref) => {
const { state, triggerState } = useTemporaryState(interval);

const event = (e: React.MouseEvent<HTMLButtonElement>) => {
if (onClick) onClick(e);
triggerState();
};

return (
<Button ref={ref} onClick={event} {...props}>
{state ? <Check className={`h-4 w-4`} /> : children}
</Button>
);
});

export { Button, TemporaryButton, buttonVariants };
2 changes: 1 addition & 1 deletion app/src/resources/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@
"operate-success-prompt": "您的操作已成功执行。",
"operate-failed": "操作失败",
"operate-failed-prompt": "操作失败,原因:{{reason}}",
"updated-at": "领取时间",
"updated-at": "更新时间",
"used-true": "已使用",
"used-false": "未使用",
"generate": "批量生成",
Expand Down
2 changes: 1 addition & 1 deletion app/src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@
"operate-success-prompt": "Your operation has been successfully executed.",
"operate-failed": "Operate Failed",
"operate-failed-prompt": "Operation failed, reason: {{reason}}",
"updated-at": "Updated At",
"updated-at": "Updated on ",
"used-true": "Used",
"used-false": "Unused",
"generate": "Generate",
Expand Down
2 changes: 1 addition & 1 deletion app/src/resources/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@
"operate-success-prompt": "アクションは正常に実行されました。",
"operate-failed": "操作に失敗しました",
"operate-failed-prompt": "{{reason}}の操作が失敗しました",
"updated-at": "乗車時間",
"updated-at": "アップデート時間",
"used-true": "使用済み",
"used-false": "活用していない",
"generate": "バッチ生成",
Expand Down
2 changes: 1 addition & 1 deletion app/src/resources/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@
"operate-success-prompt": "Ваша операция была успешно выполнена.",
"operate-failed": "Не удалось",
"operate-failed-prompt": "Не удалось выполнить операцию, причина: {{reason}}",
"updated-at": "Обновлено",
"updated-at": "Время обновления",
"used-true": "Использовано",
"used-false": "Не использовано",
"generate": "Генерировать",
Expand Down
66 changes: 32 additions & 34 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
worker_processes 1;
server
{
# this is a sample configuration for nginx
listen 80;

events {
worker_connections 8192;
multi_accept on;
use epoll;
}

http {
server {
listen 8000 default_server;
listen [::]:8000 default_server;
server_name _;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md|package.json|package-lock.json|\.env) {
return 404;
}

root /app/dist;
index index.html;
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}

location /api/ {
proxy_pass http://127.0.0.1:8094/;
proxy_set_header Host 127.0.0.1:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Host $host:$server_port;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 86400s;
proxy_send_timeout 30s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ /purge(/.*) {
proxy_cache_purge cache_one 127.0.0.1$request_uri$is_args$args;
}

location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
error_page 404 =200 /index.html;
}
location / {
# if you are using compile deployment mode, please use the http://localhost:8094 instead
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host 127.0.0.1:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
add_header X-Cache $upstream_cache_status;
proxy_set_header X-Host $host:$server_port;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 86400s;
proxy_send_timeout 30s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

access_log /www/wwwlogs/chatnio.log;
error_log /www/wwwlogs/chatnio.error.log;
}

0 comments on commit 6d92ff7

Please sign in to comment.