Allow organization creation on signup
Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
@@ -24,7 +24,7 @@ posthog.init(process.env.POSTHOG_KEY!, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const OrganizationSelectionPage = lazy(
|
const OrganizationSelectionPage = lazy(
|
||||||
() => import("./pages/OrganizationSelectionPage")
|
() => import("./pages/OrganizationSelectionPage"),
|
||||||
);
|
);
|
||||||
const HomePage = lazy(() => import("./pages/HomePage"));
|
const HomePage = lazy(() => import("./pages/HomePage"));
|
||||||
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||||
@@ -35,14 +35,14 @@ const VendorOverviewPage = lazy(() => import("./pages/VendorOverviewPage"));
|
|||||||
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
|
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
|
||||||
const CreatePeoplePage = lazy(() => import("./pages/CreatePeoplePage"));
|
const CreatePeoplePage = lazy(() => import("./pages/CreatePeoplePage"));
|
||||||
const FrameworkOverviewPage = lazy(
|
const FrameworkOverviewPage = lazy(
|
||||||
() => import("./pages/FrameworkOverviewPage")
|
() => import("./pages/FrameworkOverviewPage"),
|
||||||
);
|
);
|
||||||
const ControlOverviewPage = lazy(() => import("./pages/ControlOverviewPage"));
|
const ControlOverviewPage = lazy(() => import("./pages/ControlOverviewPage"));
|
||||||
const PeopleOverviewPage = lazy(() => import("./pages/PeopleOverviewPage"));
|
const PeopleOverviewPage = lazy(() => import("./pages/PeopleOverviewPage"));
|
||||||
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
||||||
const RegisterPage = lazy(() => import("./pages/RegisterPage"));
|
const RegisterPage = lazy(() => import("./pages/RegisterPage"));
|
||||||
const CreateOrganizationPage = lazy(
|
const CreateOrganizationPage = lazy(
|
||||||
() => import("./pages/CreateOrganizationPage")
|
() => import("./pages/CreateOrganizationPage"),
|
||||||
);
|
);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|||||||
@@ -32,19 +32,25 @@ import {
|
|||||||
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
||||||
import { TeamSwitcher } from "@/components/TeamSwitcher";
|
import { TeamSwitcher } from "@/components/TeamSwitcher";
|
||||||
|
|
||||||
function getNavItems(organizationId: string) {
|
function getNavItems(organizationId?: string) {
|
||||||
|
// Always return the same structure, but with or without URLs depending on whether an organization is selected
|
||||||
return {
|
return {
|
||||||
navMain: [
|
navMain: [
|
||||||
{
|
{
|
||||||
title: "Frameworks",
|
title: "Frameworks",
|
||||||
url: `/organizations/${organizationId}/frameworks`,
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/frameworks`
|
||||||
|
: undefined,
|
||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Organizations",
|
title: "Organizations",
|
||||||
icon: Building,
|
icon: Building,
|
||||||
url: `/organizations/${organizationId}/peoples`,
|
url: organizationId
|
||||||
items: [
|
? `/organizations/${organizationId}/peoples`
|
||||||
|
: undefined,
|
||||||
|
items: organizationId
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
title: "Peoples",
|
title: "Peoples",
|
||||||
url: `/organizations/${organizationId}/peoples`,
|
url: `/organizations/${organizationId}/peoples`,
|
||||||
@@ -55,11 +61,14 @@ function getNavItems(organizationId: string) {
|
|||||||
url: `/organizations/${organizationId}/vendors`,
|
url: `/organizations/${organizationId}/vendors`,
|
||||||
icon: ToyBrick,
|
icon: ToyBrick,
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
|
: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
url: `/organizations/${organizationId}/settings`,
|
url: organizationId
|
||||||
|
? `/organizations/${organizationId}/settings`
|
||||||
|
: undefined,
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -96,7 +105,7 @@ function AppSidebarContent({
|
|||||||
}) {
|
}) {
|
||||||
const { organizationId } = useParams();
|
const { organizationId } = useParams();
|
||||||
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
||||||
const navItems = getNavItems(organizationId!);
|
const navItems = getNavItems(organizationId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="inset" {...props}>
|
<Sidebar variant="inset" {...props}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ChevronRight, type LucideIcon } from "lucide-react";
|
import { ChevronRight, type LucideIcon } from "lucide-react";
|
||||||
import { Link, useLocation } from "react-router";
|
import { Link, useLocation, useParams } from "react-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
@@ -35,15 +35,62 @@ export function NavMain({
|
|||||||
}[];
|
}[];
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
|
const noOrganizationSelected = organizationId === undefined;
|
||||||
|
|
||||||
|
if (noOrganizationSelected) {
|
||||||
|
return (
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>
|
||||||
|
<div className="h-4 w-28 rounded-md bg-gray-200 animate-pulse" />
|
||||||
|
</SidebarGroupLabel>
|
||||||
|
<SidebarMenu className="space-y-1.5">
|
||||||
|
{/* First item - simple item */}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton className="animate-pulse">
|
||||||
|
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||||
|
<div className="h-4 w-32 rounded-md bg-gray-200 ml-2" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
|
||||||
|
{/* Second item - with dropdown */}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton className="animate-pulse">
|
||||||
|
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||||
|
<div className="h-4 w-28 rounded-md bg-gray-200 ml-2" />
|
||||||
|
<div className="ml-auto">
|
||||||
|
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{[1, 2].map((i) => (
|
||||||
|
<SidebarMenuSubItem key={i}>
|
||||||
|
<SidebarMenuSubButton className="animate-pulse pl-8">
|
||||||
|
<div className="h-3 w-20 rounded-md bg-gray-200" />
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
|
||||||
|
{/* Third item - simple item */}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton className="animate-pulse">
|
||||||
|
<div className="h-4 w-4 rounded-md bg-gray-200" />
|
||||||
|
<div className="h-4 w-24 rounded-md bg-gray-200 ml-2" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if an item is active based on the current route
|
|
||||||
const isItemActive = (item: { url?: string; items?: { url: string }[] }) => {
|
const isItemActive = (item: { url?: string; items?: { url: string }[] }) => {
|
||||||
// If the item has a URL and it matches the current path
|
|
||||||
if (item.url && location.pathname.startsWith(item.url)) {
|
if (item.url && location.pathname.startsWith(item.url)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the item has sub-items, check if any of them match the current path
|
|
||||||
if (item.items?.length) {
|
if (item.items?.length) {
|
||||||
return item.items.some((subItem) =>
|
return item.items.some((subItem) =>
|
||||||
location.pathname.startsWith(subItem.url),
|
location.pathname.startsWith(subItem.url),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { type LucideIcon } from "lucide-react";
|
import { type LucideIcon } from "lucide-react";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
@@ -19,17 +20,53 @@ export function NavSecondary({
|
|||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
}[];
|
}[];
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||||
|
const location = useLocation();
|
||||||
|
const noOrganizationSelected = location.pathname === "/";
|
||||||
|
|
||||||
|
if (noOrganizationSelected) {
|
||||||
|
return (
|
||||||
|
<SidebarGroup {...props}>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{[1, 2].map((i) => (
|
||||||
|
<SidebarMenuItem key={i}>
|
||||||
|
<SidebarMenuButton size="sm" className="animate-pulse">
|
||||||
|
<div className="h-3 w-3 rounded-lg bg-gray-200" />
|
||||||
|
<div className="h-3 w-16 rounded-lg bg-gray-200 ml-2" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup {...props}>
|
<SidebarGroup {...props}>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
<SidebarMenuButton asChild size="sm">
|
<SidebarMenuButton
|
||||||
|
asChild={item.url !== "#"}
|
||||||
|
size="sm"
|
||||||
|
tooltip={item.title}
|
||||||
|
className={
|
||||||
|
item.url === "#" ? "opacity-50 cursor-not-allowed" : ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.url !== "#" ? (
|
||||||
<a href={item.url}>
|
<a href={item.url}>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
</a>
|
</a>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useParams } from "react-router";
|
||||||
|
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
@@ -43,12 +43,31 @@ export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
|||||||
const currentUser = useFragment(navUserFragment, viewer);
|
const currentUser = useFragment(navUserFragment, viewer);
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
const noOrganizationSelected = !organizationId;
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout();
|
await logout();
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (noOrganizationSelected) {
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton size="lg" className="animate-pulse">
|
||||||
|
<div className="h-8 w-8 rounded-lg bg-gray-200" />
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<div className="h-4 w-3/4 rounded-lg bg-gray-200" />
|
||||||
|
<div className="h-3 w-1/2 rounded-lg bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto h-4 w-4 rounded-lg bg-gray-200" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
import { ChevronsUpDown, Plus, Building } from "lucide-react";
|
||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { Link, useNavigate, useParams } from "react-router";
|
import { Link, useNavigate, useParams } from "react-router";
|
||||||
|
|
||||||
@@ -67,26 +67,41 @@ export function TeamSwitcher({
|
|||||||
const { organizationId } = useParams();
|
const { organizationId } = useParams();
|
||||||
const [currentOrganization, setCurrentOrganization] =
|
const [currentOrganization, setCurrentOrganization] =
|
||||||
useState<Organization | null>(null);
|
useState<Organization | null>(null);
|
||||||
|
const hasOrganizations =
|
||||||
|
data.organizations && data.organizations.edges.length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.organizations && data.organizations.edges.length > 0) {
|
if (hasOrganizations) {
|
||||||
const org = data.organizations.edges.find(
|
const org = data.organizations.edges.find(
|
||||||
(edge) => edge.node.id === organizationId
|
(edge) => edge.node.id === organizationId,
|
||||||
);
|
);
|
||||||
if (org) {
|
if (org) {
|
||||||
setCurrentOrganization(org.node);
|
setCurrentOrganization(org.node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data.organizations, organizationId]);
|
}, [data.organizations, organizationId, hasOrganizations]);
|
||||||
|
|
||||||
if (!currentOrganization) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOrganizationSwitch = (org: Organization) => {
|
const handleOrganizationSwitch = (org: Organization) => {
|
||||||
navigate(`/organizations/${org.id}`);
|
navigate(`/organizations/${org.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!hasOrganizations) {
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton size="lg" className="animate-pulse">
|
||||||
|
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-gray-200" />
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<div className="h-4 w-3/4 rounded-lg bg-gray-200" />
|
||||||
|
<div className="h-3 w-1/2 rounded-lg bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto h-4 w-4 rounded-lg bg-gray-200" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
@@ -94,18 +109,42 @@ export function TeamSwitcher({
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
size="lg"
|
size="lg"
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
className={`data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground ${
|
||||||
|
!currentOrganization
|
||||||
|
? "border border-dashed border-gray-400"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||||
|
{currentOrganization ? (
|
||||||
<LogoComponent org={currentOrganization} className="size-4" />
|
<LogoComponent org={currentOrganization} className="size-4" />
|
||||||
|
) : (
|
||||||
|
<Building className="size-4 text-gray-400" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-semibold">
|
<span
|
||||||
{currentOrganization.name}
|
className={`truncate font-semibold ${
|
||||||
|
!currentOrganization ? "text-gray-500" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{currentOrganization
|
||||||
|
? currentOrganization.name
|
||||||
|
: "Select Organization"}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`truncate text-xs ${
|
||||||
|
!currentOrganization ? "text-gray-400" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{currentOrganization ? "Free" : "No organization selected"}
|
||||||
</span>
|
</span>
|
||||||
<span className="truncate text-xs">Free</span>
|
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown className="ml-auto" />
|
<ChevronsUpDown
|
||||||
|
className={`ml-auto ${
|
||||||
|
!currentOrganization ? "text-gray-400" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
@@ -117,14 +156,20 @@ export function TeamSwitcher({
|
|||||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||||
Organizations
|
Organizations
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
{data.organizations.edges.map((edge, index) => (
|
{hasOrganizations &&
|
||||||
|
data.organizations.edges.map((edge, index) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={edge.node.id}
|
key={edge.node.id}
|
||||||
onClick={() => handleOrganizationSwitch(edge.node)}
|
onClick={() => handleOrganizationSwitch(edge.node)}
|
||||||
className="gap-2 p-2"
|
className={`gap-2 p-2 ${
|
||||||
|
edge.node.id === organizationId ? "bg-muted" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex size-6 items-center justify-center rounded-sm border">
|
<div className="flex size-6 items-center justify-center rounded-sm border">
|
||||||
<LogoComponent org={edge.node} className="size-4 shrink-0" />
|
<LogoComponent
|
||||||
|
org={edge.node}
|
||||||
|
className="size-4 shrink-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{edge.node.name}
|
{edge.node.name}
|
||||||
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
|
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
|
||||||
|
|||||||
@@ -77,13 +77,6 @@ func RegisterHandler(usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.Handler
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
organizationID, _ := gid.ParseGID("AZSfP_xAcAC5IAAAAAAltA")
|
|
||||||
err = usrmgrSvc.AddUserToOrganization(r.Context(), user.ID, organizationID)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Failed to add user to organization: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the user in
|
// Log the user in
|
||||||
session, err := usrmgrSvc.Login(r.Context(), req.Email, req.Password)
|
session, err := usrmgrSvc.Login(r.Context(), req.Email, req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user