Commit 383ab0f1 authored by 健杭 徐's avatar 健杭 徐
Browse files

已删除 myproject/chatroom/Dockerfile, myproject/chatroom/docker-compose.yml,...

已删除 myproject/chatroom/Dockerfile, myproject/chatroom/docker-compose.yml, myproject/chatroom/go.mod, myproject/chatroom/go.sum, myproject/chatroom/main.go, myproject/my-app/src/app/ChatRoom/ChatRoom.module.css, myproject/my-app/src/app/ChatRoom/page.tsx, myproject/my-app/src/app/Register/Register.module.css, myproject/my-app/src/app/Register/page.tsx, myproject/my-app/src/app/SetName/SetName.module.css, myproject/my-app/src/app/SetName/page.tsx, myproject/my-app/src/app/public/1 (3).jpg, myproject/my-app/src/app/public/20210920000906_53764.png, myproject/my-app/src/app/public/SetbackGround.jpg, myproject/my-app/src/app/public/backGround.jpg, myproject/my-app/src/app/favicon.ico, myproject/my-app/src/app/globals.css, myproject/my-app/src/app/layout.tsx, myproject/my-app/src/app/page.module.css, myproject/my-app/src/app/page.tsx, myproject/my-app/.gitignore, myproject/my-app/Dockerfile, myproject/my-app/README.md, myproject/my-app/next.config.ts, myproject/my-app/package-lock.json, myproject/my-app/package.json, myproject/my-app/tsconfig.json, myproject/README.md, myproject/package-lock.json
parent 78c35070
# README
### 1.go返回数组时格式问题
### 2.添加了头像
### 3.使用了`react-icons/md`
## unfinished
### 1.deleted function
FROM golang:1.24.3-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/main .
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["/app/main"]
version: '3.8'
services:
backend:
build: .
ports:
- "8080:8080"
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=chat_room_user
- DB_PASSWORD=secure_password
- DB_NAME=chat_room_db
depends_on:
- db
db:
image: postgres:13-alpine
environment:
- POSTGRES_USER=chat_room_user
- POSTGRES_PASSWORD=secure_password
- POSTGRES_DB=chat_room_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
module chatroom
go 1.24.3
require (
github.com/gin-gonic/gin v1.10.1
github.com/lib/pq v1.10.9
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"strconv"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
// Helper to get env var or default
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
// Helper to get env var as int or default
func getEnvAsInt(name string, defaultVal int) int {
valueStr := getEnv(name, "")
if value, err := strconv.Atoi(valueStr); err == nil {
return value
}
return defaultVal
}
type Message struct {
MessageId int `json:"message_id"`
RoomId int `json:"room_id"`
Profile int `json:"profile"`
Sender string `json:"sender"`
Content string `json:"content"`
Time string `json:"time"`
}
type RoomPreviewInfo struct {
RoomId int `json:"roomId"`
RoomName string `json:"roomName"`
LastSender sql.NullString `json:"lastSender"`
LastContent sql.NullString `json:"lastContent"`
LastTime sql.NullTime `json:"lastTime"`
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type RoomAddRes struct {
RoomId int `json:"room_id"`
}
var db *sql.DB
func main() {
host := getEnv("DB_HOST", "localhost")
port := getEnvAsInt("DB_PORT", 5432)
user := getEnv("DB_USER", "chat_room_user")
password := getEnv("DB_PASSWORD", "secure_password")
dbname := getEnv("DB_NAME", "chat_room_db")
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
var err error
db, err = sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Ping()
if err != nil {
log.Fatal(err)
}
createTable()
var tableExists bool
err = db.QueryRow(`
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_name = 'rooms'
)
`).Scan(&tableExists)
if err != nil {
log.Fatal("Table check failed: ", err)
}
if !tableExists {
log.Println("Table 'rooms' not found, creating...")
createTable()
} else {
log.Println("Table 'rooms' already exists")
}
router := gin.Default()
router.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
router.POST("/register", Register)
router.POST("/login", Login)
router.POST("/room/add", AddNewRoom)
router.GET("/room/list", GetRoomList)
router.POST("/room/delete", DeleteRoom)
router.POST("/room/message/add", AddMessage)
router.GET("/room/message/list", GetMessageList)
router.PUT("/room/message/update", RoomMessageUpdate)
router.PUT("/room/rename", RoomRename)
router.Run(":8080")
}
func Register(c *gin.Context) {
userName := c.Query("userName")
password := c.Query("password")
var roomId int
err := db.QueryRow(
"INSERT INTO users (username, password) VALUES ($1, $2) RETURNING user_id",
userName, password,
).Scan(&roomId)
if err != nil {
c.JSON(500, Response{Code: 500, Msg: "注册失败", Data: nil})
return
}
log.Printf("New user registered: %s with ID %d", userName, roomId)
c.JSON(200, Response{Code: 0, Msg: "注册成功", Data: nil})
}
func Login(c *gin.Context) {
userName := c.Query("userName")
password := c.Query("password")
var userId int
err := db.QueryRow(
"SELECT user_id FROM users WHERE username = $1 AND password = $2",
userName, password,
).Scan(&userId)
if err != nil {
c.JSON(401, Response{Code: 401, Msg: "账号或密码错误", Data: nil})
return
}
log.Printf("User logged in: %s with ID %d", userName, userId)
c.JSON(200, Response{Code: 0, Msg: "登录成功", Data: nil})
}
func AddNewRoom(c *gin.Context) {
var room RoomPreviewInfo
if err := c.ShouldBindJSON(&room); err != nil {
c.JSON(400, Response{Code: 400, Msg: "Invalid input", Data: nil})
return
}
var roomId int
err := db.QueryRow(
"INSERT INTO rooms (room_name) VALUES ($1) RETURNING room_id",
room.RoomName,
).Scan(&roomId)
if err != nil {
c.JSON(500, Response{Code: 500, Msg: "Failed to add room", Data: nil})
return
}
room.RoomId = roomId
c.JSON(200, RoomAddRes{
RoomId: room.RoomId,
})
log.Printf("New room added: %+v", room)
}
func GetRoomList(c *gin.Context) {
var total int
roomCountQuery := "SELECT COUNT(*) FROM rooms"
err := db.QueryRow(roomCountQuery).Scan(&total)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "总数统计失败:" + err.Error(),
})
return
}
query := `
SELECT room_id, room_name, last_sender, last_content, last_time
FROM rooms
ORDER BY last_time DESC NULLS LAST
`
args := []interface{}{}
rows, err := db.Query(query, args...)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Falie while serch:" + err.Error(),
})
return
}
defer rows.Close()
var roomList []RoomPreviewInfo
for rows.Next() {
var roomItem RoomPreviewInfo
if err := rows.Scan(
&roomItem.RoomId,
&roomItem.RoomName,
&roomItem.LastSender,
&roomItem.LastContent,
&roomItem.LastTime,
); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Faile while parsing" + err.Error(),
})
return
}
roomList = append(roomList, roomItem)
}
c.JSON(http.StatusOK, Response{
Code: 0,
Msg: "Success",
Data: roomList,
})
}
func DeleteRoom(c *gin.Context) {
roomId := c.Query("roomId")
if roomId == "" {
c.JSON(400, Response{Code: 400, Msg: "Room ID is required", Data: nil})
return
}
_, err := db.Exec("DELETE FROM messages WHERE room_id = $1", roomId)
if err != nil {
c.JSON(500, Response{Code: 500, Msg: "Failed to delete messages in room: " + err.Error(), Data: nil})
return
}
_, err_ := db.Exec("DELETE FROM rooms WHERE room_id = $1", roomId)
if err_ != nil {
c.JSON(500, Response{Code: 500, Msg: "Failed to delete room: " + err_.Error(), Data: nil})
return
}
c.JSON(200, Response{
Code: 0,
Msg: "Room deleted successfully",
Data: nil,
})
}
func AddMessage(c *gin.Context) {
var message struct {
RoomId int `json:"roomId"`
ProfileId int `json:"profile"`
Sender string `json:"sender"`
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&message); err != nil {
c.JSON(http.StatusBadRequest, Response{Code: 400, Msg: "Invalid input: " + err.Error(), Data: nil})
return
}
// 写入评论
query := `
INSERT INTO messages (room_id, profile, sender, content, "time")
VALUES ($1, $2, $3, $4, NOW())
RETURNING message_id
`
var messageId int
err := db.QueryRow(query, message.RoomId, message.ProfileId, message.Sender, message.Content).Scan(&messageId)
if err != nil {
c.JSON(http.StatusInternalServerError, Response{Code: 500, Msg: "Failed to add message: " + err.Error(), Data: nil})
return
}
// 更新room
_, err = db.Exec(
"UPDATE rooms SET last_sender = $1, last_content = $2, last_time = NOW() WHERE room_id = $3",
message.Sender, message.Content, message.RoomId,
)
if err != nil {
log.Printf("Failed to update room preview: %v", err)
c.JSON(http.StatusInternalServerError, Response{Code: 500, Msg: "Failed to updata lastest message: " + err.Error(), Data: nil})
return
}
c.JSON(http.StatusOK, Response{
Code: 0,
Msg: "Message added successfully",
Data: gin.H{"messageId": messageId},
})
log.Printf("New message with ID %d added to room %d", messageId, message.RoomId)
}
func GetMessageList(c *gin.Context) {
roomId := c.Query("roomId")
if roomId == "" {
c.JSON(400, Response{Code: 400, Msg: "Room ID is required", Data: nil})
return
}
rows, err := db.Query("SELECT profile, sender, content, time FROM messages WHERE room_id = $1 ORDER BY time ASC", roomId)
if err != nil {
c.JSON(500, Response{Code: 500, Msg: "Failed to retrieve messages", Data: nil})
return
}
defer rows.Close()
var messages []Message
for rows.Next() {
var msg Message
if err := rows.Scan(&msg.Profile, &msg.Sender, &msg.Content, &msg.Time); err != nil {
c.JSON(500, Response{Code: 500, Msg: "Failed to scan message", Data: nil})
return
}
messages = append(messages, msg)
}
c.JSON(200, Response{
Code: 0,
Msg: "Messages retrieved successfully",
Data: messages,
})
}
func RoomRename(c *gin.Context) {
roomId, err := strconv.Atoi(c.Query("roomId"))
if err != nil || roomId <= 0 {
c.JSON(http.StatusOK, Response{Code: 400, Msg: "Invalid room ID"})
return
}
var newName struct {
RoomName string `json:"roomName"`
}
if err := c.ShouldBindJSON(&newName); err != nil {
c.JSON(http.StatusBadRequest, Response{Code: 400, Msg: "Invalid input: " + err.Error(), Data: nil})
return
}
_, err = db.Exec("UPDATE rooms SET room_name = $1 WHERE room_id = $2", newName.RoomName, roomId)
if err != nil {
c.JSON(http.StatusInternalServerError, Response{Code: 500, Msg: "Failed to rename room: " + err.Error(), Data: nil})
return
}
c.JSON(http.StatusOK, Response{
Code: 0,
Msg: "Room renamed successfully",
Data: nil,
})
}
func RoomMessageUpdate(c *gin.Context) {
// 处理更新房间消息的逻辑
}
func createTable() {
query := `
CREATE TABLE IF NOT EXISTS rooms (
room_id SERIAL PRIMARY KEY,
room_name VARCHAR(100) NOT NULL UNIQUE,
last_sender VARCHAR(100),
last_content TEXT,
last_time TIMESTAMP
);
CREATE TABLE IF NOT EXISTS messages (
message_id SERIAL PRIMARY KEY,
room_id INT NOT NULL,
profile INT NOT NULL,
sender VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
"time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(room_id)
);
CREATE TABLE IF NOT EXISTS users (
user_id SERIAL PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL
);
`
_, err := db.Exec(query)
if err != nil {
log.Fatal("Failed to create tables: ", err)
}
log.Println("Tables created successfully")
}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;
{
"name": "my-app",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "my-app",
"version": "0.1.0",
"dependencies": {
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz",
"integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.1.0"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz",
"integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.1.0"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz",
"integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.1.0"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz",
"integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.1.0"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz",
"integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.1.0"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz",
"integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.1.0"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz",
"integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz",
"integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.1.0"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz",
"integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.4.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz",
"integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz",
"integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz",
"integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@next/env": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz",
"integrity": "sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.3.tgz",
"integrity": "sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.3.tgz",
"integrity": "sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.3.tgz",
"integrity": "sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.3.tgz",
"integrity": "sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz",
"integrity": "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz",
"integrity": "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.3.tgz",
"integrity": "sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.3.tgz",
"integrity": "sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@types/node": {
"version": "20.19.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz",
"integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/react": {
"version": "19.1.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001722",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz",
"integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT",
"optional": true
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT",
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/next": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz",
"integrity": "sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==",
"license": "MIT",
"dependencies": {
"@next/env": "15.3.3",
"@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"styled-jsx": "5.1.6"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "15.3.3",
"@next/swc-darwin-x64": "15.3.3",
"@next/swc-linux-arm64-gnu": "15.3.3",
"@next/swc-linux-arm64-musl": "15.3.3",
"@next/swc-linux-x64-gnu": "15.3.3",
"@next/swc-linux-x64-musl": "15.3.3",
"@next/swc-win32-arm64-msvc": "15.3.3",
"@next/swc-win32-x64-msvc": "15.3.3",
"sharp": "^0.34.1"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2",
"babel-plugin-react-compiler": "*",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
},
"@playwright/test": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
},
"sass": {
"optional": true
}
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.0"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz",
"integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.4",
"semver": "^7.7.2"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.2",
"@img/sharp-darwin-x64": "0.34.2",
"@img/sharp-libvips-darwin-arm64": "1.1.0",
"@img/sharp-libvips-darwin-x64": "1.1.0",
"@img/sharp-libvips-linux-arm": "1.1.0",
"@img/sharp-libvips-linux-arm64": "1.1.0",
"@img/sharp-libvips-linux-ppc64": "1.1.0",
"@img/sharp-libvips-linux-s390x": "1.1.0",
"@img/sharp-libvips-linux-x64": "1.1.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
"@img/sharp-libvips-linuxmusl-x64": "1.1.0",
"@img/sharp-linux-arm": "0.34.2",
"@img/sharp-linux-arm64": "0.34.2",
"@img/sharp-linux-s390x": "0.34.2",
"@img/sharp-linux-x64": "0.34.2",
"@img/sharp-linuxmusl-arm64": "0.34.2",
"@img/sharp-linuxmusl-x64": "0.34.2",
"@img/sharp-wasm32": "0.34.2",
"@img/sharp-win32-arm64": "0.34.2",
"@img/sharp-win32-ia32": "0.34.2",
"@img/sharp-win32-x64": "0.34.2"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT",
"optional": true,
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/styled-jsx": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
"license": "MIT",
"dependencies": {
"client-only": "0.0.1"
},
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5"
}
}
.chat-room {
display: flex;
position: fixed;
width: 100vw;
height: 100vh;
box-sizing: border-box;
overflow: hidden;
}
.chat-room-nav {
position: fixed;
padding: 10px;
width: 15%;
height: 100%;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
z-index: 10;
}
.open {
display: flex;
position: fixed;
width: 100vw;
height: 100vh;
box-sizing: border-box;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.75);
z-index: 0;
}
.open .roomName-input {
background-color: #fff;
display: flex;
flex-direction: column;
position: fixed;
top: 40%;
left: 30%;
z-index: 0;
width: 40%;
height: 25%;
border-radius: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
h3 {
align-items: center;
text-align: center;
}
input {
width: 80%;
height: 20px;
margin: 20px auto;
padding: 10px;
border-radius: 20px;
border: 1px solid #ccc;
font-size: 16px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: -5px;
button {
width: 100px;
height: 40px;
background-color: #007bff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
margin-right: 10px;
&:hover {
background-color: #0056b3;
}
}
}
}
.chat-room-nav .chat-list {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
z-index: 1;
.chat-item {
display: flex;
position: relative;
align-items: center;
padding: 10px;
margin-bottom: 10px;
border-radius: 8px;
cursor: pointer;
border: 1px solid #ddd;
&:hover {
background-color: #e0e0e0;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.chat-info {
height: 40px;
flex-grow: 1;
right: 0;
width: 80%;
h3 {
margin: 0;
margin-left: 5px;
font-size: 1.2em;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-message {
position: relative;
margin-bottom: 10px;
margin-left: 5px;
max-width:100px;
font-weight: bold;
color: #333;
max-height: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.chat-time {
position: absolute;
color: #666;
font-size: 0.9em;
right: 5px;
bottom: 10px;
}
}
.chat-room-menu {
position: absolute;
right: 10px;
top: 10px;
width: 20px;
background-color: white;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
text-align: center;
cursor: pointer;
}
/* Dropdown menu for actions */
.menu-dropdown {
position: absolute;
right: 6px;
top: 36px;
min-width: 160px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
padding: 6px 0;
display: none;
}
.menu-open {
display: block;
}
.menu-item {
width: 100%;
background: transparent;
border: none;
text-align: left;
padding: 10px 12px;
font-size: 14px;
color: #111827;
cursor: pointer;
}
.menu-item:hover {
background: #f3f4f6;
}
}
}
.message-item {
position: relative;
left: 15%;
width: 85%;
height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #f0f0f0;
background-size: cover;
z-index: 2;
.message-header {
z-index: 5;
width: 100%;
position: sticky;
top: 0;
background-color: #fff;
height: 70px;
display: flex;
align-items: center;
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-left: 40px;
}
h2 {
width: 10%;
color: #333;
text-align: center;
margin: 0;
padding: 0;
}
border-bottom: 1px solid #000000;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
z-index: 3;
.message {
display: flex;
max-width: 80%;
padding: 10px;
margin-bottom: 15px;
margin-left: 15px;;
border-radius: 15px;
overflow-wrap: break-word;
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.message-content {
min-width: 0;
.message-info {
height: auto;
.message-sender {
font-weight: bold;
color: #333;
margin-right: 8px;
}
.message-time {
font-size: 0.8em;
color: #999;
}
}
.message-text {
padding: 8px 12px;
margin: 5px 0;
border-radius: 10px;
color: #555;
background-color: #f5f5f5;
display: block;
word-break: break-word;
}
}
}
}
.message-input {
position: sticky;
bottom: 0;
width: 90%;
padding: 15px;
box-sizing: border-box;
z-index: 4;
display: flex;
margin-left: 5%;
input {
flex: 1;
padding: 12px 15px;
border-radius: 25px;
font-size: 16px;
margin-right: 10px;
background-color: transparent;
}
input:hover {
border: #000000 1px solid;
}
}
}
/* From Uiverse.io by njesenberger */
.button {
-webkit-appearance: none;
appearance: none;
position: relative;
border-width: 0;
padding: 0 8px 12px;
min-width: 10em;
box-sizing: border-box;
background: transparent;
font: inherit;
cursor: pointer;
}
.button-top {
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 0;
padding: 8px 16px;
transform: translateY(0);
text-align: center;
color: #fff;
text-shadow: 0 -1px rgba(0, 0, 0, .25);
transition-property: transform;
transition-duration: .2s;
-webkit-user-select: none;
user-select: none;
}
.button:active .button-top {
transform: translateY(6px);
}
.button-top::after {
content: '';
position: absolute;
z-index: -1;
border-radius: 4px;
width: 100%;
height: 100%;
box-sizing: content-box;
background-image: radial-gradient(#cd3f64, #9d3656);
text-align: center;
color: #fff;
box-shadow: inset 0 0 0px 1px rgba(255, 255, 255, .2), 0 1px 2px 1px rgba(255, 255, 255, .2);
transition-property: border-radius, padding, width, transform;
transition-duration: .2s;
}
.button:active .button-top::after {
border-radius: 6px;
padding: 0 2px;
}
.button-bottom {
position: absolute;
z-index: -1;
bottom: 4px;
left: 4px;
border-radius: 8px / 16px 16px 8px 8px;
padding-top: 6px;
width: calc(100% - 8px);
height: calc(100% - 10px);
box-sizing: content-box;
background-color: #803;
background-image: radial-gradient(4px 8px at 4px calc(100% - 8px), rgba(255, 255, 255, .25), transparent), radial-gradient(4px 8px at calc(100% - 4px) calc(100% - 8px), rgba(255, 255, 255, .25), transparent), radial-gradient(16px at -4px 0, white, transparent), radial-gradient(16px at calc(100% + 4px) 0, white, transparent);
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.5), inset 0 -1px 3px 3px rgba(0, 0, 0, .4);
transition-property: border-radius, padding-top;
transition-duration: .2s;
}
.button:active .button-bottom {
border-radius: 10px 10px 8px 8px / 8px;
padding-top: 0;
}
.button-base {
position: absolute;
z-index: -2;
top: 4px;
left: 0;
border-radius: 12px;
width: 100%;
height: calc(100% - 4px);
background-color: rgba(0, 0, 0, .15);
box-shadow: 0 1px 1px 0 rgba(255, 255, 255, .75), inset 0 2px 2px rgba(0, 0, 0, .25);
}
.message-item {
/* From Uiverse.io by adamgiebl */
button {
font-family: inherit;
font-size: 20px;
background: transparent;
color: white;
padding: 0.7em 1em;
padding-left: 0.9em;
display: flex;
align-items: center;
border: none;
border-radius: 16px;
overflow: hidden;
transition: all 0.2s;
cursor: pointer;
}
button span {
display: block;
margin-left: 0.3em;
transition: all 0.3s ease-in-out;
}
button svg {
display: block;
transform-origin: center center;
transition: transform 0.3s ease-in-out;
}
button:hover .svg-wrapper {
animation: fly-1 0.6s ease-in-out infinite alternate;
}
button:hover svg {
transform: translateX(1.2em) rotate(45deg) scale(1.1);
}
button:hover span {
transform: translateX(5em);
}
button:active {
transform: scale(0.95);
}
@keyframes fly-1 {
from {
transform: translateY(0.1em);
}
to {
transform: translateY(-0.1em);
}
}
}
\ No newline at end of file
'use client';
import styles from "./ChatRoom.module.css";
import React, { useEffect, useState } from "react";
import { useRouter } from 'next/navigation';
const backEnd:string = "http://localhost:8080";
const Profile = [ 'https://pic4.zhimg.com/v2-c5a0d0d57c1a85c6db56e918707f54a3_r.jpg',
'https://pic2.zhimg.com/v2-c2e79191533fdc7fced2f658eef987c9_r.jpg',
'https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg',
'https://pic1.zhimg.com/v2-10e9368af9eb405c8844584ad3ad9dd8_r.jpg',
'https://picx.zhimg.com/50/v2-63e3500bfd25b6ae7013a6a3b6ce045b_720w.jpg',
'https://c-ssl.duitang.com/uploads/blog/202109/20/20210920000906_53764.png']
const RoomProfile = 'https://tse1-mm.cn.bing.net/th/id/OIP-C.0KyBJKAdIGi9SAQc_X62tQHaLr?cb=thvnextc2&rs=1&pid=ImgDetMain';
interface RoomEntryProps {
roomId: number;
roomName: string;
lastSender: { String: string, Valid: boolean };
lastContent: { String: string, Valid: boolean };
lastTime: { Time: string, Valid: boolean };
}
interface MessageProps {
roomId: number;
roomName: string;
messages: Array<{
profile: number;
sender: string;
content: string;
time: string;
}>;
}
function RoomEntry ({rooms, onRoomClick, onRename, onDelete} : {rooms: RoomEntryProps[], onRoomClick: (roomId: number, roomName: string) => void, onRename: (roomId: number, currentName: string) => void, onDelete: (roomId: number) => void}) {
const [openMenuFor, setOpenMenuFor] = useState<number | null>(null);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (!target.closest('[data-menu="room-actions"]')) {
setOpenMenuFor(null);
}
};
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, []);
return (
<div className={styles["chat-room-nav"]}>
<div className={styles["sidebar-action"]}>
<button type="button" className={styles["button"]} onClick={openOpenDiv}>
<div className={styles["button-top"]}>New Chat</div>
<div className={styles["button-bottom"]}></div>
<div className={styles["button-base"]}></div>
</button>
</div>
<div className={styles["chat-list"]}>
{rooms.map((room) => (
<div className={styles["chat-item"]} key={room.roomId}>
<img src={RoomProfile} alt="Avatar" className={styles["avatar"]} />
<div className={styles["chat-info"]}>
<h3 onClick={() => onRoomClick(room.roomId, room.roomName)}>{room.roomName}</h3>
<span className={styles["chat-message"]}>
{room.lastSender.Valid ? room.lastSender.String : ''}:
{room.lastContent.Valid ? room.lastContent.String : ''}</span>
<span className={styles["chat-time"]}>{room.lastTime.Valid ? formatTimeToHoursMinutes(room.lastTime.Time) : ''}</span>
</div>
<div
className={styles["chat-room-menu"]}
data-menu="room-actions"
onClick={(e) => {
e.stopPropagation();
setOpenMenuFor(prev => (prev === room.roomId ? null : room.roomId));
}}
aria-label="Room actions"
title="Room actions"
>
···
<div className={`${styles["menu-dropdown"]} ${openMenuFor === room.roomId ? styles["menu-open"] : ''}`}>
<button
className={styles["menu-item"]}
onClick={(e) => { e.stopPropagation(); setOpenMenuFor(null); onRename(room.roomId, room.roomName); }}
>
Rename room
</button>
<button
className={styles["menu-item"]}
onClick={(e) => { e.stopPropagation(); setOpenMenuFor(null); onDelete(room.roomId); }}
>
Delete room
</button>
</div>
</div>
</div>
))}
</div>
</div>
);
// Button From Uiverse.io by njesenberger
}
function formatTimeToHoursMinutes(isoString: string) {
const date = new Date(isoString);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
function InputRoomNameArea({ onAddNewRoom }: { onAddNewRoom: (roomName: string) => void}) {
const [roomNameInput, setRoomNameInput] = useState("");
const handleAddNewRoom = () => {
onAddNewRoom(roomNameInput);
setRoomNameInput("");
closeOpenDiv();
}
return (
<div className={styles["open"]}>
<div className={styles["roomName-input"]}>
<h3>Please Enter the New Room Name</h3>
<input
type="text"
className={styles["RoomNameInput"]}
placeholder="Start new chat"
value = {roomNameInput}
onChange={(e) => setRoomNameInput(e.target.value)}
onKeyUpCapture={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleAddNewRoom();
}
else if (e.key === 'Escape') {
closeOpenDiv();
}
}}
/>
<div className={styles["button-container"]}>
<button className={styles["create-button"]} onClick={handleAddNewRoom}>Submit</button>
<button className={styles["cancel-button"]} onClick={closeOpenDiv}>Cancel</button>
</div>
</div>
</div>
);
}
function openOpenDiv() {
const openDiv = document.getElementsByClassName(styles.open)[0] as HTMLDivElement | undefined;
if (openDiv) {
openDiv.style.zIndex = "1000";
}
const roomNameInput = document.getElementsByClassName(styles.RoomNameInput)[0] as HTMLInputElement | undefined;
if (roomNameInput) {
roomNameInput.style.zIndex = "1001";
}
}
function closeOpenDiv() {
const openDiv = document.getElementsByClassName(styles.open)[0] as HTMLDivElement | undefined;
if (openDiv) {
openDiv.style.zIndex = "0";
}
const roomNameInput = document.getElementsByClassName(styles.RoomNameInput)[0] as HTMLInputElement | undefined;
if (roomNameInput) {
roomNameInput.style.zIndex = "0";
roomNameInput.value = '';
}
}
function MessageItem (props: MessageProps & { onAddNewComment: (content: string) => void}) {
const [inputValue, setInputValue] = useState("");
if (props.roomId === 0) {
return <div className={styles["message-item"]}>Please select a room to chat.</div>;
}
const handlerSend = () => {
if (inputValue.trim() === '') {
alert("Message can't be empty");
return
}
props.onAddNewComment(inputValue);
setInputValue('');
}
return (
<div className={styles["message-item"]}>
<div className={styles["message-header"]}>
<img src={RoomProfile} alt="Avatar" className={styles["avatar"]} />
<h2>{props.roomName}</h2>
</div>
<div className={styles["message-list"]}>
{props.messages.map((msg, index) => (
<div key={index} className={styles["message"]}>
<img src={Profile[msg.profile]} alt={`${msg.sender}'s avatar`} className={styles["avatar"]} />
<div className={styles["message-content"]}>
<div className={styles["message-info"]}>
<span className={styles["message-sender"]}>{msg.sender}</span>
<span className={styles["message-time"]}>{formatTimeToHoursMinutes(msg.time)}</span>
</div>
<p className={styles["message-text"]}>{msg.content}</p>
</div>
</div>
))}
</div>
<div className={styles["message-input"]}>
<input
type="text"
placeholder="Type a message..."
className={styles["Inputarea"]}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyUpCapture={
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handlerSend();
}
}}/>
<button className={styles["send-button"]} onClick={handlerSend}>
<div className={styles["svg-wrapper-1"]}>
<div className={styles["svg-wrapper"]}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path fill="none" d="M0 0h24v24H0z"></path>
<path
fill="currentColor"
d="M1.946 9.315c-.522-.174-.527-.455.01-.634l19.087-6.362c.529-.176.832.12.684.638l-5.454 19.086c-.15.529-.455.547-.679.045L12 14l6-8-8 6-8.054-2.685z"
></path>
</svg>
</div>
</div>
<span>Send</span>
</button>
</div>
</div>
);
// From Uiverse.io by adamgiebl
}
function ChatRoomComponent({ userName }: { userName: string }) {
const [rooms, setRooms] = useState<RoomEntryProps[]>([]);
const [currentRoom, setCurrentRoom] = useState<MessageProps | null>(null);
const ROOM_LIST_REFRESH_INTERVAL = 1000;
const MESSAGE_REFRESH_INTERVAL = 1000;
const fetchRooms = async () => {
try {
const response = await fetch(backEnd+"/room/list");
const result = await response.json();
if (result.code === 0) {
const ferchedRooms = result.data.map((room:any) => ({
...room,
lastSender: room.lastSender || { String: '', Valid: false },
laseComment: room.lastComment || { String: '', Valid: false },
lastTime: room.lastTime || { String: '', Valid: false }
}));
setRooms(ferchedRooms);
}
} catch (error) {
console.error("Error fetching rooms:", error);
}
};
const fetchCurrentRoomMessages = async (roomId: number) => {
if (!roomId) return;
try {
const response = await fetch(backEnd+`/room/message/list?roomId=${roomId}`);
const result = await response.json();
if (result.code === 0) {
setCurrentRoom(prev => {
if (!prev || prev.roomId !== roomId) return prev;
return {
...prev,
messages: result.data || []
};
});
}
} catch (error) {
console.error("Error fetching messages:", error);
}
};
useEffect(() => {
fetchRooms();
const roomListInterval = setInterval(() => {
fetchRooms();
}, ROOM_LIST_REFRESH_INTERVAL);
return () => clearInterval(roomListInterval);
}, []);
useEffect(() => {
if (!currentRoom?.roomId) return;
const messageInterval = setInterval(() => {
fetchCurrentRoomMessages(currentRoom.roomId);
}, MESSAGE_REFRESH_INTERVAL);
return () => clearInterval(messageInterval);
}, [currentRoom?.roomId]);
const handleRoomClick = async (roomId: number, roomName: string) => {
setCurrentRoom({
roomId: roomId,
roomName: roomName,
messages: []
});
await fetchCurrentRoomMessages(roomId);
}
async function addNewRoom(roomName: string) {
if (roomName.trim() === "") {
alert("Please enter a room name.");
return;
}
try {
const response = await fetch (backEnd + "/room/add", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ roomName: roomName })
})
const result = await response.json();
if (result.room_id > 0) {
const newRoom:RoomEntryProps = {
roomId: result.RoomId,
roomName: roomName,
lastSender: { String: '', Valid: false } ,
lastContent: { String: '', Valid: false },
lastTime: { Time: '', Valid: false },
};
setRooms(prevRooms => [newRoom, ...prevRooms]);
} else {
alert("Faile to add a new room" + result.Msg);
}
} catch (error) {
console.error("Error adding new room" + error);
alert("Error adding new room.");
}
}
const addNewComment = async (content: string) => {
if (!currentRoom)
return;
let profileId = 0;
if (userName === '蔡徐坤') {
profileId = Profile.length - 1;
} else {
profileId = Math.floor(Math.random() * (Profile.length-1));
}
try {
const response = await fetch(backEnd + '/room/message/add', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
roomId: currentRoom.roomId,
profile_id: profileId,
sender: userName,
content
})
})
const result = await response.json();
debugger;
if (result.code != 0) {
alert(`Error: ${result.Msg}`);
return;
}
const newMessage: { profile: number, sender: string, content: string, time: string } = {
profile: profileId,
sender: userName,
content: content,
time: new Date().toISOString()
};
setCurrentRoom(prevRoom => {
if (!prevRoom) return null;
return {
...prevRoom,
messages: [...prevRoom.messages, newMessage]
};
});
} catch (error) {
console.error("Error in addNewComment:", error);
alert("An error occurred while sending the message.");
}
}
const handleRename = async (roomId: number, currentName: string) => {
const newName = prompt('Enter new room name', currentName);
if (!newName || newName.trim() === '' || newName === currentName) return;
try {
const response = await fetch(backEnd + '/room/rename', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ roomId, roomName: newName })
});
const result = await response.json();
if (result.code !== 0) {
alert('Rename failed: ' + (result.msg || 'unknown error'));
return;
}
setRooms(prev => prev.map(r => r.roomId === roomId ? { ...r, roomName: newName } : r));
setCurrentRoom(prev => prev && prev.roomId === roomId ? { ...prev, roomName: newName } : prev);
} catch (err) {
console.error('Rename error', err);
alert('Rename error');
}
};
const handleDelete = async (roomId: number) => {
if (!confirm('Delete this room? This cannot be undone.')) return;
try {
const response = await fetch(backEnd + '/room/delete?roomId=' + roomId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const result = await response.json();
if (result.code !== 0) {
alert('Delete failed: ' + (result.msg || 'unknown error'));
return;
}
setRooms(prev => prev.filter(r => r.roomId !== roomId));
setCurrentRoom(prev => (prev && prev.roomId === roomId) ? null : prev);
} catch (err) {
console.error('Delete error', err);
alert('Delete error');
}
};
return (
<div className={styles["chat-room"]}>
<RoomEntry rooms={rooms} onRoomClick={handleRoomClick} onRename={handleRename} onDelete={handleDelete}/>
<MessageItem
roomId={currentRoom?.roomId || 0}
roomName={currentRoom?.roomName || ""}
messages={currentRoom?.messages || []}
onAddNewComment={addNewComment}
/>
<InputRoomNameArea onAddNewRoom={addNewRoom}/>
</div>
);
}
export default function ChatRoom() {
const [userName, setUserName] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
const storedUserName = localStorage.getItem('userName');
if (storedUserName) {
setUserName(storedUserName);
} else {
router.push('/');
}
}, [router]);
if (!userName) {
return <div>Loading...</div>;
}
return <ChatRoomComponent userName={userName} />;
}
\ No newline at end of file
/*
{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
} */
.SetName-Body
{
z-index: 2000;
display: flex;
position: absolute;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100vw;
background-size: cover;
background-color: grey;
}
.login-box
{
position: relative;
width: 400px;
height: 450px;
background-color: transparent;
border: 2px solid rgba(255, 255 , 255, .5);
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(15px);
}
.setname-h2
{
font-size: 2em;
color: white;
text-align: center;
}
.input-box
{
position: relative;
width: 310px;
margin: 30px 0;
border-bottom: 2px solid white;
}
.input-box label
{
position: absolute;
top: 50%;
left: 5px;
transform: translateY(-50%);
font-size: 1em;
color: white;
pointer-events: none;
transition: .5s;
}
.input-box input:focus~label,
.input-box input:valid~label
{
top: -5px;
}
.input-box input
{
width: 100%;
height: 50px;
border: none;
outline: none;
background-color: transparent;
color: black;
font-size: 1em;
padding: 0 35px 0 5px;
}
.input-box .icon
{
position: absolute;
right: 8px;
color: white;
font-size: .9em;
line-height: 57px;
}
.SetName-button
{
outline: none;
border: none;
width: 100%;
height: 50px;
background-color: white;
border-radius: 40px;
cursor: pointer;
color: black;
font-size: 1em;
font-weight: bold;
}
.SetName-button:hover
{
background-color: lightgray;
}
.setname-repeat
{
font-size: .9em;
color: white;
margin: 20px 0 10px 0;
}
\ No newline at end of file
'use client';
import styles from"./Register.module.css"
import { MdLock, MdPerson } from "react-icons/md";
import { useState } from "react";
import { useRouter } from 'next/navigation';
const backEnd:string = "http://localhost:8080";
export default function Register() {
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setPasswordError('');
if (password !== confirmPassword) {
setPasswordError('Passwords do not match');
return;
}
if (password.length < 6) {
setPasswordError('Password must be at least 6 characters');
return;
}
try {
const response = await fetch(backEnd + '/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ userName, password }),
});
if (!response.ok) {
throw new Error('Failed to register');
}
alert('Registration successful! Redirecting to login page.');
router.push('/SetName');
} catch (error) {
console.error(error);
setPasswordError('Registration failed. Please try again.');
}
}
return (
<div className={styles["SetName-Body"]}>
<div className={styles["login-box"]}>
<form onSubmit={handleSubmit}>
<h2 className={styles["setname-h2"]}>Register</h2>
<div className={styles["input-box"]}>
<span className={styles["icon"]}>
<MdPerson />
</span>
<input
required
value={userName}
onChange={(e) => setUserName(e.target.value)} />
<label>Name</label>
</div>
<div className={styles["input-box"]}>
<span className={styles["icon"]}>
<MdLock />
</span>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)} />
<label>Password</label>
</div>
<div className={styles["input-box"]}>
<span className={styles["icon"]}>
<MdLock />
</span>
<input
type="password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)} />
<label>Confirm Password</label>
</div>
{passwordError && (
<div style={{ color: 'red', fontSize: '14px', marginTop: '5px', textAlign: 'center' }}>
{passwordError}
</div>
)}
<div>
<button className={styles["SetName-button"]} type="submit">Register</button>
</div>
</form>
</div>
</div>
);
}
\ No newline at end of file
/*
{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
} */
.SetName-Body
{
z-index: 2000;
display: flex;
position: absolute;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100vw;
background-size: cover;
background-color: grey;
}
.login-box
{
position: relative;
width: 400px;
height: 450px;
background-color: transparent;
border: 2px solid rgba(255, 255 , 255, .5);
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(15px);
}
.setname-h2
{
font-size: 2em;
color: white;
text-align: center;
}
.input-box
{
position: relative;
width: 310px;
margin: 30px 0;
border-bottom: 2px solid white;
}
.input-box label
{
position: absolute;
top: 50%;
left: 5px;
transform: translateY(-50%);
font-size: 1em;
color: white;
pointer-events: none;
transition: .5s;
}
.input-box input:focus~label,
.input-box input:valid~label
{
top: -5px;
}
.input-box input
{
width: 100%;
height: 50px;
border: none;
outline: none;
background-color: transparent;
color: black;
font-size: 1em;
padding: 0 35px 0 5px;
}
.input-box .icon
{
position: absolute;
right: 8px;
color: white;
font-size: .9em;
line-height: 57px;
}
.SetName-button
{
outline: none;
border: none;
width: 100%;
height: 50px;
background-color: white;
border-radius: 40px;
cursor: pointer;
color: black;
font-size: 1em;
font-weight: bold;
}
.SetName-button:hover
{
background-color: lightgray;
}
.register_link
{
font-size: .9em;
text-align: center;
color: white;
margin: 20px 0 10px 0;
a
{
text-decoration: none;
color: white;
font-weight: 600;
}
a:hover
{
text-decoration: underline;
}
}
\ No newline at end of file
'use client';
import styles from"./SetName.module.css"
import { MdLock, MdPerson } from "react-icons/md";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from 'next/navigation';
const backEnd:string = "http://localhost:8080";
export default function SetName() {
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch(`${backEnd}/login`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ userName, password })
});
const data = await response.json();
if (data.code === 0) {
localStorage.setItem('userName', userName);
router.push('/ChatRoom');
} else {
alert(data.msg);
}
} catch (error) {
console.error("Error logging in:", error);
alert("Login failed. Please check the console for details.");
}
}
return (
<div className={styles["SetName-Body"]}>
<div className={styles["login-box"]}>
<form onSubmit={handleSubmit}>
<h2 className={styles["setname-h2"]}>Login</h2>
<div className={styles["input-box"]}>
<span className={styles["icon"]}>
<MdPerson />
</span>
<input
required
value={userName}
onChange={(e) => setUserName(e.target.value)} />
<label>Name</label>
</div>
<div className={styles["input-box"]}>
<span className={styles["icon"]}>
<MdLock />
</span>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)} />
<label>Password</label>
</div>
<div>
<button className={styles["SetName-button"]} type="submit">Login in</button>
</div>
<div className={styles["register_link"]}>
<p>Don't have an account?<Link href="/Register">Register</Link></p>
</div>
</form>
</div>
</div>
);
}
:root {
/* 字体家族 */
--font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
/* 字体大小 */
--font-size-base: 13px;
--font-size-sm: 12px;
/* 字体粗细 */
--font-weight-normal: 400;
--font-weight-medium: 500;
/* 行高 */
--line-height-title: 1.5;
--line-height-body: 1.4;
/* 颜色变量 - 基础灰色 */
--gray1: hsl(0, 0%, 99%);
--gray2: hsl(0, 0%, 97.3%);
--gray3: hsl(0, 0%, 95.1%);
--gray4: hsl(0, 0%, 93%);
--gray5: hsl(0, 0%, 90.9%);
--gray6: hsl(0, 0%, 88.7%);
--gray7: hsl(0, 0%, 85.8%);
--gray8: hsl(0, 0%, 78%);
--gray9: hsl(0, 0%, 56.1%);
--gray10: hsl(0, 0%, 52.3%);
--gray11: hsl(0, 0%, 43.5%);
--gray12: hsl(0, 0%, 9%);
/* 主题颜色 */
--normal-text: var(--gray12);
--success-text: hsl(140, 100%, 27%);
}
/* 全局字体应用 */
body {
margin: 0;
font-family: var(--font-family);
font-size: var(--font-size-base);
line-height: var(--line-height-body);
font-weight: var(--font-weight-normal);
color: var(--normal-text);
}
/* 标题样式 */
h1, h2, h3, h4, h5, h6,
.title {
font-weight: var(--font-weight-medium);
line-height: var(--line-height-title);
}
/* 辅助样式类 */
.text-sm {
font-size: var(--font-size-sm);
}
.text-medium {
font-weight: var(--font-weight-medium);
}
.text-success {
color: var(--success-text);
}
/* 响应式字体大小(可选) */
@media (max-width: 600px) {
:root {
--font-size-base: 12px;
--font-size-sm: 11px;
}
}
.context-menu {
/* From Uiverse.io by andrew-demchenk0 */
.button {
--main-focus: #2d8cf0;
--font-color: #323232;
--bg-color-sub: #dedede;
--bg-color: #eee;
--main-color: #323232;
position: relative;
width: 150px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
border: 2px solid var(--main-color);
box-shadow: 4px 4px var(--main-color);
background-color: var(--bg-color);
border-radius: 10px;
overflow: hidden;
}
.button, .button__icon, .button__text {
transition: all 0.3s;
}
.button .button__text {
transform: translateX(33px);
color: var(--font-color);
font-weight: 600;
}
.button .button__icon {
position: absolute;
transform: translateX(109px);
height: 100%;
width: 39px;
background-color: var(--bg-color-sub);
display: flex;
align-items: center;
justify-content: center;
}
.button .svg {
width: 20px;
fill: var(--main-color);
}
.button:hover {
background: var(--bg-color);
}
.button:hover .button__text {
color: transparent;
}
.button:hover .button__icon {
width: 148px;
transform: translateX(0);
}
.button:active {
transform: translate(3px, 3px);
box-shadow: 0px 0px var(--main-color);
}
}
.root {
display: flex;
position: relative;
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment