mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: tweak setting page
This commit is contained in:
@ -56,15 +56,15 @@ const AccessTokenSection = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-8 w-full flex flex-col justify-start items-start space-y-4">
|
||||
<div className="mt-4 w-full flex flex-col justify-start items-start space-y-4">
|
||||
<div className="w-full">
|
||||
<div className="sm:flex sm:items-center sm:justify-between">
|
||||
<div className="sm:flex-auto space-y-1">
|
||||
<p className="flex flex-row justify-start items-center font-medium text-gray-700 dark:text-gray-300">
|
||||
<p className="flex flex-row justify-start items-center font-medium text-gray-700 dark:text-gray-400">
|
||||
Access Tokens
|
||||
<LearnMore className="ml-2" url="https://usememos.com/docs/security/access-tokens" />
|
||||
</p>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-400">A list of all access tokens for your account.</p>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-500">A list of all access tokens for your account.</p>
|
||||
</div>
|
||||
<div className="mt-4 sm:mt-0">
|
||||
<Button
|
||||
@ -81,19 +81,19 @@ const AccessTokenSection = () => {
|
||||
<div className="mt-2 flow-root">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="inline-block min-w-full py-2 align-middle">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-gray-400">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-zinc-600">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
Token
|
||||
</th>
|
||||
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
<th scope="col" className="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
Description
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
Created At
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
Expires At
|
||||
</th>
|
||||
<th scope="col" className="relative py-3.5 pl-3 pr-4">
|
||||
@ -101,10 +101,10 @@ const AccessTokenSection = () => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-500">
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-zinc-700">
|
||||
{userAccessTokens.map((userAccessToken) => (
|
||||
<tr key={userAccessToken.accessToken}>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
|
||||
<span className="font-mono">{getFormatedAccessToken(userAccessToken.accessToken)}</span>
|
||||
<IconButton
|
||||
color="neutral"
|
||||
@ -115,16 +115,16 @@ const AccessTokenSection = () => {
|
||||
<Icon.Clipboard className="w-4 h-auto text-gray-400" />
|
||||
</IconButton>
|
||||
</td>
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-400">
|
||||
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-400">
|
||||
{userAccessToken.description}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{userAccessToken.issuedAt?.toLocaleString()}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{userAccessToken.expiresAt?.toLocaleString() ?? "Never"}
|
||||
</td>
|
||||
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm">
|
||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
|
||||
<IconButton
|
||||
color="danger"
|
||||
variant="plain"
|
||||
|
@ -121,8 +121,8 @@ const MemberSection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section-container member-section-container">
|
||||
<p className="title-text">{t("setting.member-section.create-a-member")}</p>
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.member-section.create-a-member")}</p>
|
||||
<div className="w-full flex flex-col justify-start items-start gap-2">
|
||||
<div className="flex flex-col justify-start items-start gap-1">
|
||||
<span className="text-sm">{t("common.username")}</span>
|
||||
@ -140,10 +140,10 @@ const MemberSection = () => {
|
||||
<div className="title-text">{t("setting.member-list")}</div>
|
||||
</div>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="inline-block min-w-full align-middle border rounded-lg dark:border-gray-500">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-gray-500">
|
||||
<div className="inline-block min-w-full align-middle border rounded-lg dark:border-zinc-600">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-zinc-600">
|
||||
<thead>
|
||||
<tr className="text-sm font-semibold text-left text-gray-900 dark:text-gray-300">
|
||||
<tr className="text-sm font-semibold text-left text-gray-900 dark:text-gray-400">
|
||||
<th scope="col" className="py-2 pl-4 pr-3">
|
||||
ID
|
||||
</th>
|
||||
@ -159,16 +159,16 @@ const MemberSection = () => {
|
||||
<th scope="col" className="relative py-2 pl-3 pr-4"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-500">
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-zinc-600">
|
||||
{userList.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-300">{user.id}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">
|
||||
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-400">{user.id}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{user.username}
|
||||
<span className="ml-1 italic">{user.rowStatus === RowStatus.ARCHIVED && "(Archived)"}</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.nickname}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.email}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">{user.nickname}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">{user.email}</td>
|
||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end">
|
||||
{currentUser?.id === user.id ? (
|
||||
<span>{t("common.yourself")}</span>
|
||||
|
@ -11,28 +11,26 @@ const MyAccountSection = () => {
|
||||
const user = useCurrentUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section-container account-section-container">
|
||||
<p className="font-medium my-2 text-gray-700 dark:text-gray-400">{t("setting.account-section.title")}</p>
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<UserAvatar className="mr-2 w-14 h-14" avatarUrl={user.avatarUrl} />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<span className="text-2xl font-medium">{user.nickname}</span>
|
||||
<span className="-mt-2 text-base text-gray-500 dark:text-gray-400">({user.username})</span>
|
||||
</div>
|
||||
<div className="w-full gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.account-section.title")}</p>
|
||||
<div className="mt-1 flex flex-row justify-start items-center">
|
||||
<UserAvatar className="mr-2" avatarUrl={user.avatarUrl} />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<span className="text-2xl font-medium">{user.nickname}</span>
|
||||
<span className="-mt-2 text-base text-gray-500 dark:text-gray-400">({user.username})</span>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-start items-center mt-4 space-x-2">
|
||||
<Button variant="outlined" onClick={showUpdateAccountDialog}>
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={showChangePasswordDialog}>
|
||||
{t("setting.account-section.change-password")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AccessTokenSection />
|
||||
</div>
|
||||
</>
|
||||
<div className="w-full flex flex-row justify-start items-center mt-2 space-x-2">
|
||||
<Button variant="outlined" onClick={showUpdateAccountDialog}>
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={showChangePasswordDialog}>
|
||||
{t("setting.account-section.change-password")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AccessTokenSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,6 @@ import Icon from "../Icon";
|
||||
import LocaleSelect from "../LocaleSelect";
|
||||
import VisibilityIcon from "../VisibilityIcon";
|
||||
import WebhookSection from "./WebhookSection";
|
||||
import "@/less/settings/preferences-section.less";
|
||||
|
||||
const PreferencesSection = () => {
|
||||
const t = useTranslate();
|
||||
@ -72,19 +71,19 @@ const PreferencesSection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section-container preferences-section-container">
|
||||
<p className="title-text">{t("common.basic")}</p>
|
||||
<div className="form-label selector">
|
||||
<span className="text-sm">{t("common.language")}</span>
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("common.basic")}</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("common.language")}</span>
|
||||
<LocaleSelect value={locale} onChange={handleLocaleSelectChange} />
|
||||
</div>
|
||||
<div className="form-label selector">
|
||||
<span className="text-sm">{t("setting.preference-section.theme")}</span>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.preference-section.theme")}</span>
|
||||
<AppearanceSelect value={appearance} onChange={handleAppearanceSelectChange} />
|
||||
</div>
|
||||
<p className="title-text">{t("setting.preference")}</p>
|
||||
<div className="form-label selector">
|
||||
<span className="text-sm break-keep text-ellipsis overflow-hidden">{t("setting.preference-section.default-memo-visibility")}</span>
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.preference")}</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
|
||||
<Select
|
||||
className="!min-w-fit"
|
||||
value={setting.memoVisibility}
|
||||
@ -110,7 +109,7 @@ const PreferencesSection = () => {
|
||||
<div className="w-full flex flex-col justify-start items-start">
|
||||
<div className="mb-2 w-full flex flex-row justify-between items-center">
|
||||
<div className="w-auto flex items-center">
|
||||
<span className="text-sm mr-1">{t("setting.preference-section.telegram-user-id")}</span>
|
||||
<span className="mr-1">{t("setting.preference-section.telegram-user-id")}</span>
|
||||
</div>
|
||||
<Button variant="outlined" color="neutral" onClick={handleSaveTelegramUserId}>
|
||||
{t("common.save")}
|
||||
|
@ -57,10 +57,10 @@ const SSOSection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section-container">
|
||||
<div className="mb-2 w-full flex flex-row justify-between items-center gap-1">
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<div className="w-full flex flex-row justify-between items-center gap-1">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<span className="font-mono text-sm text-gray-400">{t("setting.sso-section.sso-list")}</span>
|
||||
<span className="font-mono text-gray-400">{t("setting.sso-section.sso-list")}</span>
|
||||
<LearnMore url="https://usememos.com/docs/advanced-settings/keycloak" />
|
||||
</div>
|
||||
<Button onClick={() => showCreateIdentityProviderDialog(undefined, fetchIdentityProviderList)}>{t("common.create")}</Button>
|
||||
@ -100,8 +100,13 @@ const SSOSection = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{identityProviderList.length === 0 && (
|
||||
<div className="w-full mt-2 text-sm dark:border-zinc-700 opacity-60 flex flex-row items-center justify-between">
|
||||
<p className="">No SSO found.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full mt-8">
|
||||
<div className="w-full mt-4">
|
||||
<p className="text-sm">{t("common.learn-more")}:</p>
|
||||
<List component="ul" marker="disc" size="sm">
|
||||
<ListItem>
|
||||
|
@ -60,9 +60,9 @@ const StorageSection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section-container">
|
||||
<div className="mt-4 mb-2 w-full flex flex-row justify-start items-center">
|
||||
<span className="font-mono text-sm text-gray-400 mr-2">{t("setting.storage-section.current-storage")}</span>
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<div className="w-full flex flex-row justify-start items-center">
|
||||
<span className="font-mono text-sm text-gray-400 mr-2 dark:text-gray-500">{t("setting.storage-section.current-storage")}</span>
|
||||
</div>
|
||||
<RadioGroup
|
||||
className="w-full"
|
||||
@ -82,7 +82,7 @@ const StorageSection = () => {
|
||||
<Radio key={storage.id} value={storage.id} label={storage.name} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
<Divider className="!my-4" />
|
||||
<Divider className="!my-2" />
|
||||
<div className="mb-2 w-full flex flex-row justify-between items-center gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-mono text-sm text-gray-400">{t("setting.storage-section.storage-services")}</span>
|
||||
@ -90,7 +90,7 @@ const StorageSection = () => {
|
||||
</div>
|
||||
<Button onClick={() => showCreateStorageServiceDialog(undefined, fetchStorageList)}>{t("common.create")}</Button>
|
||||
</div>
|
||||
<div className="mt-2 w-full flex flex-col">
|
||||
<div className="w-full flex flex-col">
|
||||
{storageList.map((storage) => (
|
||||
<div
|
||||
key={storage.id}
|
||||
@ -123,7 +123,7 @@ const StorageSection = () => {
|
||||
</div>
|
||||
))}
|
||||
{storageList.length === 0 && (
|
||||
<div className="pb-2 w-full text-sm dark:border-zinc-700 opacity-60 flex flex-row items-center justify-between">
|
||||
<div className="w-full text-sm dark:border-zinc-700 opacity-60 flex flex-row items-center justify-between">
|
||||
<p className="">No storage service found.</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -10,7 +10,6 @@ import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import showDisablePasswordLoginDialog from "../DisablePasswordLoginDialog";
|
||||
import Icon from "../Icon";
|
||||
import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
|
||||
import "@/less/settings/system-section.less";
|
||||
|
||||
interface State {
|
||||
dbSize: number;
|
||||
@ -244,38 +243,38 @@ const SystemSection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="section-container system-section-container">
|
||||
<p className="title-text">{t("common.basic")}</p>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("common.basic")}</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="normal-text">
|
||||
{t("setting.system-section.server-name")}: <span className="font-mono font-bold">{systemStatus.customizedProfile.name}</span>
|
||||
</div>
|
||||
<Button onClick={handleUpdateCustomizedProfileButtonClick}>{t("common.edit")}</Button>
|
||||
</div>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="text-sm">
|
||||
{t("setting.system-section.database-file-size")}: <span className="font-mono font-bold">{formatBytes(state.dbSize)}</span>
|
||||
</span>
|
||||
<Button onClick={handleVacuumBtnClick}>{t("common.vacuum")}</Button>
|
||||
</div>
|
||||
<p className="title-text">{t("common.settings")}</p>
|
||||
<div className="form-label">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("common.settings")}</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="normal-text">{t("setting.system-section.allow-user-signup")}</span>
|
||||
<Switch checked={state.allowSignUp} onChange={(event) => handleAllowSignUpChanged(event.target.checked)} />
|
||||
</div>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="normal-text">{t("setting.system-section.disable-password-login")}</span>
|
||||
<Switch checked={state.disablePasswordLogin} onChange={(event) => handleDisablePasswordLoginChanged(event.target.checked)} />
|
||||
</div>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="normal-text">{t("setting.system-section.disable-public-memos")}</span>
|
||||
<Switch checked={state.disablePublicMemos} onChange={(event) => handleDisablePublicMemosChanged(event.target.checked)} />
|
||||
</div>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="normal-text">{t("setting.system-section.display-with-updated-time")}</span>
|
||||
<Switch checked={state.memoDisplayWithUpdatedTs} onChange={(event) => handleMemoDisplayWithUpdatedTs(event.target.checked)} />
|
||||
</div>
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-sm mr-1">{t("setting.system-section.max-upload-size")}</span>
|
||||
<Tooltip title={t("setting.system-section.max-upload-size-hint")} placement="top">
|
||||
@ -293,7 +292,7 @@ const SystemSection = () => {
|
||||
/>
|
||||
</div>
|
||||
<Divider className="!mt-3 !my-4" />
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="w-auto flex items-center">
|
||||
<span className="text-sm mr-1">Instance URL</span>
|
||||
@ -324,7 +323,7 @@ const SystemSection = () => {
|
||||
</Link>
|
||||
</div>
|
||||
<Divider className="!mt-3 !my-4" />
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="w-auto flex items-center">
|
||||
<span className="text-sm mr-1">{t("setting.system-section.telegram-bot-token")}</span>
|
||||
@ -355,7 +354,7 @@ const SystemSection = () => {
|
||||
</Link>
|
||||
</div>
|
||||
<Divider className="!mt-3 !my-4" />
|
||||
<div className="form-label">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="normal-text">{t("setting.system-section.additional-style")}</span>
|
||||
<Button variant="outlined" color="neutral" onClick={handleSaveAdditionalStyle}>
|
||||
{t("common.save")}
|
||||
@ -373,7 +372,7 @@ const SystemSection = () => {
|
||||
value={state.additionalStyle}
|
||||
onChange={(event) => handleAdditionalStyleChanged(event.target.value)}
|
||||
/>
|
||||
<div className="form-label mt-2">
|
||||
<div className="w-full flex flex-row justify-between items-center mt-2">
|
||||
<span className="normal-text">{t("setting.system-section.additional-script")}</span>
|
||||
<Button variant="outlined" color="neutral" onClick={handleSaveAdditionalScript}>
|
||||
{t("common.save")}
|
||||
|
@ -49,7 +49,7 @@ const WebhookSection = () => {
|
||||
<div className="w-full flex flex-col justify-start items-start">
|
||||
<div className="w-full flex justify-between items-center">
|
||||
<div className="flex-auto space-y-1">
|
||||
<p className="flex flex-row justify-start items-center font-medium text-gray-700 dark:text-gray-300">Webhooks</p>
|
||||
<p className="flex flex-row justify-start items-center font-medium text-gray-700 dark:text-gray-400">Webhooks</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
@ -65,8 +65,8 @@ const WebhookSection = () => {
|
||||
</div>
|
||||
<div className="w-full mt-2 flow-root">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="inline-block min-w-full border rounded-lg align-middle dark:border-gray-500">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-gray-500">
|
||||
<div className="inline-block min-w-full border rounded-lg align-middle dark:border-zinc-600">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-zinc-600">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
|
Reference in New Issue
Block a user