mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Move to reuse existing routing
This commit is contained in:
@@ -80,7 +80,7 @@
|
||||
title: 'Switch Organization',
|
||||
items: organizations.map((organization) => ({
|
||||
name: organization.name,
|
||||
href: `/console/organization-${organization?.$id}`
|
||||
href: `${base}/organization-${organization?.$id}`
|
||||
}))
|
||||
},
|
||||
bottom: {
|
||||
@@ -101,7 +101,7 @@
|
||||
items: [
|
||||
{
|
||||
name: 'Organization overview',
|
||||
href: `/console/organization-${selectedOrg?.$id}`
|
||||
href: `${base}/organization-${selectedOrg?.$id}`
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -139,12 +139,12 @@
|
||||
if (index < 4) {
|
||||
return {
|
||||
name: project.name,
|
||||
href: `/console/project-${project.$id}/overview`
|
||||
href: `${base}/project-${project.$id}/overview`
|
||||
};
|
||||
} else if (index === 4) {
|
||||
return {
|
||||
name: 'All projects',
|
||||
href: `/console/organization-${selectedOrg?.$id}`
|
||||
href: `${base}/organization-${selectedOrg?.$id}`
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -156,7 +156,7 @@
|
||||
{
|
||||
name: 'Create project',
|
||||
trailingIcon: IconPlus,
|
||||
href: `/console/organization-${selectedOrg?.$id}?create-project`
|
||||
href: `${base}/organization-${selectedOrg?.$id}?create-project`
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -167,7 +167,7 @@
|
||||
{
|
||||
name: 'Create project',
|
||||
trailingIcon: IconPlus,
|
||||
href: `/console/organization-${selectedOrg?.$id}?create-project`
|
||||
href: `${base}/organization-${selectedOrg?.$id}?create-project`
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -221,7 +221,7 @@
|
||||
{#if selectedOrg}
|
||||
<div use:melt={$itemOrganizations}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor href={`/console/organization-${selectedOrg?.$id}`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${selectedOrg?.$id}`}
|
||||
>Organization overview</ActionMenu.Item.Anchor
|
||||
></ActionMenu.Root>
|
||||
</div>
|
||||
@@ -241,7 +241,7 @@
|
||||
<div use:melt={$itemOrganizations}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor
|
||||
href={`/console/organization-${organization?.$id}`}
|
||||
href={`${base}/organization-${organization?.$id}`}
|
||||
>{organization.name}</ActionMenu.Item.Anchor>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@
|
||||
<div use:melt={$itemOrganizations}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor
|
||||
href={`/console/organization-${organization?.$id}`}
|
||||
href={`${base}/organization-${organization?.$id}`}
|
||||
>{organization.name}</ActionMenu.Item.Anchor
|
||||
></ActionMenu.Root>
|
||||
</div>
|
||||
@@ -317,7 +317,7 @@
|
||||
{#if index < 4}
|
||||
<div use:melt={$itemProjects}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor href={`/console/project-${project.$id}`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/project-${project.$id}`}
|
||||
>{project.name}</ActionMenu.Item.Anchor
|
||||
></ActionMenu.Root>
|
||||
</div>
|
||||
@@ -325,7 +325,7 @@
|
||||
<div use:melt={$itemProjects}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor
|
||||
href={`/console/organization-${selectedOrg.$id}`}
|
||||
href={`${base}/organization-${selectedOrg.$id}`}
|
||||
>All projects</ActionMenu.Item.Anchor
|
||||
></ActionMenu.Root>
|
||||
</div>
|
||||
@@ -337,7 +337,7 @@
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Anchor
|
||||
leadingIcon={IconPlusSm}
|
||||
href={`/console/organization-${selectedOrg?.$id}?create-project`}
|
||||
href={`${base}/organization-${selectedOrg?.$id}?create-project`}
|
||||
>Create project</ActionMenu.Item.Anchor
|
||||
></ActionMenu.Root>
|
||||
</div>
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
<Typography.Text color="--fgcolor-neutral-tertiary">{organization.name}</Typography.Text>
|
||||
|
||||
<Layout.Stack gap="xs">
|
||||
<ActionMenu.Item.Anchor href={`${base}/org-${organization.$id}`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${organization.$id}`}
|
||||
>Projects</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor href={`${base}/org-${organization.$id}/members`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${organization.$id}/members`}
|
||||
>Members</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor href={`${base}/org-${organization.$id}/usage`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${organization.$id}/usage`}
|
||||
>Usage</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor href={`${base}/org-${organization.$id}/billing`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${organization.$id}/billing`}
|
||||
>Billing</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor href={`${base}/org-${organization.$id}/settings`}
|
||||
<ActionMenu.Item.Anchor href={`${base}/organization-${organization.$id}/settings`}
|
||||
>Settings</ActionMenu.Item.Anchor>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { isStudio } from '$lib/system';
|
||||
export let overlapCover = false;
|
||||
export let size: 'small' | 'medium' | 'large' | 'xl' | 'xxl' | 'xxxl' = null;
|
||||
|
||||
@@ -8,13 +9,17 @@
|
||||
: '';
|
||||
</script>
|
||||
|
||||
<div style:container-type="inline-size" class:overlap-cover={overlapCover}>
|
||||
<div class="console-container" {style}>
|
||||
<Layout.Stack gap="l">
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
{#if isStudio}
|
||||
<slot />
|
||||
{:else}
|
||||
<div style:container-type="inline-size" class:overlap-cover={overlapCover}>
|
||||
<div class="console-container" {style}>
|
||||
<Layout.Stack gap="l">
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlap-cover {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { AvatarInitials, Breadcrumbs } from '$lib/components/index.js';
|
||||
import { Avatar, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { organization, organizationList } from '$lib/stores/organization';
|
||||
import { user } from '$lib/stores/user';
|
||||
import SidebarProject from '$lib/components/studio/sidebarProject.svelte';
|
||||
import SidebarOrganization from '$lib/components/studio/sidebarOrganization.svelte';
|
||||
import { BillingPlan } from '$lib/constants.js';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { tierToPlan } from '$lib/stores/billing.js';
|
||||
import type { NavbarProject } from '$lib/components/navbar.svelte';
|
||||
|
||||
$: hasProjectSidebar = $page.url.pathname.startsWith('/studio/proj');
|
||||
|
||||
export let loadedProjects: Array<NavbarProject> = [];
|
||||
|
||||
$: organizations = $organizationList.teams.map((org) => {
|
||||
const billingPlan = org['billingPlan'];
|
||||
return {
|
||||
name: org.name,
|
||||
$id: org.$id,
|
||||
showUpgrade: billingPlan === BillingPlan.FREE,
|
||||
tierName: isCloud ? (tierToPlan(billingPlan)?.name ?? '') : null,
|
||||
isSelected: $page.data.organization?.$id === org.$id,
|
||||
projects: loadedProjects
|
||||
};
|
||||
});
|
||||
|
||||
$: console.log(organizations);
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Layout.Stack>
|
||||
<header>
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Breadcrumbs {organizations} />
|
||||
<AvatarInitials name={$user?.name ?? ''} size="s" />
|
||||
</Layout.Stack>
|
||||
</header>
|
||||
<div class="studio-content" class:project-sidebar={hasProjectSidebar}><slot /></div>
|
||||
{#if hasProjectSidebar}
|
||||
<SidebarProject />
|
||||
{:else}
|
||||
<SidebarOrganization organization={$page.data.organization} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
main {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bgcolor-neutral-default);
|
||||
}
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.studio-content {
|
||||
min-height: calc(100vh - 48px);
|
||||
margin-top: 48px;
|
||||
width: calc(100vw - 200px);
|
||||
margin-left: 200px;
|
||||
padding-right: var(--space-4);
|
||||
|
||||
&.project-sidebar {
|
||||
width: calc(100vw - 52px);
|
||||
margin-left: 52px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@
|
||||
import { BillingPlan, INTERVAL } from '$lib/constants';
|
||||
import Footer from '$lib/layout/footer.svelte';
|
||||
import Shell from '$lib/layout/shell.svelte';
|
||||
import ShellStudio from '$lib/layout/shellStudio.svelte';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { newOrgModal, organization, type Organization } from '$lib/stores/organization';
|
||||
import { database, checkForDatabaseBackupPolicies } from '$lib/stores/database';
|
||||
@@ -32,7 +33,7 @@
|
||||
import { openMigrationWizard } from './(migration-wizard)';
|
||||
import { project } from './project-[project]/store';
|
||||
import { feedback } from '$lib/stores/feedback';
|
||||
import { hasStripePublicKey, isCloud, VARS } from '$lib/system';
|
||||
import { hasStripePublicKey, isCloud, isStudio, VARS } from '$lib/system';
|
||||
import { stripe } from '$lib/stores/stripe';
|
||||
import MobileSupportModal from './wizard/support/mobileSupportModal.svelte';
|
||||
import { showSupportModal } from './wizard/support/store';
|
||||
@@ -329,21 +330,24 @@
|
||||
</script>
|
||||
|
||||
<CommandCenter />
|
||||
<Shell
|
||||
showSideNavigation={$page.url.pathname !== '/console' &&
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account') &&
|
||||
!$page.url.pathname.includes('/console/card') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}
|
||||
showHeader={!$page.url.pathname.includes('/console/onboarding')}
|
||||
showFooter={!$page.url.pathname.includes('/console/onboarding')}
|
||||
bind:loadedProjects
|
||||
bind:projects={data.projects}>
|
||||
<!-- <Header slot="header" />-->
|
||||
<slot />
|
||||
<Footer slot="footer" />
|
||||
</Shell>
|
||||
|
||||
{#if isStudio}
|
||||
<ShellStudio bind:loadedProjects><slot /></ShellStudio>
|
||||
{:else}
|
||||
<Shell
|
||||
showSideNavigation={$page.url.pathname !== '/console' &&
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account') &&
|
||||
!$page.url.pathname.includes('/console/card') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}
|
||||
showHeader={!$page.url.pathname.includes('/console/onboarding')}
|
||||
showFooter={!$page.url.pathname.includes('/console/onboarding')}
|
||||
bind:loadedProjects
|
||||
bind:projects={data.projects}>
|
||||
<!-- <Header slot="header" />-->
|
||||
<slot />
|
||||
<Footer slot="footer" />
|
||||
</Shell>
|
||||
{/if}
|
||||
{#if $wizard.show && $wizard.component}
|
||||
<svelte:component this={$wizard.component} {...$wizard.props} />
|
||||
{:else if $wizard.cover}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Container } from '$lib/layout';
|
||||
import CreateProject from './createProject.svelte';
|
||||
import CreateOrganization from '../createOrganization.svelte';
|
||||
import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system';
|
||||
import { GRACE_PERIOD_OVERRIDE, isCloud, isStudio } from '$lib/system';
|
||||
import { page } from '$app/stores';
|
||||
import { registerCommands } from '$lib/commandCenter';
|
||||
import {
|
||||
@@ -28,7 +28,7 @@
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { Badge, Icon, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Badge, Divider, Icon, Typography, Card, Layout } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
@@ -48,6 +48,7 @@
|
||||
let showCreateProjectCloud = false;
|
||||
|
||||
function allServiceDisabled(project: Models.Project): boolean {
|
||||
return false;
|
||||
let disabled = true;
|
||||
services.load(project);
|
||||
$services.list.forEach((service) => {
|
||||
@@ -142,94 +143,124 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Typography.Title>Projects</Typography.Title>
|
||||
{#if isStudio}
|
||||
<Card.Base>
|
||||
<Layout.Stack>
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-secondary"
|
||||
>Projects</Typography.Text>
|
||||
<Button size="s">Create project</Button>
|
||||
</Layout.Stack>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end">
|
||||
{#if $canWriteProjects}
|
||||
<Button
|
||||
on:click={handleCreateProject}
|
||||
event="create_project"
|
||||
disabled={$readOnly && !GRACE_PERIOD_OVERRIDE}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create project
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => (showCreate = true)}>Empty project</DropListItem>
|
||||
<DropListItem on:click={importProject}>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
Import project <span class="tag eyebrow-heading-3">Experimental</span>
|
||||
</div>
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
<Divider />
|
||||
|
||||
{#if data.projects.total}
|
||||
<CardContainer
|
||||
showEmpty={$canWriteProjects}
|
||||
total={data.projects.total}
|
||||
<Layout.Grid gap="l" columnsS={3} columns={4} columnsL={4} columnsXL={5}>
|
||||
<!-- TODO: this now maps over projects, but probably will be more like artifacts (and location might be different-->
|
||||
{#each data.projects.projects as project}
|
||||
<a href={`${base}/project-${project.$id}`}
|
||||
><Card.Media
|
||||
src="https://picsum.photos/260/150"
|
||||
alt=""
|
||||
description={project.description}
|
||||
title={project.name}></Card.Media
|
||||
></a>
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
{:else}
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Typography.Title>Projects</Typography.Title>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end">
|
||||
{#if $canWriteProjects}
|
||||
<Button
|
||||
on:click={handleCreateProject}
|
||||
event="create_project"
|
||||
disabled={$readOnly && !GRACE_PERIOD_OVERRIDE}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create project
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => (showCreate = true)}>Empty project</DropListItem>
|
||||
<DropListItem on:click={importProject}>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
Import project <span class="tag eyebrow-heading-3">Experimental</span>
|
||||
</div>
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
|
||||
{#if data.projects.total}
|
||||
<CardContainer
|
||||
showEmpty={$canWriteProjects}
|
||||
total={data.projects.total}
|
||||
offset={data.offset}
|
||||
on:click={handleCreateProject}>
|
||||
{#each data.projects.projects as project}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
<GridItem1 href={`${base}/project-${project.$id}`}>
|
||||
<svelte:fragment slot="eyebrow">
|
||||
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{#if allServiceDisabled(project)}
|
||||
<p>
|
||||
<span class="icon-pause" aria-hidden="true"></span> All services are
|
||||
disabled.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
{@const icon = getIconForPlatform(platform.icon)}
|
||||
<Badge variant="secondary" content={platform.name}>
|
||||
<Icon {icon} size="s" slot="start" />
|
||||
</Badge>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={`+${project.platforms.length - 3}`} />
|
||||
{/if}
|
||||
<svelte:fragment slot="icons">
|
||||
{#if isCloud && regions}
|
||||
{@const region = findRegion(project)}
|
||||
<span class="u-color-text-gray u-medium u-line-height-2">
|
||||
{region?.name}
|
||||
</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</GridItem1>
|
||||
{/each}
|
||||
<svelte:fragment slot="empty">
|
||||
<p>Create a new project</p>
|
||||
</svelte:fragment>
|
||||
</CardContainer>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
allowCreate={$canWriteProjects}
|
||||
on:click={handleCreateProject}
|
||||
target="project"
|
||||
href="https://appwrite.io/docs/quick-starts"></Empty>
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Projects"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
on:click={handleCreateProject}>
|
||||
{#each data.projects.projects as project}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
<GridItem1 href={`${base}/project-${project.$id}`}>
|
||||
<svelte:fragment slot="eyebrow">
|
||||
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{#if allServiceDisabled(project)}
|
||||
<p>
|
||||
<span class="icon-pause" aria-hidden="true"></span> All services are disabled.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
{@const icon = getIconForPlatform(platform.icon)}
|
||||
<Badge variant="secondary" content={platform.name}>
|
||||
<Icon {icon} size="s" slot="start" />
|
||||
</Badge>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
<Badge variant="secondary" content={`+${project.platforms.length - 3}`} />
|
||||
{/if}
|
||||
<svelte:fragment slot="icons">
|
||||
{#if isCloud && regions}
|
||||
{@const region = findRegion(project)}
|
||||
<span class="u-color-text-gray u-medium u-line-height-2">
|
||||
{region?.name}
|
||||
</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</GridItem1>
|
||||
{/each}
|
||||
<svelte:fragment slot="empty">
|
||||
<p>Create a new project</p>
|
||||
</svelte:fragment>
|
||||
</CardContainer>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
allowCreate={$canWriteProjects}
|
||||
on:click={handleCreateProject}
|
||||
target="project"
|
||||
href="https://appwrite.io/docs/quick-starts"></Empty>
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Projects"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.projects.total} />
|
||||
</Container>
|
||||
total={data.projects.total} />
|
||||
</Container>
|
||||
{/if}
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
<CreateProject bind:show={showCreate} teamId={$page.params.organization} />
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import SidebarProject from '$lib/components/studio/sidebarProject.svelte';
|
||||
|
||||
$: hasProjectSidebar = $page.url.pathname.startsWith('/studio/proj');
|
||||
$: console.log('hasProjectSidebar', hasProjectSidebar);
|
||||
|
||||
$: loadedProjects = $page.data.projects.map((project) => {
|
||||
return {
|
||||
@@ -34,8 +33,6 @@
|
||||
projects: loadedProjects
|
||||
};
|
||||
});
|
||||
|
||||
$: console.log('organizations', organizations);
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
||||
Reference in New Issue
Block a user