--[[ Roblox Ban System — GUI (CLIENT) Location: StarterPlayer > StarterPlayerScripts (LocalScript type) Builds the entire admin panel automatically. No manual UI creation needed. Works together with the BanSystem server script (needs BanSystemRemote in ReplicatedStorage, which the server script creates). Controls: - A small "BAN PANEL" toggle button appears in the bottom-right. - Click a row to select a banned player, then Unban them. - Use the fields at the bottom to Ban or Temp-Ban someone by username. ]] local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer -- Wait for the remote the server creates. local remote = ReplicatedStorage:WaitForChild("BanSystemRemote", 15) --============================== THEME ==============================-- local THEME = { Background = Color3.fromRGB(28, 30, 38), Panel = Color3.fromRGB(38, 41, 52), Row = Color3.fromRGB(48, 52, 66), RowSelected = Color3.fromRGB(74, 110, 200), Accent = Color3.fromRGB(88, 140, 255), Danger = Color3.fromRGB(230, 70, 70), Success = Color3.fromRGB(70, 200, 120), Text = Color3.fromRGB(235, 238, 245), SubText = Color3.fromRGB(160, 165, 180), Stroke = Color3.fromRGB(60, 64, 80), } --============================== HELPERS ==============================-- local function make(className, props, children) local obj = Instance.new(className) for k, v in pairs(props or {}) do if k ~= "Parent" then obj[k] = v end end if props and props.Parent then obj.Parent = props.Parent end for _, child in ipairs(children or {}) do child.Parent = obj end return obj end local function setCorner(obj, radius) local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, radius or 8) c.Parent = obj return c end local function setStroke(obj, color, thickness) local s = Instance.new("UIStroke") s.Color = color or THEME.Stroke s.Thickness = thickness or 1 s.ApplyStrokeMode = Enum.ApplyStrokeMode.Border s.Parent = obj return s end local function makeButton(text, color) local b = make("TextButton", { Text = text, Font = Enum.Font.GothamSemibold, TextSize = 14, TextColor3 = THEME.Text, BackgroundColor3 = color or THEME.Accent, AutoButtonColor = true, }) setCorner(b, 6) return b end local function makeTextBox(placeholder) local t = make("TextBox", { PlaceholderText = placeholder, Text = "", Font = Enum.Font.Gotham, TextSize = 14, TextColor3 = THEME.Text, PlaceholderColor3 = THEME.SubText, BackgroundColor3 = THEME.Row, BorderSizePixel = 0, TextXAlignment = Enum.TextXAlignment.Left, AutomaticSize = Enum.AutomaticSize.None, }) setCorner(t, 6) -- Inner padding local pad = Instance.new("UIPadding") pad.PaddingLeft = UDim.new(0, 10) pad.PaddingRight = UDim.new(0, 10) pad.Parent = t return t end --============================== GUI BUILD ==============================-- local screenGui = make("ScreenGui", { Name = "BanSystemGui", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, Parent = player:WaitForChild("PlayerGui"), }) -- Toggle button (bottom-right) local toggleBtn = make("TextButton", { Name = "ToggleBtn", Text = "BAN PANEL", Font = Enum.Font.GothamBold, TextSize = 14, TextColor3 = Color3.fromRGB(255, 255, 255), BackgroundColor3 = THEME.Accent, Size = UDim2.new(0, 130, 0, 38), Position = UDim2.new(1, -145, 1, -50), Parent = screenGui, }) setCorner(toggleBtn, 8) -- Main panel (hidden by default) local panel = make("Frame", { Name = "Panel", Size = UDim2.new(0, 560, 0, 460), Position = UDim2.new(0.5, -280, 0.5, -230), BackgroundColor3 = THEME.Background, BorderSizePixel = 0, Visible = false, Parent = screenGui, }) setCorner(panel, 12) setStroke(panel, THEME.Stroke, 2) -- Panel title bar local titleBar = make("Frame", { Name = "TitleBar", Size = UDim2.new(1, 0, 0, 44), BackgroundColor3 = THEME.Panel, BorderSizePixel = 0, Parent = panel, }) -- Title bar needs its own corner only at top — use a full corner, fine visually. setCorner(titleBar, 12) local title = make("TextLabel", { Text = "Ban Management", Font = Enum.Font.GothamBold, TextSize = 18, TextColor3 = THEME.Text, BackgroundTransparency = 1, Size = UDim2.new(1, -100, 1, 0), Position = UDim2.new(0, 16, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, Parent = titleBar, }) local closeBtn = make("TextButton", { Text = "X", Font = Enum.Font.GothamBold, TextSize = 16, TextColor3 = THEME.SubText, BackgroundColor3 = THEME.Panel, Size = UDim2.new(0, 32, 0, 32), Position = UDim2.new(1, -38, 0, 6), Parent = titleBar, }) setCorner(closeBtn, 6) -- Refresh button in title bar local refreshBtn = make("TextButton", { Text = "Refresh", Font = Enum.Font.GothamSemibold, TextSize = 13, TextColor3 = THEME.Text, BackgroundColor3 = THEME.Row, Size = UDim2.new(0, 80, 0, 28), Position = UDim2.new(1, -130, 0, 8), Parent = titleBar, }) setCorner(refreshBtn, 6) -- Scroll list of banned players local listLabel = make("TextLabel", { Text = "Banned Players", Font = Enum.Font.GothamSemibold, TextSize = 13, TextColor3 = THEME.SubText, BackgroundTransparency = 1, Size = UDim2.new(1, -32, 0, 20), Position = UDim2.new(0, 16, 0, 52), TextXAlignment = Enum.TextXAlignment.Left, Parent = panel, }) local scrollFrame = make("ScrollingFrame", { Name = "BanList", Size = UDim2.new(1, -32, 0, 200), Position = UDim2.new(0, 16, 0, 76), BackgroundColor3 = THEME.Panel, BorderSizePixel = 0, ScrollBarThickness = 6, ScrollBarImageColor3 = THEME.Accent, CanvasSize = UDim2.new(0, 0, 0, 0), AutomaticCanvasSize = Enum.AutomaticSize.Y, Parent = panel, }) setCorner(scrollFrame, 8) local listLayout = Instance.new("UIListLayout") listLayout.Padding = UDim.new(0, 4) listLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center listLayout.SortOrder = Enum.SortOrder.LayoutOrder listLayout.Parent = scrollFrame local listPad = Instance.new("UIPadding") listPad.PaddingTop = UDim.new(0, 6) listPad.PaddingBottom = UDim.new(0, 6) listPad.PaddingLeft = UDim.new(0, 6) listPad.PaddingRight = UDim.new(0, 6) listPad.Parent = scrollFrame -- Status / feedback label local statusLabel = make("TextLabel", { Text = "", Font = Enum.Font.Gotham, TextSize = 13, TextColor3 = THEME.SubText, BackgroundTransparency = 1, Size = UDim2.new(1, -32, 0, 18), Position = UDim2.new(0, 16, 0, 284), TextXAlignment = Enum.TextXAlignment.Left, Parent = panel, }) -- Selected player row label + unban button local selectedLabel = make("TextLabel", { Text = "No player selected", Font = Enum.Font.GothamSemibold, TextSize = 14, TextColor3 = THEME.Text, BackgroundTransparency = 1, Size = UDim2.new(0, 320, 0, 26), Position = UDim2.new(0, 16, 0, 308), TextXAlignment = Enum.TextXAlignment.Left, Parent = panel, }) local unbanBtn = makeButton("UNBAN SELECTED", THEME.Success) unbanBtn.Size = UDim2.new(0, 170, 0, 30) unbanBtn.Position = UDim2.new(1, -186, 0, 306) unbanBtn.Parent = panel unbanBtn.Visible = false -- Bottom action bar: ban / tempban inputs local actionBar = make("Frame", { Name = "ActionBar", Size = UDim2.new(1, -32, 0, 110), Position = UDim2.new(0, 16, 0, 344), BackgroundColor3 = THEME.Panel, BorderSizePixel = 0, Parent = panel, }) setCorner(actionBar, 8) local nameInput = makeTextBox("Username to ban...") nameInput.Size = UDim2.new(0, 200, 0, 32) nameInput.Position = UDim2.new(0, 12, 0, 12) nameInput.Parent = actionBar local reasonInput = makeTextBox("Reason (optional)") reasonInput.Size = UDim2.new(1, -228, 0, 32) reasonInput.Position = UDim2.new(0, 220, 0, 12) reasonInput.Parent = actionBar local banBtn = makeButton("PERM BAN", THEME.Danger) banBtn.Size = UDim2.new(0, 130, 0, 32) banBtn.Position = UDim2.new(0, 12, 0, 58) banBtn.Parent = actionBar local durInput = makeTextBox("Duration (30m, 6h, 1d)") durInput.Size = UDim2.new(0, 130, 0, 32) durInput.Position = UDim2.new(0, 150, 0, 58) durInput.Parent = actionBar local tempBanBtn = makeButton("TEMP BAN", Color3.fromRGB(230, 160, 50)) tempBanBtn.Size = UDim2.new(0, 130, 0, 32) tempBanBtn.Position = UDim2.new(0, 290, 0, 58) tempBanBtn.Parent = actionBar local checkBtn = makeButton("CHECK", THEME.Row) checkBtn.Size = UDim2.new(0, 110, 0, 32) checkBtn.Position = UDim2.new(1, -122, 0, 58) checkBtn.TextColor3 = THEME.Text checkBtn.Parent = actionBar --============================== LOGIC ==============================-- local selectedUserId = nil local listRows = {} local function setStatus(msg, color) statusLabel.Text = msg statusLabel.TextColor3 = color or THEME.SubText end local function clearList() for _, row in ipairs(listRows) do row:Destroy() end table.clear(listRows) end local function formatExpiry(record) if not record.tempBan then return "Permanent" end if not record.expiresAt then return "Temp" end local remaining = record.expiresAt - os.time() if remaining <= 0 then return "Expiring..." end local d = math.floor(remaining / 86400) local h = math.floor((remaining % 86400) / 3600) local m = math.floor((remaining % 3600) / 60) if d > 0 then return d .. "d " .. h .. "h left" elseif h > 0 then return h .. "h " .. m .. "m left" else return m .. "m left" end end local function buildRow(record) local row = make("TextButton", { Text = "", BackgroundColor3 = THEME.Row, BorderSizePixel = 0, Size = UDim2.new(1, -12, 0, 44), AutoButtonColor = true, }) setCorner(row, 6) local nameText = make("TextLabel", { Text = record.userName, Font = Enum.Font.GothamSemibold, TextSize = 15, TextColor3 = THEME.Text, BackgroundTransparency = 1, Size = UDim2.new(0, 160, 1, 0), Position = UDim2.new(0, 10, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, Parent = row, }) local typeTag = make("TextLabel", { Text = record.tempBan and "TEMP" or "PERM", Font = Enum.Font.GothamBold, TextSize = 11, TextColor3 = record.tempBan and Color3.fromRGB(255, 200, 80) or THEME.Danger, BackgroundTransparency = 1, Size = UDim2.new(0, 50, 1, 0), Position = UDim2.new(0, 175, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, Parent = row, }) local expiryText = make("TextLabel", { Text = formatExpiry(record), Font = Enum.Font.Gotham, TextSize = 12, TextColor3 = THEME.SubText, BackgroundTransparency = 1, Size = UDim2.new(0, 120, 1, 0), Position = UDim2.new(0, 230, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, Parent = row, }) local reasonText = make("TextLabel", { Text = record.reason or "N/A", Font = Enum.Font.Gothom, TextSize = 12, TextColor3 = THEME.SubText, BackgroundTransparency = 1, Size = UDim2.new(1, -420, 1, 0), Position = UDim2.new(0, 360, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, TextTruncate = Enum.TextTruncate.AtEnd, Parent = row, }) row.MouseButton1Click:Connect(function() selectedUserId = record.userId selectedLabel.Text = "Selected: " .. record.userName .. " (ID " .. record.userId .. ")" unbanBtn.Visible = true -- Highlight selected for _, r in ipairs(listRows) do r.BackgroundColor3 = THEME.Row end row.BackgroundColor3 = THEME.RowSelected end) return row end local function refreshList() if not remote then setStatus("Remote not found!", THEME.Danger) return end setStatus("Loading ban list...") local ok, result = pcall(function() return remote:InvokeServer("list") end) if not ok then setStatus("Failed to contact server.", THEME.Danger) return end local success, data = type(result) == "table" and result[1], result and result[2] if not success then setStatus("Error: " .. tostring(data), THEME.Danger) return end clearList() local bans = data or {} if #bans == 0 then setStatus("No banned players. 🎉", THEME.Success) else setStatus(#bans .. " player(s) banned.") end for _, record in ipairs(bans) do local row = buildRow(record) row.Parent = scrollFrame table.insert(listRows, row) end end --============================== EVENTS ==============================-- toggleBtn.MouseButton1Click:Connect(function() panel.Visible = not panel.Visible if panel.Visible then toggleBtn.Text = "CLOSE" refreshList() else toggleBtn.Text = "BAN PANEL" end end) closeBtn.MouseButton1Click:Connect(function() panel.Visible = false toggleBtn.Text = "BAN PANEL" end) refreshBtn.MouseButton1Click:Connect(refreshList) unbanBtn.MouseButton1Click:Connect(function() if not selectedUserId then return end setStatus("Unbanning...") local ok, result = pcall(function() return remote:InvokeServer("unban", selectedUserId) end) if ok and result and result[1] then setStatus(tostring(result[2]), THEME.Success) selectedUserId = nil selectedLabel.Text = "No player selected" unbanBtn.Visible = false refreshList() else setStatus("Unban failed: " .. tostring(result and result[2]), THEME.Danger) end end) banBtn.MouseButton1Click:Connect(function() local name = nameInput.Text:match("^%s*(.-)%s*$") if name == "" then setStatus("Enter a username first.", THEME.Danger) return end setStatus("Banning " .. name .. "...") local ok, result = pcall(function() return remote:InvokeServer("ban", name, reasonInput.Text) end) if ok and result and result[1] then setStatus(tostring(result[2]), THEME.Success) nameInput.Text = "" reasonInput.Text = "" refreshList() else setStatus("Ban failed: " .. tostring(result and result[2]), THEME.Danger) end end) tempBanBtn.MouseButton1Click:Connect(function() local name = nameInput.Text:match("^%s*(.-)%s*$") local dur = durInput.Text:match("^%s*(.-)%s*$") if name == "" then setStatus("Enter a username first.", THEME.Danger) return end if dur == "" then setStatus("Enter a duration (e.g. 6h, 1d).", THEME.Danger) return end setStatus("Temp-banning " .. name .. "...") local ok, result = pcall(function() return remote:InvokeServer("tempban", name, dur, reasonInput.Text) end) if ok and result and result[1] then setStatus(tostring(result[2]), THEME.Success) nameInput.Text = "" reasonInput.Text = "" durInput.Text = "" refreshList() else setStatus("Temp-ban failed: " .. tostring(result and result[2]), THEME.Danger) end end) checkBtn.MouseButton1Click:Connect(function() local name = nameInput.Text:match("^%s*(.-)%s*$") if name == "" then setStatus("Enter a username to check.", THEME.Danger) return end local ok, result = pcall(function() return remote:InvokeServer("check", name) end) if ok and result and result[1] then setStatus(tostring(result[2]), THEME.Success) else setStatus(tostring(result and result[2]), THEME.Danger) end end) --============================== GUARD ==============================-- -- Check if the local player is an admin; hide the toggle if not. -- The server still validates every call, so this is just UX. task.spawn(function() -- Try a benign call; server returns false with "not admin" for non-admins. local ok, result = pcall(function() return remote:InvokeServer("list") end) if ok and result and result[1] == false and result[2] == "You are not an admin." then toggleBtn.Visible = false end end) print("[BanGui] Loaded.")