Skip to content

Commit

Permalink
✨ Config based UI (#123)
Browse files Browse the repository at this point in the history
* 💄 Handle reviewNone, labeler account display and sticky note more prominent

* 🐛 Change icon for reviewNone

* 💄 Handle user badge in dark mode

* 💄 Show labels with mod service details

* 💄 Color code labels based on blur and hide config

* 💄 Add ModerationLabel in record and repo view

* 🚧 WIP dynamic labeler

* ✨ Cleanup

* ✨ Allow removing external labeler

* 🐛 Fix import

* ✨ Add better details for service config placeholder

* ✨ Show config and tweak UI based on config

* ⬆️ Upgrade @atproto/api version

* ✨ Add permission check for team manager UI
  • Loading branch information
foysalit authored Jun 27, 2024
1 parent c415e58 commit d9c3e8c
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 26 deletions.
1 change: 1 addition & 0 deletions app/actions/ModActionPanel/QuickAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ function Form(
selectedAction={modEventType}
isSubjectDid={isSubjectDid}
hasBlobs={!!record?.blobs?.length}
serverConfig={client.session.serverConfig}
setSelectedAction={(action) => setModEventType(action)}
/>
<ModEventDetailsPopover modEventType={modEventType} />
Expand Down
10 changes: 10 additions & 0 deletions app/communication-template/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Loading, LoadingFailed } from '@/common/Loader'
import { useCommunicationTemplateList } from 'components/communication-template/hooks'
import { CommunicationTemplateDeleteConfirmationModal } from 'components/communication-template/delete-confirmation-modal'
import { ActionButton, LinkButton } from '@/common/buttons'
import client from '@/lib/client'
import { ErrorInfo } from '@/common/ErrorInfo'

export default function CommunicationTemplatePage() {
const { data, error, isLoading } = useCommunicationTemplateList({})
Expand All @@ -22,6 +24,14 @@ export default function CommunicationTemplatePage() {
: []
useTitle(`Communication Templates`)

if (!client.session.serverConfig?.permissions.canManageTemplates) {
return (
<ErrorInfo type="warn">
Sorry, you don{"'"}t have permission to manage communication templates.
</ErrorInfo>
)
}

if (isLoading) {
return <Loading message="Loading templates" />
}
Expand Down
2 changes: 1 addition & 1 deletion components/common/RecordCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import {
AppBskyFeedDefs,
AppBskyActorDefs,
ToolsOzoneModerationDefs,
AppBskyActorDefs,
ComAtprotoLabelDefs,
} from '@atproto/api'
import { buildBlueSkyAppUrl, parseAtUri } from '@/lib/util'
Expand Down
2 changes: 2 additions & 0 deletions components/config/Labeler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useSyncedState } from '@/lib/useSyncedState'
import { isDarkModeEnabled } from '@/common/useColorScheme'
import { Checkbox, Textarea } from '@/common/forms'
import { ExternalLabelerConfig } from './external-labeler'
import { ServerConfig } from './server-config'

const BrowserReactJsonView = dynamic(() => import('react-json-view'), {
ssr: false,
Expand Down Expand Up @@ -41,6 +42,7 @@ export function LabelerConfig({
</div>
)}

<ServerConfig session={session} />
<ExternalLabelerConfig />
</div>
)
Expand Down
7 changes: 5 additions & 2 deletions components/config/Member.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ActionButton } from '@/common/buttons'
import client from '@/lib/client'
import { ToolsOzoneTeamDefs } from '@atproto/api'
import { PlusIcon } from '@heroicons/react/24/outline'
import { MemberEditor } from 'components/team/MemberEditor'
Expand All @@ -18,14 +19,15 @@ export function MemberConfig() {
setShowMemberCreateForm(false)
}
}
const canManageTeam = client.session?.serverConfig

return (
<div className="pt-4">
<div className="flex flex-row justify-between mb-4">
<h4 className="font-medium text-gray-700 dark:text-gray-100">
Manage Members
</h4>
{!showMemberCreateForm && !editingMember && (
{!showMemberCreateForm && !editingMember && canManageTeam && (
<ActionButton
size="sm"
appearance="primary"
Expand All @@ -36,7 +38,7 @@ export function MemberConfig() {
</ActionButton>
)}
</div>
{(showMemberCreateForm || !!editingMember) && (
{(showMemberCreateForm || !!editingMember) && canManageTeam && (
<MemberEditor
member={editingMember}
onCancel={hideEditorForm}
Expand All @@ -49,6 +51,7 @@ export function MemberConfig() {
fetchNextPage,
isInitialLoading,
onEdit: setEditingMember,
canEdit: canManageTeam,
members: data?.pages.map((page) => page.members).flat(),
}}
/>
Expand Down
3 changes: 1 addition & 2 deletions components/config/external-labeler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const ExternalLabelerConfig = () => {
useEffect(() => {
setLabelers(getExternalLabelers())
}, [])

const alreadySubscribed = !!labelerDetails.find(([d]) => d === did)

return (
Expand Down Expand Up @@ -141,7 +140,7 @@ export const ExternalLabelerConfig = () => {
setLabelers(labelers)
}}
>
<span className="text-sm">Unsubscribe</span>
<span className="text-sm sm:text-base">Unsubscribe</span>
</ActionButton>
)}
</div>
Expand Down
159 changes: 159 additions & 0 deletions components/config/server-config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { ActionButton } from '@/common/buttons'
import { Card } from '@/common/Card'
import { CopyButton } from '@/common/CopyButton'
import client, { ClientSession } from '@/lib/client'
import {
CheckCircleIcon,
XCircleIcon,
LinkIcon,
GlobeAltIcon,
CloudIcon,
ChatBubbleLeftIcon,
} from '@heroicons/react/24/solid'
import { useState } from 'react'

const RefetchConfigButton = () => {
const [isRefetching, setIsRefetching] = useState(false)
return (
<ActionButton
onClick={async () => {
setIsRefetching(true)
await client.refetchServerConfig()
setIsRefetching(false)
window.location.reload()
}}
size="xs"
disabled={isRefetching}
appearance="outlined"
>
{isRefetching ? 'Refetching...' : 'Refetch'}
</ActionButton>
)
}

export const ServerConfig = ({ session }: { session: ClientSession }) => {
const config = session.serverConfig
if (!config) {
return (
<div className="flex flex-row justify-between my-4">
<h4 className="font-medium text-gray-700 dark:text-gray-100">
No Server Config Found
</h4>
<RefetchConfigButton />
</div>
)
}

return (
<>
<div className="flex flex-row justify-between my-4">
<h4 className="font-medium text-gray-700 dark:text-gray-100">
Server Config
</h4>
<RefetchConfigButton />
</div>
<Card className="mb-4 pb-4">
<div className="p-2">
{config.pds && <UrlDisplay label="PDS" url={config.pds} />}
{config.appview && (
<UrlDisplay label="App View" url={config.appview} />
)}
{config.blobDivert && (
<UrlDisplay label="Blob Divert" url={config.blobDivert} />
)}
{config.chat && <UrlDisplay label="Chat" url={config.chat} />}
<div className="mt-4">
<h3 className="font-semibold">Permissions</h3>
<ul className="mt-2 list-disc list-inside text-gray-900 dark:text-gray-100">
{' '}
<PermissionItem
label="Manage Templates"
enabled={config.permissions.canManageTemplates}
/>
<PermissionItem
label="Takedown"
enabled={config.permissions.canTakedown}
/>
<PermissionItem
label="Takedown Feed Generators"
enabled={config.permissions.canTakedownFeedGenerators}
/>
<PermissionItem
label="Label"
enabled={config.permissions.canLabel}
/>
<PermissionItem
label="Manage Chat"
enabled={config.permissions.canManageChat}
/>
<PermissionItem
label="Manage Team"
enabled={config.permissions.canManageTeam}
/>
</ul>
</div>
</div>
</Card>
</>
)
}

type PermissionItemProps = {
label: string
enabled: boolean
}

const PermissionItem: React.FC<PermissionItemProps> = ({ label, enabled }) => {
return (
<li className="flex items-center">
{enabled ? (
<CheckCircleIcon className="h-4 w-4 text-green-600" />
) : (
<XCircleIcon className="h-4 w-4 text-red-600" />
)}
<span className="ml-2">{label}</span>
</li>
)
}

type UrlDisplayProps = {
label: 'App View' | 'PDS' | 'Blob Divert' | 'Chat'
url?: string
}

const getIcon = (label: UrlDisplayProps['label'] | string) => {
const classNames = 'h-5 w-5 text-gray-800 dark:text-gray-300'
switch (label) {
case 'PDS':
return <LinkIcon className={classNames} />
case 'App View':
return <GlobeAltIcon className={classNames} />
case 'Blob Divert':
return <CloudIcon className={classNames} />
case 'Chat':
return <ChatBubbleLeftIcon className={classNames} />
default:
return <LinkIcon className={classNames} />
}
}

const UrlDisplay: React.FC<UrlDisplayProps> = ({ label, url }) => {
if (!url) return null
return (
<div className="mb-2 flex items-center">
{getIcon(label)}
<span className="font-medium ml-2 text-gray-900 dark:text-gray-200">
{label}:
</span>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="ml-2 mr-1 text-blue-500 dark:text-blue-400 underline"
>
{url}
</a>
<CopyButton text={url} label={`Copy ${label} URL to clipboard`} />
</div>
)
}
1 change: 0 additions & 1 deletion components/list/Lists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export function Lists({ actor }: { actor: string }) {
getNextPageParam: (lastPage) => lastPage.cursor,
})

console.log(data, hasNextPage)

if (isInitialLoading) {
return (
Expand Down
24 changes: 19 additions & 5 deletions components/mod-event/SelectorButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Dropdown } from '@/common/Dropdown'
import { MOD_EVENTS } from './constants'
import { isReporterMuted, isSubjectMuted } from '@/subject/helpers'
import { DM_DISABLE_TAG } from '@/lib/constants'
import { ServerConfig } from '@/lib/server-config'

const actions = [
{ text: 'Acknowledge', key: MOD_EVENTS.ACKNOWLEDGE },
Expand Down Expand Up @@ -59,15 +60,17 @@ export const ModEventSelectorButton = ({
setSelectedAction,
hasBlobs,
isSubjectDid,
serverConfig,
}: {
subjectStatus?: ToolsOzoneModerationDefs.SubjectStatusView | null
selectedAction: string
setSelectedAction: (action: string) => void
hasBlobs: boolean
isSubjectDid: boolean
serverConfig: ServerConfig
}) => {
const availableActions = useMemo(() => {
return actions.filter(({ key, text }) => {
return actions.filter(({ key }) => {
// Don't show resolve appeal action if subject is not already in appealed status
if (key === MOD_EVENTS.RESOLVE_APPEAL && !subjectStatus?.appealed) {
return false
Expand All @@ -84,11 +87,17 @@ export const ModEventSelectorButton = ({
return false
}
// Don't show divert action if the subject does not have any blobs
if (key === MOD_EVENTS.DIVERT && !hasBlobs) {
if (
key === MOD_EVENTS.DIVERT &&
(!hasBlobs || !serverConfig.blobDivert)
) {
return false
}
// Don't show reverse takedown action if subject is not takendown
if (key === MOD_EVENTS.REVERSE_TAKEDOWN && !subjectStatus?.takendown) {
if (
key === MOD_EVENTS.REVERSE_TAKEDOWN &&
(!subjectStatus?.takendown || !serverConfig.permissions.canTakedown)
) {
return false
}
// Don't show mute action if subject is already muted
Expand Down Expand Up @@ -123,13 +132,17 @@ export const ModEventSelectorButton = ({

if (
key === MOD_EVENTS.DISABLE_DMS &&
(subjectStatus?.tags?.includes(DM_DISABLE_TAG) || !isSubjectDid)
(subjectStatus?.tags?.includes(DM_DISABLE_TAG) ||
!isSubjectDid ||
!serverConfig.permissions.canManageChat)
) {
return false
}
if (
key === MOD_EVENTS.ENABLE_DMS &&
(!subjectStatus?.tags?.includes(DM_DISABLE_TAG) || !isSubjectDid)
(!subjectStatus?.tags?.includes(DM_DISABLE_TAG) ||
!isSubjectDid ||
!serverConfig.permissions.canManageChat)
) {
return false
}
Expand All @@ -145,6 +158,7 @@ export const ModEventSelectorButton = ({
subjectStatus?.tags,
hasBlobs,
isSubjectDid,
serverConfig,
])

return (
Expand Down
5 changes: 4 additions & 1 deletion components/repositories/AccountView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,12 @@ export function AccountView({
views.push(
{ view: Views.Invites, label: 'Invites', sublabel: String(numInvited) },
{ view: Views.Events, label: 'Events' },
{ view: Views.Email, label: 'Email' },
)

if (client.session.serverConfig?.permissions.canSendEmail) {
views.push({ view: Views.Email, label: 'Email' })
}

return views
}

Expand Down
4 changes: 3 additions & 1 deletion components/team/MemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export function MemberList({
fetchNextPage,
hasNextPage,
onEdit,
canEdit = false,
}: {
canEdit: boolean
isInitialLoading: boolean
members: ToolsOzoneTeamDefs.Member[] | undefined
fetchNextPage: () => void
Expand Down Expand Up @@ -101,7 +103,7 @@ export function MemberList({
</div>
</div>
<div>
{!isCurrentMember && (
{!isCurrentMember && canEdit && (
<ActionButton
size="xs"
appearance="outlined"
Expand Down
1 change: 0 additions & 1 deletion cypress/e2e/mod-action/label.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ describe('Mod Action -> Label', () => {
})
cy.fixture('seed.json').then((data) => {
seedFixture = data
console.log(seedFixture)
mockRepoResponse({ statusCode: 200, body: seedFixture.carla.repo })
})

Expand Down
Loading

0 comments on commit d9c3e8c

Please sign in to comment.