mirror of
https://github.com/neon-mmd/websurfx.git
synced 2024-12-22 12:28:21 -05:00
Merge pull request #110 from neon-mmd/improve-and-fix-settings-page
Improve the code associated with the settings page
This commit is contained in:
commit
311a8d5b1f
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -535,9 +535,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
|
||||
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -1773,9 +1773,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
|
||||
checksum = "16833386b02953ca926d19f64af613b9bf742c48dcd5e09b32fbfc9740bf84e2"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
@ -1783,9 +1783,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
|
||||
checksum = "7763190f9406839f99e5197afee8c9e759969f7dbfa40ad3b8dbee8757b745b5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@ -1793,9 +1793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
|
||||
checksum = "249061b22e99973da1f5f5f1410284419e283bb60b79255bf5f42a94b66a2e00"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@ -1806,9 +1806,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
|
||||
checksum = "457c310cfc9cf3f22bc58901cc7f0d3410ac5d6298e432a4f9a6138565cb6df6"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
@ -2555,9 +2555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.96"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
|
||||
dependencies = [
|
||||
"itoa 1.0.6",
|
||||
"ryu",
|
||||
@ -2617,9 +2617,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
@ -3381,7 +3381,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "websurfx"
|
||||
version = "0.12.3"
|
||||
version = "0.13.0"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "websurfx"
|
||||
version = "0.12.3"
|
||||
version = "0.13.0"
|
||||
edition = "2021"
|
||||
description = "An open-source alternative to Searx that provides clean, ad-free, and organic results with incredible speed while keeping privacy and security in mind."
|
||||
repository = "https://github.com/neon-mmd/websurfx"
|
||||
|
18
public/static/cookies.js
Normal file
18
public/static/cookies.js
Normal file
@ -0,0 +1,18 @@
|
||||
// This function is executed when any page on the website finsihes loading and
|
||||
// this function retrieves the cookies if it is present on the user's machine.
|
||||
// If it is available then the saved cookies is display in the cookies tab
|
||||
// otherwise an appropriate message is displayed if it is not available.
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
try {
|
||||
let cookie = decodeURIComponent(document.cookie)
|
||||
document.querySelector('.cookies input').value =
|
||||
cookie !== '' ? cookie : 'No cookies have been saved on your system'
|
||||
} catch (error) {
|
||||
console.error('Error decoding cookie:', error)
|
||||
document.querySelector('.cookies input').value = 'Error decoding cookie'
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
83
public/static/settings.js
Normal file
83
public/static/settings.js
Normal file
@ -0,0 +1,83 @@
|
||||
// This function handles the toggling of selections of all upstream search engines
|
||||
// options in the settings page under the tab engines.
|
||||
function toggleAllSelection() {
|
||||
document
|
||||
.querySelectorAll('.engine')
|
||||
.forEach(
|
||||
(engine_checkbox) =>
|
||||
(engine_checkbox.checked =
|
||||
document.querySelector('.select_all').checked)
|
||||
)
|
||||
}
|
||||
|
||||
// This function adds the functionality to sidebar buttons to only show settings
|
||||
// related to that tab.
|
||||
function setActiveTab(current_tab) {
|
||||
document
|
||||
.querySelectorAll('.tab')
|
||||
.forEach((tab) => tab.classList.remove('active'))
|
||||
document
|
||||
.querySelectorAll('.btn')
|
||||
.forEach((tab) => tab.classList.remove('active'))
|
||||
current_tab.classList.add('active')
|
||||
document
|
||||
.querySelector(`.${current_tab.innerText.toLowerCase().replace(' ', '_')}`)
|
||||
.classList.add('active')
|
||||
}
|
||||
|
||||
// This function adds the functionality to save all the user selected preferences
|
||||
// to be saved in a cookie on the users machine.
|
||||
function setClientSettings() {
|
||||
let cookie_dictionary = new Object()
|
||||
document.querySelectorAll('select').forEach((select_tag) => {
|
||||
if (select_tag.name === 'themes') {
|
||||
cookie_dictionary['theme'] = select_tag.value
|
||||
} else if (select_tag.name === 'colorschemes') {
|
||||
cookie_dictionary['colorscheme'] = select_tag.value
|
||||
}
|
||||
})
|
||||
let engines = []
|
||||
document.querySelectorAll('.engine').forEach((engine_checkbox) => {
|
||||
if (engine_checkbox.checked === true) {
|
||||
engines.push(engine_checkbox.parentNode.parentNode.innerText.trim())
|
||||
}
|
||||
})
|
||||
cookie_dictionary['engines'] = engines
|
||||
let expiration_date = new Date()
|
||||
expiration_date.setFullYear(expiration_date.getFullYear() + 1)
|
||||
document.cookie = `appCookie=${JSON.stringify(
|
||||
cookie_dictionary
|
||||
)}; expires=${expiration_date.toUTCString()}`
|
||||
|
||||
document.querySelector('.message').innerText =
|
||||
'✅ The settings have been saved sucessfully!!'
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector('.message').innerText = ''
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
// This functions gets the saved cookies if it is present on the user's machine If it
|
||||
// is available then it is parsed and converted to an object which is then used to
|
||||
// retrieve the preferences that the user had selected previously and is then loaded in the
|
||||
// website otherwise the function does nothing and the default server side settings are loaded.
|
||||
function getClientSettings() {
|
||||
let cookie = decodeURIComponent(document.cookie)
|
||||
|
||||
if (cookie !== '') {
|
||||
let cookie_value = decodeURIComponent(document.cookie)
|
||||
.split(';')
|
||||
.map((item) => item.split('='))
|
||||
.reduce((acc, [_, v]) => (acc = JSON.parse(v)) && acc, {})
|
||||
|
||||
let links = Array.from(document.querySelectorAll('link')).forEach(
|
||||
(item) => {
|
||||
if (item.href.includes('static/themes')) {
|
||||
item.href = `static/themes/${cookie_value['theme']}.css`
|
||||
} else if (item.href.includes('static/colorschemes')) {
|
||||
item.href = `static/colorschemes/${cookie_value['colorscheme']}.css`
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -263,41 +263,243 @@ footer {
|
||||
|
||||
/* Styles for the about page */
|
||||
|
||||
.about-container article{
|
||||
font-size: 1.5rem;
|
||||
color:var(--fg);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.about-container article {
|
||||
font-size: 1.5rem;
|
||||
color: var(--fg);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.about-container article h1{
|
||||
color: var(--2);
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
.about-container article h1 {
|
||||
color: var(--2);
|
||||
font-size: 2.8rem;
|
||||
}
|
||||
|
||||
.about-container article div{
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.about-container article div {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.about-container a{
|
||||
color:var(--3);
|
||||
}
|
||||
.about-container a {
|
||||
color: var(--3);
|
||||
}
|
||||
|
||||
.about-container article h2{
|
||||
color: var(--3);
|
||||
font-size: 1.8rem;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.about-container article h2 {
|
||||
color: var(--3);
|
||||
font-size: 1.8rem;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.about-container p{
|
||||
color:var(--fg);
|
||||
font-size: 1.6rem;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.about-container p {
|
||||
color: var(--fg);
|
||||
font-size: 1.6rem;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.about-container h3{
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.about-container h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.about-container {
|
||||
width: 80%;
|
||||
}
|
||||
.about-container {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/* Styles for the settings page */
|
||||
.settings_container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 80dvw;
|
||||
}
|
||||
|
||||
.settings h1 {
|
||||
color: var(--2);
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.settings hr {
|
||||
border-color: var(--3);
|
||||
margin: 0.3rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.settings_container .sidebar {
|
||||
width: 30%;
|
||||
cursor: pointer;
|
||||
font-size: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 0.5rem;
|
||||
margin-left: -0.7rem;
|
||||
padding: 0.7rem;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--fg);
|
||||
text-transform: capitalize;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.settings_container .sidebar .btn {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.settings_container .sidebar .btn.active {
|
||||
background-color: var(--2);
|
||||
}
|
||||
|
||||
.settings_container .main_container {
|
||||
width: 70%;
|
||||
border-left: 1.5px solid var(--3);
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.settings_container .tab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings_container .tab.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.settings_container button {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.5rem;
|
||||
background: var(--3);
|
||||
color: var(--bg);
|
||||
border-radius: 0.5rem;
|
||||
border: 2px solid transparent;
|
||||
font-weight: bold;
|
||||
transition: all 0.1s ease-out;
|
||||
cursor: pointer;
|
||||
box-shadow: 5px 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings_container button:active {
|
||||
box-shadow: none;
|
||||
translate: 5px 5px;
|
||||
}
|
||||
|
||||
.settings_container .main_container .message {
|
||||
font-size: 1.5rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.settings_container .tab h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: var(--4);
|
||||
margin-top: 1.5rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.settings_container .tab .description {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.settings_container .user_interface select {
|
||||
margin: 0.7rem 0;
|
||||
width: 20rem;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.5rem;
|
||||
outline: none;
|
||||
border: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.settings_container .user_interface option:hover {
|
||||
background-color: var(--1);
|
||||
}
|
||||
|
||||
.settings_container .engines .engine_selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.settings_container .engines .toggle_btn {
|
||||
color: var(--fg);
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings_container .engines hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings_container .cookies input {
|
||||
margin: 1rem 0rem;
|
||||
}
|
||||
|
||||
/* Styles for the toggle button */
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 6rem;
|
||||
height: 3.4rem;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bg);
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 2.6rem;
|
||||
width: 2.6rem;
|
||||
left: 0.4rem;
|
||||
bottom: 0.4rem;
|
||||
background-color: var(--fg);
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--3);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px var(--3);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(2.6rem);
|
||||
-ms-transform: translateX(2.6rem);
|
||||
transform: translateX(2.6rem);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 3.4rem;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
12
public/templates/cookies_tab.html
Normal file
12
public/templates/cookies_tab.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="cookies tab">
|
||||
<h1>Cookies</h1>
|
||||
<p class="description">
|
||||
This is the cookies are saved on your system and it contains the preferences
|
||||
you chose in the settings page
|
||||
</p>
|
||||
<input type="text" name="cookie_field" value="" readonly />
|
||||
<p class="description">
|
||||
The cookies stored are not used by us for any malicious intend or for
|
||||
tracking you in any way.
|
||||
</p>
|
||||
</div>
|
31
public/templates/engines_tab.html
Normal file
31
public/templates/engines_tab.html
Normal file
@ -0,0 +1,31 @@
|
||||
<div class="engines tab">
|
||||
<h3>select search engines</h3>
|
||||
<p class="description">
|
||||
Select the search engines from the list of engines that you want results
|
||||
from
|
||||
</p>
|
||||
<div class="engine_selection">
|
||||
<div class="toggle_btn">
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="select_all" onchange="toggleAllSelection()" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
Select All
|
||||
</div>
|
||||
<hr />
|
||||
<div class="toggle_btn">
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="engine" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
Duckduckgo
|
||||
</div>
|
||||
<div class="toggle_btn">
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="engine" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
Searx
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -10,6 +10,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="static/settings.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
4
public/templates/general_tab.html
Normal file
4
public/templates/general_tab.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="general tab active">
|
||||
<h1>General</h1>
|
||||
<p class="description">Coming soon!!</p>
|
||||
</div>
|
@ -1,12 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<title>Websurfx</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link href="static/colorschemes/{{colorscheme}}.css" rel="stylesheet" type="text/css" />
|
||||
<link href="static/themes/{{theme}}.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body onload="getClientSettings()">
|
||||
<header>{{>navbar}}</header>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="about">about</a></li>
|
||||
<li><a href="settings">settings</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="about">about</a></li>
|
||||
<li><a href="settings">settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
@ -1,5 +1,22 @@
|
||||
{{>header this}}
|
||||
<main class="settings">
|
||||
<h1>Page is under construction</h1>
|
||||
<main class="settings" >
|
||||
<h1>Settings</h1>
|
||||
<hr />
|
||||
<div class="settings_container">
|
||||
<div class="sidebar">
|
||||
<div class="btn active" onclick="setActiveTab(this)">general</div>
|
||||
<div class="btn" onclick="setActiveTab(this)">user interface</div>
|
||||
<div class="btn" onclick="setActiveTab(this)">engines</div>
|
||||
<div class="btn" onclick="setActiveTab(this)">cookies</div>
|
||||
</div>
|
||||
<div class="main_container">
|
||||
{{> general_tab}} {{> user_interface_tab}} {{> engines_tab}} {{>
|
||||
cookies_tab}}
|
||||
<p class="message"></p>
|
||||
<button type="submit" onclick="setClientSettings()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="static/settings.js"></script>
|
||||
<script src="static/cookies.js"></script>
|
||||
{{>footer}}
|
||||
|
27
public/templates/user_interface_tab.html
Normal file
27
public/templates/user_interface_tab.html
Normal file
@ -0,0 +1,27 @@
|
||||
<div class="user_interface tab">
|
||||
<h3>select theme</h3>
|
||||
<p class="description">
|
||||
Select the theme from the available themes to be used in user interface
|
||||
</p>
|
||||
<select name="themes">
|
||||
<option value="simple">simple</option>
|
||||
</select>
|
||||
<h3>select color scheme</h3>
|
||||
<p class="description">
|
||||
Select the color scheme for your theme to be used in user interface
|
||||
</p>
|
||||
<select name="colorschemes">
|
||||
<option value="catppuccin-mocha">catppuccin mocha</option>
|
||||
<option value="dark-chocolate">dark chocolate</option>
|
||||
<option value="dracula">dracula</option>
|
||||
<option value="gruvbox-dark">gruvbox dark</option>
|
||||
<option value="monokai">monokai</option>
|
||||
<option value="nord">nord</option>
|
||||
<option value="oceanic-next">oceanic next</option>
|
||||
<option value="one-dark">one dark</option>
|
||||
<option value="solarized-dark">solarized dark</option>
|
||||
<option value="solarized-light">solarized light</option>
|
||||
<option value="tokyo-night">tokyo night</option>
|
||||
<option value="tomorrow-night">tomorrow night</option>
|
||||
</select>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user