Repository: PostHog/posthog
Pull request: Project-based permissioning `TopNavigation`
## Changes

This adjusts `TopNavigation` UI for https://github.com/PostHog/posthog/pull/5976 like this:

### Project switcher with an allowed project and a restricted project (restricted only if project-based permissioning is available and enabled)
<img width="262" alt="Screen Shot 2021-09-10 at 14 45 40" src="https://user-images.githubusercontent.com/4550621/133452618-a310516f-8fbe-40f6-958c-8f19a9a88a3f.png">

Change extracted from https://github.com/PostHog/posthog/pull/5976 for clarity.

Resolves #4263 when merged with #6068.

Problem statement:
Project-based permissioning
## Is your feature request related to a problem?

A recent focus customer has asked for some granular permissions related to projects. The basic use case is track multiple different properties (websites/apps) under the same account (organization), so each property would be under its own project, and be able to grant access to specific team members only to specific properties (i.e. projects).

## Describe the solution you'd like

We could introduce the option of project access being available only to admins/owners by default and then for members you have to be assigned to a project specifically.

We could alternatively introduce the concept of private projects where only those with specific access in the org can see them.

## Describe alternatives you've considered

See above.


## Additional context

Tagging @Twixes here for input as he created most of the permissions engine.

This would certainly be a useful feature for large enterprise customers who need more granular controls due to privacy or compliance requirements.

#### *Thank you* for your feature request – we love each and every one!

Hints from issue discussion:
That's definitely possible and I was thinking about it when implementing organizations, but obviously more complexity involved. Would probably be best as an intermediary many-to-many `ProjectMembership` (for future per-project access levels) model. Probably worth taking a look at what Sentry have done in this area.
Related: https://github.com/PostHog/posthog/issues/3113
From speaking to multiple customers about this type of permission issue these primary needs to solve for arose grouped by priority:

**P0**
- Admins must have access to every project
- Admins must be able to grant or revoke access to any project
- Users must have access to one or more projects
- During migration we must preserve current access rights of users to prevent the product breaking, until an administrator has granted appropriate access to users and enabled project level permissions

**P1**
- Some users should only have access to specific dashboards (within a project) assigned by an admin
- Roles (Authorization) to access projects should be assigned through SSO (e.g Active Directory Groups)
- [For some customers] Project members should be able to grant access to others to this project
- Organizational Admins should be able to administer the platform without seeing any data themselves

cc: @jamesefhawkins 
@marcushyett-ph that looks like a good list. Was there any thought given to allowing users to opt out of or leave organizations or projects? 
Great question.

I made one change above to say granting and revoking - to cover this. Revoking was only discussed from the point of view of admins, and not users revoking their own access to organizations.

Also where SSO is used, they would expect authorization to be kept in line with SSO groups - so if they're removed from the (e.g. Active Directory) group, then they should be removed form the associated projects automatically.
Per https://github.com/PostHog/product-internal/issues/142 this is ready to be worked on, so I think we can start getting ahead of the next sprint and start planning the solution / creating mockups (CC @clarkus) so we can get them in front of users before building it. @mariusandra / @Twixes I imagine you'd like to own this on the engineering side, thoughts?
OK I will get started on this next week 👍 
Have we got any mocks on this? @clarkus 
@Twixes no not yet. I've been focused on paths. I can pick this up after https://github.com/PostHog/posthog/issues/5543 though.

Commit message: Delete 0169_project_based_permissioning.py

Patch to review:
diff --git a/frontend/src/layout/__stories__/Navigation.stories.tsx b/frontend/src/layout/__stories__/Navigation.stories.tsx
new file mode 100644
index 0000000000000..2c41f8588d03f
--- /dev/null
+++ b/frontend/src/layout/__stories__/Navigation.stories.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+import { Meta } from '@storybook/react'
+import { Layout } from 'antd'
+import { keaStory } from 'lib/storybook/kea-story'
+import { MainNavigation } from '../navigation'
+
+import { TopNavigation } from '../navigation/TopNavigation'
+
+import navigationInitial from './navigation.initial.json'
+
+export default {
+    title: 'PostHog/Components/TopNavigation',
+    parameters: {
+        layout: 'fullscreen',
+    },
+} as Meta
+
+export const PlainTopNavigation = keaStory(
+    () => (
+        <Layout>
+            <MainNavigation />
+            <Layout>
+                <TopNavigation />
+                {/* Normally here is the scene */}
+            </Layout>
+            {/* Normally here are essential elements */}
+        </Layout>
+    ),
+    navigationInitial
+)
diff --git a/frontend/src/layout/__stories__/navigation.initial.json b/frontend/src/layout/__stories__/navigation.initial.json
new file mode 100644
index 0000000000000..243e74cf2fa6d
--- /dev/null
+++ b/frontend/src/layout/__stories__/navigation.initial.json
@@ -0,0 +1,553 @@
+{
+    "kea": {
+        "router": {
+            "location": {
+                "pathname": "/insights",
+                "search": "",
+                "hash": ""
+            },
+            "searchParams": {},
+            "hashParams": {}
+        }
+    },
+    "scenes": {
+        "userLogic": {
+            "user": {
+                "date_joined": "2021-09-16T11:39:21.181994Z",
+                "uuid": "017bee67-6ede-0000-2e55-cd3c50873f0a",
+                "distinct_id": "3ReACAF6yDHyHYPzSpNTJkdpBa0MKGRUTDkWJ0BeIs8",
+                "first_name": "Jane Doe",
+                "email": "test@posthog.com",
+                "email_opt_in": false,
+                "anonymize_data": false,
+                "toolbar_mode": "toolbar",
+                "has_password": true,
+                "is_staff": true,
+                "is_impersonated": false,
+                "team": {
+                    "id": 1,
+                    "uuid": "017bee67-6f35-0000-1aa4-099013f32dd1",
+                    "organization": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                    "api_token": "e2e_token_1239",
+                    "name": "HogFlix Demo App",
+                    "completed_snippet_onboarding": true,
+                    "ingested_event": true,
+                    "is_demo": false,
+                    "timezone": "UTC",
+                    "effective_membership_level": 15
+                },
+                "organization": {
+                    "id": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                    "name": "HogFlix",
+                    "created_at": "2021-09-16T11:39:20.154006Z",
+                    "updated_at": "2021-09-16T12:37:01.817475Z",
+                    "membership_level": 15,
+                    "personalization": {},
+                    "setup": {
+                        "is_active": false,
+                        "current_section": null
+                    },
+                    "plugins_access_level": 9,
+                    "teams": [
+                        {
+                            "id": 1,
+                            "uuid": "017bee67-6f35-0000-1aa4-099013f32dd1",
+                            "organization": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                            "api_token": "e2e_token_1239",
+                            "name": "HogFlix Demo App",
+                            "completed_snippet_onboarding": true,
+                            "ingested_event": true,
+                            "is_demo": false,
+                            "timezone": "UTC",
+                            "effective_membership_level": 15
+                        }
+                    ],
+                    "available_features": [
+                        "zapier",
+                        "organizations_projects",
+                        "per_project_access",
+                        "google_login",
+                        "saml",
+                        "dashboard_collaboration",
+                        "ingestion_taxonomy"
+                    ],
+                    "domain_whitelist": [],
+                    "is_member_join_email_enabled": true,
+                    "per_project_access": true
+                },
+                "organizations": [
+                    {
+                        "id": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                        "name": "HogFlix"
+                    }
+                ],
+                "events_column_config": {
+                    "active": "DEFAULT"
+                }
+            },
+            "userLoading": false
+        },
+        "PreflightCheck": {
+            "logic": {
+                "preflight": {
+                    "django": true,
+                    "redis": true,
+                    "plugins": true,
+                    "celery": true,
+                    "db": true,
+                    "initiated": true,
+                    "cloud": false,
+                    "realm": "hosted",
+                    "available_social_auth_providers": {
+                        "github": false,
+                        "gitlab": false,
+                        "google-oauth2": false,
+                        "saml": false
+                    },
+                    "can_create_org": false,
+                    "ee_available": true,
+                    "is_clickhouse_enabled": false,
+                    "db_backend": "postgres",
+                    "available_timezones": {},
+                    "opt_out_capture": false,
+                    "posthog_version": "1.28.0",
+                    "email_service_available": false,
+                    "is_debug": true,
+                    "is_event_property_usage_enabled": false,
+                    "licensed_users_available": 4,
+                    "site_url": "http://localhost:8000"
+                },
+                "preflightLoading": false,
+                "preflightMode": null
+            }
+        },
+        "App": {
+            "showingDelayedSpinner": true,
+            "featureFlagsTimedOut": true
+        },
+        "sceneLogic": {
+            "scene": "insights",
+            "params": {},
+            "loadedScenes": {
+                "insights": {}
+            },
+            "loadingScene": null,
+            "upgradeModalFeatureNameAndCaption": null
+        },
+        "organizationLogic": {
+            "currentOrganization": {
+                "id": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                "name": "HogFlix",
+                "created_at": "2021-09-16T11:39:20.154006Z",
+                "updated_at": "2021-09-16T12:37:01.817475Z",
+                "membership_level": 15,
+                "personalization": {},
+                "setup": {
+                    "is_active": false,
+                    "current_section": null
+                },
+                "plugins_access_level": 9,
+                "teams": [
+                    {
+                        "id": 1,
+                        "uuid": "017bee67-6f35-0000-1aa4-099013f32dd1",
+                        "organization": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                        "api_token": "e2e_token_1239",
+                        "name": "HogFlix Demo App",
+                        "completed_snippet_onboarding": true,
+                        "ingested_event": true,
+                        "is_demo": false,
+                        "timezone": "UTC",
+                        "effective_membership_level": 15
+                    }
+                ],
+                "available_features": [
+                    "zapier",
+                    "organizations_projects",
+                    "per_project_access",
+                    "google_login",
+                    "saml",
+                    "dashboard_collaboration",
+                    "ingestion_taxonomy"
+                ],
+                "domain_whitelist": [],
+                "is_member_join_email_enabled": true,
+                "per_project_access": true
+            },
+            "currentOrganizationLoading": false,
+            "organizationBeingDeleted": null
+        },
+        "instance": {
+            "SystemStatus": {
+                "systemStatusLogic": {
+                    "systemStatus": {
+                        "overview": [
+                            {
+                                "key": "posthog_version",
+                                "metric": "PostHog version",
+                                "value": "1.28.0"
+                            },
+                            {
+                                "key": "posthog_git_sha",
+                                "metric": "PostHog Git SHA",
+                                "value": "Undefined"
+                            },
+                            {
+                                "key": "analytics_database",
+                                "metric": "Analytics database in use",
+                                "value": "Postgres"
+                            },
+                            {
+                                "key": "plugin_sever_alive",
+                                "metric": "Plugin server alive",
+                                "value": true
+                            },
+                            {
+                                "key": "plugin_sever_version",
+                                "metric": "Plugin server version",
+                                "value": "1.8.0"
+                            },
+                            {
+                                "key": "plugin_sever_job_queues",
+                                "metric": "Job queues enabled in plugin server",
+                                "value": "Graphile"
+                            },
+                            {
+                                "key": "db_alive",
+                                "metric": "Postgres database alive",
+                                "value": true
+                            },
+                            {
+                                "key": "pg_version",
+                                "metric": "Postgres version",
+                                "value": "13.0.4"
+                            },
+                            {
+                                "metric": "Postgres elements table size",
+                                "value": "206 rows (~256 kB)"
+                            },
+                            {
+                                "metric": "Postgres events table size",
+                                "value": "1.31K rows (~1656 kB)"
+                            },
+                            {
+                                "metric": "Postgres session recording table size",
+                                "value": "62 rows (~184 kB)"
+                            },
+                            {
+                                "key": "redis_alive",
+                                "metric": "Redis alive",
+                                "value": true
+                            },
+                            {
+                                "metric": "Redis version",
+                                "value": "6.2.5"
+                            },
+                            {
+                                "metric": "Redis current queue depth",
+                                "value": "0"
+                            },
+                            {
+                                "metric": "Redis connected client count",
+                                "value": "36"
+                            },
+                            {
+                                "metric": "Redis memory used",
+                                "value": "2.05MB"
+                            },
+                            {
+                                "metric": "Redis memory peak",
+                                "value": "5.24MB"
+                            },
+                            {
+                                "metric": "Redis total memory available",
+                                "value": "32.00GB"
+                            }
+                        ],
+                        "internal_metrics": {}
+                    },
+                    "systemStatusLoading": false,
+                    "queries": null,
+                    "queriesLoading": false,
+                    "analyzeQueryResult": null,
+                    "analyzeQueryResultLoading": false,
+                    "tab": "overview",
+                    "error": null,
+                    "openSections": ["0"],
+                    "analyzeModalOpen": false,
+                    "analyzeQuery": ""
+                }
+            }
+        },
+        "billing": {
+            "billingLogic": {
+                "billing": null,
+                "billingLoading": false,
+                "plans": [],
+                "plansLoading": false,
+                "billingSubscription": null,
+                "billingSubscriptionLoading": false
+            }
+        },
+        "teamLogic": {
+            "currentTeam": {
+                "id": 1,
+                "uuid": "017bee67-6f35-0000-1aa4-099013f32dd1",
+                "organization": "017bee67-6ad9-0000-2ae2-0c6fd4bd9273",
+                "api_token": "e2e_token_1239",
+                "app_urls": [],
+                "name": "HogFlix Demo App",
+                "slack_incoming_webhook": null,
+                "created_at": "2021-09-16T11:39:21.270216Z",
+                "updated_at": "2021-09-16T11:39:21.589504Z",
+                "anonymize_ips": false,
+                "completed_snippet_onboarding": true,
+                "ingested_event": true,
+                "test_account_filters": [
+                    {
+                        "key": "$host",
+                        "value": [
+                            "localhost:8000",
+                            "localhost:5000",
+                            "127.0.0.1:8000",
+                            "127.0.0.1:3000",
+                            "localhost:3000"
+                        ],
+                        "operator": "is_not"
+                    }
+                ],
+                "is_demo": false,
+                "timezone": "UTC",
+                "data_attributes": ["data-attr"],
+                "session_recording_opt_in": false,
+                "session_recording_retention_period_days": null,
+                "effective_membership_level": 15
+            },
+            "currentTeamLoading": false,
+            "teamBeingDeleted": null
+        },
+        "organization": {
+            "Settings": {
+                "bulkInviteLogic": {
+                    "invitedTeamMembers": [],
+                    "invitedTeamMembersLoading": false,
+                    "invites": [
+                        {
+                            "target_email": "",
+                            "first_name": "",
+                            "isValid": true
+                        }
+                    ]
+                },
+                "membersLogic": {
+                    "members": [
+                        {
+                            "id": "017bee67-6f43-0000-338f-fb1910dd798e",
+                            "user": {
+                                "id": 1,
+                                "uuid": "017bee67-6ede-0000-2e55-cd3c50873f0a",
+                                "distinct_id": "3ReACAF6yDHyHYPzSpNTJkdpBa0MKGRUTDkWJ0BeIs8",
+                                "first_name": "Jane Doe",
+                                "email": "test@posthog.com"
+                            },
+                            "level": 15,
+                            "joined_at": "2021-09-16T11:39:21.283404Z",
+                            "updated_at": "2021-09-16T11:39:21.283413Z"
+                        }
+                    ],
+                    "membersLoading": false
+                }
+            }
+        }
+    },
+    "lib": {
+        "logic": {
+            "featureFlagLogic": {
+                "featureFlags": {
+                    "per-project-access": true
+                },
+                "receivedFeatureFlags": true
+            }
+        },
+        "components": {
+            "CommandPalette": {
+                "commandPaletteLogic": {
+                    "isPaletteShown": false,
+                    "keyboardResultIndex": 0,
+                    "hoverResultIndex": null,
+                    "input": "",
+                    "activeFlow": null,
+                    "rawCommandRegistrations": {
+                        "go-to": {
+                            "key": "go-to",
+                            "scope": "global",
+                            "prefixes": ["open", "visit"],
+                            "resolver": [
+                                {
+                                    "icon": {},
+                                    "display": "Go to Dashboards"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Insights"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Trends"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Sessions"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Funnels"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Retention"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to User Paths"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Events"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Actions"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Live Sessions"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Persons",
+                                    "synonyms": ["people"]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Cohorts"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Feature Flags",
+                                    "synonyms": ["feature flags", "a/b tests"]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Annotations"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Team Members",
+                                    "synonyms": ["organization", "members", "invites", "teammates"]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Project Settings"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to My Settings",
+                                    "synonyms": ["account"]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to Plugins",
+                                    "synonyms": ["integrations"]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Go to System Status Page",
+                                    "synonyms": [
+                                        "redis",
+                                        "celery",
+                                        "django",
+                                        "postgres",
+                                        "backend",
+                                        "service",
+                                        "online"
+                                    ]
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Create Action"
+                                },
+                                {
+                                    "icon": {},
+                                    "display": "Log Out"
+                                }
+                            ]
+                        },
+                        "open-urls": {
+                            "key": "open-urls",
+                            "scope": "global",
+                            "prefixes": ["open", "visit"]
+                        },
+                        "debug-clickhouse-queries": {
+                            "key": "debug-clickhouse-queries",
+                            "scope": "global",
+                            "resolver": {
+                                "icon": {},
+                                "display": "Debug ClickHouse Queries"
+                            }
+                        },
+                        "calculator": {
+                            "key": "calculator",
+                            "scope": "global"
+                        },
+                        "create-personal-api-key": {
+                            "key": "create-personal-api-key",
+                            "scope": "global",
+                            "resolver": {
+                                "icon": {},
+                                "display": "Create Personal API Key"
+                            }
+                        },
+                        "create-dashboard": {
+                            "key": "create-dashboard",
+                            "scope": "global",
+                            "resolver": {
+                                "icon": {},
+                                "display": "Create Dashboard"
+                            }
+                        },
+                        "share-feedback": {
+                            "key": "share-feedback",
+                            "scope": "global",
+                            "resolver": {
+                                "icon": {},
+                                "display": "Share Feedback",
+                                "synonyms": ["send opinion", "ask question", "message posthog", "github issue"]
+                            }
+                        }
+                    }
+                }
+            },
+            "BackTo": {
+                "backTo": null
+            }
+        }
+    },
+    "layout": {
+        "navigation": {
+            "navigationLogic": {
+                "latestVersion": "1.28.0",
+                "latestVersionLoading": false,
+                "menuCollapsed": false,
+                "changelogModalOpen": false,
+                "toolbarModalOpen": false,
+                "inviteMembersModalOpen": false,
+                "pinnedDashboardsVisible": false,
+                "hotkeyNavigationEngaged": false,
+                "filteredEnvironment": "production",
+                "projectModalShown": false,
+                "organizationModalShown": false
+            }
+        }
+    }
+}
diff --git a/frontend/src/layout/navigation/TopNavigation.tsx b/frontend/src/layout/navigation/TopNavigation.tsx
index 184c5607a2e60..8c0fc7d7663ba 100644
--- a/frontend/src/layout/navigation/TopNavigation.tsx
+++ b/frontend/src/layout/navigation/TopNavigation.tsx
@@ -21,6 +21,7 @@ import {
     CreditCardOutlined,
     KeyOutlined,
     SmileOutlined,
+    StopOutlined,
 } from '@ant-design/icons'
 import { sceneLogic, urls } from 'scenes/sceneLogic'
 import { CreateProjectModal } from 'scenes/project/CreateProjectModal'
@@ -30,14 +31,16 @@ import { commandPaletteLogic } from 'lib/components/CommandPalette/commandPalett
 import { Link } from 'lib/components/Link'
 import { LinkButton } from 'lib/components/LinkButton'
 import { BulkInviteModal } from 'scenes/organization/Settings/BulkInviteModal'
-import { AvailableFeature, UserType } from '~/types'
+import { AvailableFeature, TeamBasicType, UserType } from '~/types'
 import { CreateInviteModalWithButton } from 'scenes/organization/Settings/CreateInviteModal'
 import { preflightLogic } from 'scenes/PreflightCheck/logic'
 import { billingLogic } from 'scenes/billing/billingLogic'
 import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
-import { Environments, FEATURE_FLAGS } from 'lib/constants'
+import { Environments, FEATURE_FLAGS, OrganizationMembershipLevel } from 'lib/constants'
 import { ProfilePicture } from 'lib/components/ProfilePicture'
 import { Tooltip } from 'lib/components/Tooltip'
+import { teamLogic } from '../../scenes/teamLogic'
+import { organizationLogic } from '../../scenes/organizationLogic'
 
 export function WhoAmI({ user }: { user: UserType }): JSX.Element {
     return (
@@ -51,6 +54,53 @@ export function WhoAmI({ user }: { user: UserType }): JSX.Element {
     )
 }
 
+function ProjectRow({ team }: { team: TeamBasicType }): JSX.Element {
+    const { currentTeam } = useValues(teamLogic)
+    const { updateCurrentTeam } = useActions(userLogic)
+    const { push } = router.actions
+
+    const isCurrent = team.id === currentTeam?.id
+    const isRestricted = !team.effective_membership_level
+
+    return (
+        <button
+            key={team.id}
+            className="plain-button"
+            type="button"
+            onClick={(e) => {
+                if (!isCurrent && !isRestricted) {
+                    updateCurrentTeam(team.id, '/')
+                } else {
+                    e.preventDefault() // Prevent dropdown from hiding if can't switch project
+                }
+            }}
+            disabled={isCurrent || isRestricted}
+            style={{
+                cursor: isCurrent || isRestricted ? 'default' : undefined,
+                color: isRestricted ? 'var(--text-muted)' : undefined,
+            }}
+        >
+            {isRestricted ? <StopOutlined className="mr-05" /> : <ProjectOutlined className="mr-05" />}
+            <span style={{ flexGrow: 1, fontWeight: isCurrent ? 700 : 400 }}>{team.name}</span>
+            {!isRestricted && (
+                <span
+                    className="subaction"
+                    onClick={(e) => {
+                        e.stopPropagation()
+                        if (isCurrent) {
+                            push(urls.projectSettings())
+                        } else {
+                            updateCurrentTeam(team.id, '/project/settings')
+                        }
+                    }}
+                >
+                    <ToolOutlined />
+                </span>
+            )}
+        </button>
+    )
+}
+
 export function TopNavigation(): JSX.Element {
     const {
         setMenuCollapsed,
@@ -70,16 +120,22 @@ export function TopNavigation(): JSX.Element {
         projectModalShown,
         organizationModalShown,
     } = useValues(navigationLogic)
+    const { currentTeam } = useValues(teamLogic)
     const { user } = useValues(userLogic)
     const { preflight } = useValues(preflightLogic)
     const { billing } = useValues(billingLogic)
-    const { logout, updateCurrentTeam, updateCurrentOrganization } = useActions(userLogic)
+    const { currentOrganization } = useValues(organizationLogic)
+    const { logout, updateCurrentOrganization } = useActions(userLogic)
     const { guardAvailableFeature } = useActions(sceneLogic)
     const { sceneConfig } = useValues(sceneLogic)
-    const { push } = router.actions
     const { showPalette } = useActions(commandPaletteLogic)
     const { featureFlags } = useValues(featureFlagLogic)
 
+    const isCurrentProjectRestricted = currentTeam && !currentTeam.effective_membership_level
+    const isProjectCreationForbidden =
+        !currentOrganization?.membership_level ||
+        currentOrganization.membership_level < OrganizationMembershipLevel.Admin
+
     const whoAmIDropdown = (
         <div className="navigation-top-dropdown whoami-dropdown">
             <div className="whoami" style={{ margin: 16 }}>
@@ -201,46 +257,21 @@ export function TopNavigation(): JSX.Element {
         <div className="navigation-top-dropdown project-dropdown">
             <div className="title">Select project</div>
             <div className="projects">
-                {user?.organization?.teams &&
-                    user.organization.teams
+                {currentOrganization?.teams &&
+                    currentOrganization.teams
                         .sort((teamA, teamB) =>
-                            teamA.id === user?.team?.id ? -2 : teamA.name.localeCompare(teamB.name)
+                            teamA.id === currentTeam?.id
+                                ? -2
+                                : teamA.effective_membership_level
+                                ? 2
+                                : teamA.name.localeCompare(teamB.name)
                         )
-                        .map((team) => {
-                            const isCurrentTeam = team.id === user?.team?.id
-                            return (
-                                <button
-                                    key={team.id}
-                                    className="plain-button"
-                                    type="button"
-                                    onClick={() => updateCurrentTeam(team.id, '/')}
-                                    style={isCurrentTeam ? { cursor: 'default' } : undefined}
-                                    disabled={isCurrentTeam}
-                                >
-                                    <ProjectOutlined className="mr-05" />
-                                    <span style={{ flexGrow: 1, fontWeight: isCurrentTeam ? 700 : 400 }}>
-                                        {team.name}
-                                    </span>
-                                    <span
-                                        className="subaction"
-                                        onClick={(e) => {
-                                            e.stopPropagation()
-                                            if (isCurrentTeam) {
-                                                push(urls.projectSettings())
-                                            } else {
-                                                updateCurrentTeam(team.id, '/project/settings')
-                                            }
-                                        }}
-                                    >
-                                        <ToolOutlined />
-                                    </span>
-                                </button>
-                            )
-                        })}
+                        .map((team) => <ProjectRow key={team.id} team={team} />)}
             </div>
             <button
                 type="button"
                 className="plain-button"
+                disabled={isProjectCreationForbidden}
                 onClick={() =>
                     guardAvailableFeature(
                         AvailableFeature.ORGANIZATIONS_PROJECTS,
@@ -251,6 +282,10 @@ export function TopNavigation(): JSX.Element {
                         }
                     )
                 }
+                style={{
+                    cursor: isProjectCreationForbidden ? 'default' : undefined,
+                    color: isProjectCreationForbidden ? 'var(--text-muted)' : undefined,
+                }}
             >
                 <PlusOutlined className="mr-05" />
                 Create new project
@@ -307,8 +342,12 @@ export function TopNavigation(): JSX.Element {
                         placement="bottomCenter"
                     >
                         <div>
-                            <ProjectOutlined className="mr-05" />
-                            {user?.team?.name}
+                            {isCurrentProjectRestricted ? (
+                                <StopOutlined className="mr-05" />
+                            ) : (
+                                <ProjectOutlined className="mr-05" />
+                            )}
+                            {currentTeam?.name}
                             <DownOutlined className="ml-05" />
                         </div>
                     </Dropdown>
diff --git a/frontend/src/lib/storybook/kea-story.tsx b/frontend/src/lib/storybook/kea-story.tsx
index 4b69c9cfaa4f7..a25410525ed61 100644
--- a/frontend/src/lib/storybook/kea-story.tsx
+++ b/frontend/src/lib/storybook/kea-story.tsx
@@ -4,10 +4,14 @@ import { router } from 'kea-router'
 import { getContext, Provider } from 'kea'
 import React, { useEffect, useState } from 'react'
 import { App } from 'scenes/App'
+import { featureFlagLogic } from '../logic/featureFlagLogic'
+import { systemStatusLogic } from '../../scenes/instance/SystemStatus/systemStatusLogic'
 
 function resetKeaWithState(state: Record<string, any>): void {
     const history = createMemoryHistory({ initialEntries: [state.kea.router.location] })
     initKea({ state, routerLocation: history.location, routerHistory: history })
+    featureFlagLogic.mount()
+    systemStatusLogic.mount()
     router.mount()
     const { store } = getContext()
     store.dispatch({ type: 'bla' })
diff --git a/frontend/src/scenes/project/Settings/AccessControl.tsx b/frontend/src/scenes/project/Settings/AccessControl.tsx
index 38700addcdf7e..2be7e6214a782 100644
--- a/frontend/src/scenes/project/Settings/AccessControl.tsx
+++ b/frontend/src/scenes/project/Settings/AccessControl.tsx
@@ -47,7 +47,7 @@ export function AccessControl({ isRestricted }: RestrictedComponentProps): JSX.E
             </p>
             <Switch
                 // @ts-expect-error - id works just fine despite not being in CompoundedComponent
-                id="project-based-permissioning-switch"
+                id="access-control-switch"
                 onChange={(checked) => {
                     guardAvailableFeature(
                         AvailableFeature.PROJECT_BASED_PERMISSIONING,
@@ -64,7 +64,7 @@ export function AccessControl({ isRestricted }: RestrictedComponentProps): JSX.E
                 style={{
                     marginLeft: '10px',
                 }}
-                htmlFor="project-based-permissioning-switch"
+                htmlFor="access-control-switch"
             >
                 Make project private
             </label>
diff --git a/frontend/src/scenes/saved-insights/SavedInsights.tsx b/frontend/src/scenes/saved-insights/SavedInsights.tsx
index f04b59ac88706..5a0e35e70e9f5 100644
--- a/frontend/src/scenes/saved-insights/SavedInsights.tsx
+++ b/frontend/src/scenes/saved-insights/SavedInsights.tsx
@@ -32,6 +32,7 @@ import { dashboardsModel } from '~/models/dashboardsModel'
 import { DateFilter } from 'lib/components/DateFilter/DateFilter'
 import '../insights/InsightHistoryPanel/InsightHistoryPanel.scss'
 import dayjs from 'dayjs'
+
 import { PageHeader } from 'lib/components/PageHeader'
 
 const { TabPane } = Tabs


Code review comments:
diff_hunk: @@ -51,6 +54,53 @@ export function WhoAmI({ user }: { user: UserType }): JSX.Element {
     )
 }
 
+function ProjectRow({ team }: { team: TeamBasicType }): JSX.Element {
+    const { currentTeam } = useValues(teamLogic)
+    const { updateCurrentTeam } = useActions(userLogic)
+    const { push } = router.actions
+
+    const isCurrent = team.id === currentTeam?.id
+    const isRestricted = !team.effective_membership_level
+
+    return (
+        <button
+            key={team.id}
+            className="plain-button"
+            type="button"
+            onClick={(e) => {
+                if (!isCurrent && !isRestricted) {
+                    updateCurrentTeam(team.id, '/')
+                } else {
+                    e.preventDefault() // Prevent dropdown from hiding if can't switch project
+                }
+            }}
+            disabled={isCurrent || isRestricted}
+            style={{
+                cursor: isCurrent || isRestricted ? 'default' : undefined,
+                color: isRestricted ? 'var(--text-muted)' : undefined,
+            }}
+        >
+            {isRestricted ? <StopOutlined className="mr-05" /> : <ProjectOutlined className="mr-05" />}
+            <span style={{ flexGrow: 1, fontWeight: isCurrent ? 700 : 400 }}>{team.name}</span>
path: frontend/src/layout/navigation/TopNavigation.tsx
text: @user1:
```suggestion
            <span style={{ flexGrow: 1, fontWeight: isCurrent ? 'bold' : 'normal' }}>{team.name}</span>
```

diff_hunk: @@ -251,6 +282,10 @@ export function TopNavigation(): JSX.Element {
                         }
                     )
                 }
+                style={{
+                    cursor: isProjectCreationForbidden ? 'default' : undefined,
path: frontend/src/layout/navigation/TopNavigation.tsx
text: @user1:
```suggestion
                    cursor: isProjectCreationForbidden ? 'default' : 'not-allowed',
```