Compare commits

..
Author SHA1 Message Date
Eduard Gert d4451274c7 Replace the pending-approval error page with a dedicated screen (#802)
* Replace the pending-approval error page with a dedicated screen

A user waiting for an administrator to approve their account was shown
the generic /error page: an error icon, a raw response_message dump and
"if you believe this is an error, please contact your administrator".
Waiting for approval is an expected part of signing up, so it now gets a
screen of its own, following the onboarding layout of logo, centered card
and a stepper placing the user between "Account Created" and
"Join Account".

The owner to contact is read out of the refusal management sends, which
names a masked address, and the copy asks for the owner without naming
one when the address is absent.

Steps.Step gains an optional status and size, so a step can render as
complete, current or upcoming at a larger scale. Steps that pass neither
keep the look used by the instruction-list steppers.

* Detect the pending-approval screen by test id in e2e

The three places that recognise a pending user matched on "User Approval
Pending", the heading the old error page rendered. The screen that
replaced it has no heading, so the approval branch of the login races
could never resolve and every leg timed out instead: both fixtures treat
approval as a legitimate outcome, not a failure.

They now key off the data-testid on the screen's root, which does not
move when the copy does.

* Point the pending-approval footer at the docs and support pages

A mailbox asks the reader to compose something; the approval docs explain
what they are waiting for, and the support page routes them the way every
other NetBird surface does. The support address constant went with it.

* Render the pending-approval screen from the caller-identity response

Every call a pending user makes is refused, but only /users/current
names the owner who can approve them. Redirecting to /error on whichever
403 landed first therefore threw that name away about as often as it
kept it, and which screen appeared came down to a race.

Pending users are no longer routed to /error at all. The provider that
already waits on /users/current renders the screen from that response,
which also ends the wait it could never finish: the caller identity it
blocks on is exactly what such a user is refused.

* Decide the refused-user screen from the responses, not the first 403

Routing on whichever refused call landed first meant the screen depended
on a race, and once only /users/current could name the owner it meant
the name was usually thrown away. Management older than that reports a
pending user as merely blocked there, so it also meant a pending user
landed on the blocked screen, which is what the e2e run caught.

UserProfileProvider now decides from the responses it already waits on:
/users/current first, since it alone names the owner, then the user list,
which reports pending on every management version. Blocked keeps the
error page, decided after pending so the older wording cannot win.

* Wait for both refusal responses before choosing a screen

Concluding blocked the moment /users/current said so meant concluding it
while the user list was still in flight — and on management that reports
a pending user as merely blocked there, that list is the one response
that would have said otherwise. The screen it was meant to prevent was
reachable again, just through a narrower window.

The decision moves into resolveRefusedUser, which holds off while a
better-informed response is still coming: the named one before the list,
the list before blocked. Navigating to the error page becomes an effect
rather than a call made while rendering.

* Move the refused-user decision next to the provider that makes it

It sat in utils only because two callers needed it, but the second one —
the guard keeping these errors off the error boundary — needs nothing
more than to recognise them, which is a line of its own. So the decision
moves beside the provider that acts on it, and reads an error into a
RefusalReason rather than assembling a discriminated union.

The cost is that both files now spell out management's wording. The one
here is the tested reading; the one in @utils/api only has to be no
narrower than it.

* Assert the refusal kind the resolver actually returns

The result property is kind, not reason. toEqual types its argument
loosely, so the stale expectations type-checked and only failed when run.
2026-09-21 10:33:43 +02:00
Brad Ison b2e658a2be Add a deployment announcement banner set through NETBIRD_ANNOUNCEMENT (#806)
An operator can now give a deployment a permanent banner: the text in
NETBIRD_ANNOUNCEMENT is shown to every user in the prominent
announcement style, cannot be closed, and appears in every edition.  It
sits ahead of the remote and MSP announcements, behind only the billing
warnings when those open, so it is always the first thing a user sees.
Empty or unset means no banner, which is the default.
2026-09-18 10:54:22 +02:00
Jack Carter 4b2c394181 fix: keep tables from scrolling horizontally when the Active toggle shows (#805)
* fix: keep tables from scrolling horizontally when the Active toggle shows

The Active toggle column restored in #803 adds about 100px to every table,
which pushed policies and legacy routes into a horizontal scrollbar next to
an expanded sidebar on 1280px and 1440px screens.

Hide the column below the xl breakpoint (2xl for legacy routes, the widest
table) instead of dropping it. Enable/Disable remains available in each
row's action menu on narrower viewports.

Also on the legacy routes page: tighten cell padding in the expanded route
rows, remove a 10px max-width on the network address that made it overflow
into the Type column, let the Name cell shrink from 270px to 200px when
space is tight, and drop the unused sort control from the Active header.

* refactor: hide the Active column with responsive classes instead of a media query hook

Columns can now carry a className through TanStack's column meta, which
DataTable applies to both the header and body cells. The Active column
uses `hidden xl:table-cell` (`2xl` for legacy routes) so the browser
handles the breakpoint and no JavaScript listener is needed.

* refactor: apply the Active column treatment consistently and cover it with a test

- Peer routes, group resources and reverse proxy targets carry the same
  Active toggle column and now hide it below xl like the other tables.
- DataTable gains tableHeadClassName so RouteTable can tighten header and
  body padding through props instead of descendant selectors.
- The four tables reference ENABLED_COLUMN_ID instead of a literal id so
  the fade-on-disable helper and the column definitions stay linked.
- The network address cell truncates past 16rem with a title tooltip so a
  long IPv6 CIDR cannot push the routes table back into a scrollbar.
- Add a DataTable render test for column meta classes on header and body.
2026-09-17 11:08:58 +02:00
Jack Carter 74eae99820 Fix group and resource order in resource group audit events (#804) 2026-09-17 09:54:40 +02:00
189 changed files with 1287 additions and 1095 deletions

No files matched your search

+1 -6
View File
@@ -35,9 +35,6 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
# local UI sandbox (throwaway preview harness — never commit)
/src/app/sandbox/
# config
.local*config*.json
.test-config.json
@@ -56,6 +53,4 @@ playwright-report*.zip
/public/ironrdp-pkg/
/public/netbird.wasm
.idea
src/.local-config*
# local mock data (throwaway preview — never commit)
/src/utils/mockData.ts
src/.local-config*
+2 -1
View File
@@ -24,5 +24,6 @@
"hubspotSignupFormId": "$NETBIRD_HUBSPOT_SIGNUP_FORM_ID",
"hubspotOnboardingFormId": "$NETBIRD_HUBSPOT_ONBOARDING_FORM_ID",
"hubspotSurveyFormId": "$NETBIRD_HUBSPOT_SURVEY_FORM_ID",
"analyticsExcludedEmails": "$NETBIRD_ANALYTICS_EXCLUDED_EMAILS"
"analyticsExcludedEmails": "$NETBIRD_ANALYTICS_EXCLUDED_EMAILS",
"announcement": "$NETBIRD_ANNOUNCEMENT"
}
+3 -1
View File
@@ -73,6 +73,8 @@ export NETBIRD_HUBSPOT_SIGNUP_FORM_ID=${NETBIRD_HUBSPOT_SIGNUP_FORM_ID}
export NETBIRD_HUBSPOT_ONBOARDING_FORM_ID=${NETBIRD_HUBSPOT_ONBOARDING_FORM_ID}
export NETBIRD_HUBSPOT_SURVEY_FORM_ID=${NETBIRD_HUBSPOT_SURVEY_FORM_ID}
export NETBIRD_ANALYTICS_EXCLUDED_EMAILS=${NETBIRD_ANALYTICS_EXCLUDED_EMAILS}
# A permanent banner for this deployment, shown to every user; empty for none.
export NETBIRD_ANNOUNCEMENT=${NETBIRD_ANNOUNCEMENT}
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
@@ -163,7 +165,7 @@ sed -i "s|add_header Content-Security-Policy \"[^\"]*\" always;|$CSP_HEADER|g" /
}
# replace ENVs in the config
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$NETBIRD_GOOGLE_TAG_MANAGER_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS \$\$NETBIRD_AUTH_SERVICE_URL \$\$NETBIRD_WASM_PATH \$\$NETBIRD_LICENSED \$\$NETBIRD_CLOUD \$\$NETBIRD_AGENT_NETWORK_ONLY \$\$NETBIRD_AGENT_NETWORK_ENABLED \$\$NETBIRD_HUBSPOT_PORTAL_ID \$\$NETBIRD_HUBSPOT_SIGNUP_FORM_ID \$\$NETBIRD_HUBSPOT_ONBOARDING_FORM_ID \$\$NETBIRD_HUBSPOT_SURVEY_FORM_ID \$\$NETBIRD_ANALYTICS_EXCLUDED_EMAILS"
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$NETBIRD_GOOGLE_TAG_MANAGER_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS \$\$NETBIRD_AUTH_SERVICE_URL \$\$NETBIRD_WASM_PATH \$\$NETBIRD_LICENSED \$\$NETBIRD_CLOUD \$\$NETBIRD_AGENT_NETWORK_ONLY \$\$NETBIRD_AGENT_NETWORK_ENABLED \$\$NETBIRD_HUBSPOT_PORTAL_ID \$\$NETBIRD_HUBSPOT_SIGNUP_FORM_ID \$\$NETBIRD_HUBSPOT_ONBOARDING_FORM_ID \$\$NETBIRD_HUBSPOT_SURVEY_FORM_ID \$\$NETBIRD_ANALYTICS_EXCLUDED_EMAILS \$\$NETBIRD_ANNOUNCEMENT"
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS".tmpl > "$OIDC_TRUSTED_DOMAINS"
+1 -1
View File
@@ -33,7 +33,7 @@ export async function loginToApp(
// Use locators that match either outcome — Playwright auto-waits.
const appReady = page.getByTestId("left-navigation-item").first();
const setupModal = page.getByTestId("setup-netbird-modal");
const approvalPending = page.getByText("User Approval Pending");
const approvalPending = page.getByTestId("pending-approval");
const onboarding = page.getByText("Add new device to your network");
const selectAccount = page.getByText("Select account");
const loginInput = page.locator("input[id=loginName]");
+1 -1
View File
@@ -102,7 +102,7 @@ async function loginAndSave(
const skipButton = page.locator("button[name=skip]");
const appNav = page.getByTestId("left-navigation-item").first();
const modal = page.getByTestId("setup-netbird-modal");
const approval = page.getByText("User Approval Pending");
const approval = page.getByTestId("pending-approval");
let after_login: "2fa" | "app" | "modal" | "approval";
try {
@@ -20,7 +20,7 @@ test.describe.serial("User Approval & Billing Admin @team", () => {
});
const page = await context.newPage();
await loginToApp(page, "user");
await expect(page.getByText("User Approval Pending")).toBeVisible();
await expect(page.getByTestId("pending-approval")).toBeVisible();
await context.close();
});
@@ -60,7 +60,7 @@ export default function Integrations() {
(!isNetBirdCloud() && (permission?.settings?.read ?? false))
}
>
<div className={"w-full"}>
<div className={"border-l border-nb-gray-930 w-full"}>
<IdentityProviderTab />
<SSOTab />
<EventStreamingTab />
@@ -87,7 +87,7 @@ export default function AgentNetworkConfigurationPage() {
<GroupsProvider>
<PeersProvider>
<AIProvidersProvider>
<div className={"w-full"}>
<div className={"border-l border-nb-gray-930 w-full"}>
<Tabs.Content
value={TAB_BUDGET_SETTINGS}
className={"w-full"}
+2 -13
View File
@@ -15,11 +15,6 @@ import React, { useState } from "react";
import { useSWRConfig } from "swr";
import GroupsProvider from "@/contexts/GroupsProvider";
import PeersProvider from "@/contexts/PeersProvider";
import { useTheme } from "@/contexts/ThemeProvider";
import {
canvasBackground,
canvasDotColor,
} from "@/modules/control-center/utils/canvas-theme";
import PoliciesProvider from "@/contexts/PoliciesProvider";
import { Network } from "@/interfaces/Network";
import PageContainer from "@/layouts/PageContainer";
@@ -110,8 +105,6 @@ const PRO_OPTIONS = { hideAttribution: true };
const DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: EMPTY_STATE_ZOOM };
function ControlCenterCanvas() {
const { resolvedTheme } = useTheme();
const isLight = resolvedTheme === "light";
const canvas = useCanvasState();
const ui = useControlCenterUI();
const draft = useDraft();
@@ -290,7 +283,7 @@ function ControlCenterCanvas() {
onInit={stableOnInit}
maxZoom={DEFAULT_MAX_ZOOM}
minZoom={DEFAULT_MIN_ZOOM}
colorMode={isLight ? "light" : "dark"}
colorMode={"dark"}
panOnDrag={canInteract}
panOnScroll={draft.isSelectMode && !emptyState}
zoomOnScroll={canInteract}
@@ -304,11 +297,7 @@ function ControlCenterCanvas() {
selectionOnDrag={draft.isSelectMode && !emptyState}
selectionMode={SelectionMode.Partial}
>
<Background
bgColor={canvasBackground(resolvedTheme)}
gap={20}
color={canvasDotColor(resolvedTheme)}
/>
<Background bgColor={"#181a1d"} gap={20} color={"#717171"} />
<CanvasContextMenu onOpenChange={setContextMenuOpen} />
<NodeContextMenu
position={nodeContextMenuPos}
+1 -1
View File
@@ -115,7 +115,7 @@ const GroupDetailsName = () => {
>
<div
className={cn(
"flex h-8 w-8 items-center justify-center gap-2 text-nb-gray-300 hover:text-nb-gray-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer",
"flex h-8 w-8 items-center justify-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer",
!isAllowedToRename &&
"opacity-40 cursor-not-allowed pointer-events-none",
)}
+2 -2
View File
@@ -257,7 +257,7 @@ const PeerHeader = () => {
<ModalTrigger>
<div
className={
"flex h-8 w-8 items-center justify-center gap-2 text-nb-gray-400 hover:text-nb-gray-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
"flex h-8 w-8 items-center justify-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
}
>
<PencilIcon size={16} />
@@ -741,7 +741,7 @@ function EditableValue({
<span>{value}</span>
{canEdit && (
<button
className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-nb-gray-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
onClick={(e) => {
e.stopPropagation();
onEdit();
+1 -1
View File
@@ -107,7 +107,7 @@ export default function NetBirdSettings() {
page={"Settings"}
hasAccess={permission?.billing?.read || permission?.settings?.read}
>
<div className={"w-full"}>
<div className={"border-l border-nb-gray-930 w-full"}>
{account && <AuthenticationTab account={account} />}
{permission.setup_keys.read && <SetupKeysTab />}
{account?.settings?.embedded_idp_enabled &&
+1 -1
View File
@@ -183,7 +183,7 @@ function UserOverview({ user, initialGroups }: Readonly<Props>) {
<div className={"flex items-center gap-3"}>
<div
className={
"w-10 h-10 rounded-full relative flex items-center justify-center text-nb-gray-100 uppercase text-md font-medium bg-nb-gray-900"
"w-10 h-10 rounded-full relative flex items-center justify-center text-white uppercase text-md font-medium bg-nb-gray-900"
}
style={
isServiceUser
+15 -6
View File
@@ -8,6 +8,7 @@ import { ArrowRightIcon, RefreshCw } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
import { PendingApproval } from "@/modules/users/PendingApproval";
const config = loadConfig();
@@ -57,9 +58,20 @@ export default function ErrorPage() {
error?.code === 403 &&
error?.message?.toLowerCase().includes("pending approval");
// Waiting for an approval is an expected part of signing up, so it gets a
// welcoming screen of its own instead of the error treatment.
if (isPendingApproval) {
return (
<PendingApproval
error={error}
onRefresh={handleRetry}
onLogout={handleLogout}
/>
);
}
const getTitle = () => {
if (isBlockedUser) return "User Account Blocked";
if (isPendingApproval) return "User Approval Pending";
return "Access Error";
};
@@ -67,9 +79,6 @@ export default function ErrorPage() {
if (isBlockedUser) {
return "Your access has been blocked by the NetBird account administrator, possibly due to new user approval requirements or security policies. Please contact your administrator to regain access.";
}
if (isPendingApproval) {
return "Your account is pending approval from an administrator. Please wait for approval before accessing the dashboard.";
}
return "An error occurred while trying to access the dashboard. Please try again or contact your administrator.";
};
@@ -98,7 +107,7 @@ export default function ErrorPage() {
</Paragraph>
<div className="mt-5 space-y-3">
{!isBlockedUser && !isPendingApproval && (
{!isBlockedUser && (
<Button variant="default-outline" size="sm" onClick={handleRetry}>
<RefreshCw size={16} className="mr-2" />
Try Again
@@ -106,7 +115,7 @@ export default function ErrorPage() {
)}
<Button variant="primary" size="sm" onClick={handleLogout}>
{isBlockedUser || isPendingApproval ? "Sign Out" : "Logout"}
{isBlockedUser ? "Sign Out" : "Logout"}
<ArrowRightIcon size={16} />
</Button>
</div>
+4 -67
View File
@@ -5,69 +5,6 @@
:root {
--toasts-before: 0;
--lift: 1;
/* nb-gray INVERTED ramp (light mode) — space-separated RGB channels.
High stops are surfaces (near-white), low stops are text (near-black).
DEFAULT (the app background) sits slightly below white so white
inputs, buttons and cards stand in front of it.
The ramp is pure neutral (R = G = B) and matches the desktop client's
light ramp (netbirdio/netbird#7344) so both UIs share one palette. */
--nb-gray-DEFAULT: 243 243 243;
--nb-gray-50: 26 26 26;
--nb-gray-100: 33 33 33;
--nb-gray-200: 50 50 50;
--nb-gray-250: 58 58 58;
--nb-gray-300: 77 77 77;
--nb-gray-350: 92 92 92;
--nb-gray-400: 108 108 108;
--nb-gray-500: 135 135 135;
--nb-gray-600: 154 154 154;
--nb-gray-700: 209 209 209;
--nb-gray-800: 226 226 226;
--nb-gray-850: 234 234 234;
--nb-gray-900: 238 238 238;
--nb-gray-910: 240 240 240;
--nb-gray-920: 243 243 243;
--nb-gray-925: 245 245 245;
--nb-gray-930: 246 246 246;
--nb-gray-935: 248 248 248;
--nb-gray-940: 250 250 250;
--nb-gray-950: 252 252 252;
--nb-gray-960: 255 255 255;
/* Loading-skeleton colours, consumed by ThemeProvider's SkeletonTheme. */
--skeleton-base: 238 238 238;
--skeleton-highlight: 247 247 247;
}
.dark {
/* nb-gray ORIGINAL ramp (dark mode) */
--nb-gray-DEFAULT: 24 26 29;
--nb-gray-50: 244 246 247;
--nb-gray-100: 228 231 233;
--nb-gray-200: 203 210 214;
--nb-gray-250: 183 192 198;
--nb-gray-300: 163 173 181;
--nb-gray-350: 143 156 168;
--nb-gray-400: 124 137 148;
--nb-gray-500: 97 110 121;
--nb-gray-600: 83 93 103;
--nb-gray-700: 71 78 87;
--nb-gray-800: 63 68 75;
--nb-gray-850: 54 59 64;
--nb-gray-900: 46 50 56;
--nb-gray-910: 43 47 51;
--nb-gray-920: 37 40 45;
--nb-gray-925: 30 33 35;
--nb-gray-930: 37 40 44;
--nb-gray-935: 31 33 36;
--nb-gray-940: 28 30 33;
--nb-gray-950: 24 26 29;
--nb-gray-960: 22 24 27;
/* Dark skeletons keep their existing nb-gray-920 / nb-gray-850 values. */
--skeleton-base: 37 40 45;
--skeleton-highlight: 54 59 64;
}
html{
@@ -75,13 +12,13 @@ html{
}
h1 {
@apply text-2xl font-medium text-nb-gray-100 my-1;
@apply text-2xl font-medium text-gray-700 dark:text-nb-gray-100 my-1;
}
h2 {
@apply text-xl font-medium text-nb-gray-100 my-1;
@apply text-xl font-medium text-gray-700 dark:text-nb-gray-100 my-1;
}
p {
@apply font-light tracking-wide text-nb-gray-200 text-sm;
@apply font-light tracking-wide text-gray-700 dark:text-zinc-50 text-sm;
}
.p-default {
@@ -158,7 +95,7 @@ p {
}
.timescape input:focus {
@apply bg-nb-gray-700 text-nb-gray-50 rounded py-1 px-0.5 border-0 outline-0;
@apply bg-nb-gray-700 text-white rounded py-1 px-0.5 border-0 outline-0;
}
.timescape .separator {
+6 -6
View File
@@ -111,7 +111,7 @@ function InviteAcceptContent() {
<Clock className="w-8 h-8 text-yellow-500" />
</div>
</div>
<h1 className="text-2xl font-semibold dark:text-white text-nb-gray-900 mb-2">
<h1 className="text-2xl font-semibold text-white mb-2">
Too Many Requests
</h1>
<Paragraph className="text-nb-gray-400 text-base">
@@ -138,7 +138,7 @@ function InviteAcceptContent() {
<AlertCircle className="w-8 h-8 text-red-500" />
</div>
</div>
<h1 className="text-2xl font-semibold dark:text-white text-nb-gray-900 mb-2">
<h1 className="text-2xl font-semibold text-white mb-2">
Invalid Invite
</h1>
<Paragraph className="text-nb-gray-400 text-base">
@@ -166,7 +166,7 @@ function InviteAcceptContent() {
<CheckCircle2 className="w-8 h-8 text-green-500" />
</div>
</div>
<h1 className="text-2xl font-semibold dark:text-white text-nb-gray-900 mb-2">
<h1 className="text-2xl font-semibold text-white mb-2">
Account Created!
</h1>
<Paragraph className="text-nb-gray-400">
@@ -194,7 +194,7 @@ function InviteAcceptContent() {
<AlertCircle className="w-8 h-8 text-yellow-500" />
</div>
</div>
<h1 className="text-2xl font-semibold dark:text-white text-nb-gray-900 mb-2">
<h1 className="text-2xl font-semibold text-white mb-2">
Invite Expired
</h1>
<Paragraph className="text-nb-gray-400">
@@ -221,7 +221,7 @@ function InviteAcceptContent() {
</div>
<div className="text-center mb-8">
<h1 className="text-2xl font-semibold dark:text-white text-nb-gray-900 mb-2">
<h1 className="text-2xl font-semibold text-white mb-2">
Welcome to NetBird
</h1>
<p className="dark:text-nb-gray-400 text-nb-gray-500 text-base">
@@ -235,7 +235,7 @@ function InviteAcceptContent() {
<User2 className="w-5 h-5 text-nb-gray-400" />
</div>
<div>
<div className="dark:text-white text-nb-gray-900 font-medium">{inviteInfo.name}</div>
<div className="text-white font-medium">{inviteInfo.name}</div>
<div className="text-nb-gray-400 text-sm flex items-center gap-1">
<Mail className="w-3 h-3" />
{inviteInfo.email}
-19
View File
@@ -1,19 +0,0 @@
<svg width="133" height="23" viewBox="0 0 133 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_0_3)">
<path d="M46.9438 7.5013C48.1229 8.64688 48.7082 10.3025 48.7082 12.4683V21.6663H46.1411V12.8362C46.1411 11.2809 45.7481 10.0851 44.9704 9.26566C44.1928 8.43783 43.1308 8.0281 41.7846 8.0281C40.4383 8.0281 39.3345 8.45455 38.5234 9.30747C37.7123 10.1604 37.3109 11.4063 37.3109 13.0369V21.6663H34.7188V6.06305H37.3109V8.28732C37.821 7.49294 38.5234 6.87416 39.4014 6.43934C40.2878 6.00452 41.2578 5.78711 42.3197 5.78711C44.2179 5.78711 45.7565 6.36408 46.9355 7.50966L46.9438 7.5013Z" fill="#1f2124"/>
<path d="M67.1048 14.8344H54.6288C54.7208 16.373 55.2476 17.5771 56.2092 18.4384C57.1708 19.2997 58.3331 19.7345 59.6961 19.7345C60.8166 19.7345 61.7531 19.4753 62.4973 18.9485C63.2499 18.4301 63.7767 17.7277 64.0777 16.858H66.8706C66.4525 18.3548 65.6163 19.5756 64.3621 20.5205C63.1078 21.4571 61.5525 21.9337 59.6878 21.9337C58.2077 21.9337 56.8865 21.5992 55.7159 20.9386C54.5452 20.278 53.6337 19.3331 52.9648 18.1039C52.2958 16.8831 51.9697 15.4616 51.9697 13.8477C51.9697 12.2339 52.2958 10.8207 52.9397 9.60825C53.5836 8.39578 54.495 7.45924 55.6573 6.80702C56.828 6.15479 58.1659 5.82031 59.6878 5.82031C61.2096 5.82031 62.4806 6.14643 63.6178 6.79029C64.7551 7.43416 65.6331 8.32052 66.2518 9.44938C66.8706 10.5782 67.18 11.8576 67.18 13.2791C67.18 13.7725 67.1549 14.2909 67.0964 14.8428L67.1048 14.8344ZM63.8603 10.1769C63.4255 9.4661 62.8318 8.92258 62.0793 8.55465C61.3267 8.18673 60.4989 8.00277 59.5874 8.00277C58.2746 8.00277 57.1625 8.42086 56.2427 9.25705C55.3228 10.0932 54.796 11.2472 54.6623 12.7356H64.5126C64.5126 11.7489 64.2952 10.896 63.8603 10.1852V10.1769Z" fill="#1f2124"/>
<path d="M73.7695 8.20355V17.4016C73.7695 18.1626 73.9284 18.6977 74.2545 19.0071C74.5806 19.3165 75.1409 19.4754 75.9352 19.4754H77.8418V21.6662H75.5088C74.0622 21.6662 72.9835 21.3317 72.2644 20.6711C71.5452 20.0105 71.1857 18.9151 71.1857 17.3933V8.19519H69.1621V6.0629H71.1857V2.13281H73.7779V6.0629H77.8501V8.19519H73.7779L73.7695 8.20355Z" fill="#1f2124"/>
<path d="M85.9022 6.68902C86.9307 6.10369 88.093 5.80266 89.4058 5.80266C90.8106 5.80266 92.0732 6.13714 93.1937 6.79773C94.3142 7.46668 95.2006 8.39485 95.8444 9.59896C96.4883 10.8031 96.8144 12.2079 96.8144 13.7966C96.8144 15.3854 96.4883 16.7818 95.8444 18.011C95.2006 19.2486 94.3142 20.2018 93.1854 20.8875C92.0565 21.5732 90.7939 21.916 89.4141 21.916C88.0344 21.916 86.8805 21.6234 85.8687 21.0297C84.8569 20.4443 84.0876 19.6918 83.5775 18.7803V21.6568H80.9854V0.601562H83.5775V8.97182C84.1127 8.04365 84.8904 7.28272 85.9105 6.69738L85.9022 6.68902ZM93.4529 10.7362C92.9763 9.86654 92.3408 9.19759 91.5297 8.74605C90.7186 8.29451 89.8322 8.06037 88.8706 8.06037C87.909 8.06037 87.0394 8.29451 86.2366 8.75441C85.4255 9.22268 84.7817 9.89163 84.2967 10.778C83.8117 11.6643 83.5692 12.6845 83.5692 13.8384C83.5692 14.9924 83.8117 16.046 84.2967 16.9323C84.7817 17.8187 85.4255 18.4877 86.2366 18.9559C87.0394 19.4242 87.9174 19.65 88.8706 19.65C89.8239 19.65 90.727 19.4158 91.5297 18.9559C92.3324 18.4877 92.9763 17.8187 93.4529 16.9323C93.9296 16.046 94.1637 15.0091 94.1637 13.8134C94.1637 12.6176 93.9296 11.6142 93.4529 10.7362Z" fill="#1f2124"/>
<path d="M100.318 3.01864C99.9749 2.67581 99.8076 2.25771 99.8076 1.76436C99.8076 1.27101 99.9749 0.852913 100.318 0.510076C100.661 0.167238 101.079 0 101.572 0C102.065 0 102.45 0.167238 102.784 0.510076C103.119 0.852913 103.286 1.27101 103.286 1.76436C103.286 2.25771 103.119 2.67581 102.784 3.01864C102.45 3.36148 102.049 3.52872 101.572 3.52872C101.095 3.52872 100.661 3.36148 100.318 3.01864ZM102.826 6.06237V21.6657H100.234V6.06237H102.826Z" fill="#1f2124"/>
<path d="M111.773 6.52155C112.617 6.0282 113.646 5.77734 114.867 5.77734V8.45315H114.181C111.28 8.45315 109.825 10.0252 109.825 13.1776V21.6649H107.232V6.06165H109.825V8.5953C110.276 7.70058 110.928 7.00654 111.773 6.51319V6.52155Z" fill="#1f2124"/>
<path d="M117.861 9.60732C118.505 8.40321 119.391 7.46668 120.52 6.80609C121.649 6.1455 122.92 5.81102 124.325 5.81102C125.537 5.81102 126.666 6.09533 127.711 6.64721C128.757 7.20746 129.551 7.94331 130.103 8.85475V0.601562H132.72V21.6735H130.103V18.7385C129.593 19.6667 128.832 20.436 127.828 21.0297C126.825 21.6317 125.646 21.9244 124.3 21.9244C122.953 21.9244 121.657 21.5816 120.528 20.8959C119.4 20.2102 118.513 19.257 117.869 18.0194C117.226 16.7818 116.899 15.377 116.899 13.805C116.899 12.233 117.226 10.8114 117.869 9.60732H117.861ZM129.392 10.7613C128.915 9.89163 128.28 9.22268 127.469 8.75441C126.658 8.28614 125.771 8.06037 124.81 8.06037C123.848 8.06037 122.962 8.28614 122.159 8.74605C121.356 9.20595 120.729 9.86654 120.253 10.7362C119.776 11.6058 119.542 12.6343 119.542 13.8134C119.542 14.9924 119.776 16.046 120.253 16.9323C120.729 17.8187 121.365 18.4877 122.159 18.9559C122.953 19.4242 123.84 19.65 124.81 19.65C125.78 19.65 126.666 19.4158 127.469 18.9559C128.272 18.4877 128.915 17.8187 129.392 16.9323C129.869 16.046 130.103 15.0175 130.103 13.8384C130.103 12.6594 129.869 11.6393 129.392 10.7613Z" fill="#1f2124"/>
<path d="M21.4651 0.568359C17.8193 0.902835 16.0047 3.00167 15.3191 4.06363L4.66602 22.5183H17.5182L30.1949 0.568359H21.4651Z" fill="#F68330"/>
<path d="M17.5265 22.5187L0 3.9302C0 3.9302 19.8177 -1.39633 21.7493 15.2188L17.5265 22.5187Z" fill="#F68330"/>
<path d="M14.9255 4.75055L9.54883 14.0657L17.5177 22.5196L21.7405 15.2029C21.0715 9.49174 18.287 6.37276 14.9255 4.74219" fill="#F35E32"/>
</g>
<defs>
<clipPath id="clip0_0_3">
<rect width="132.72" height="22.5186" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

@@ -143,7 +143,7 @@ const PolicyListItem = ({
<Link href={`/access-control?id=${policy?.id}`}>
<span
className={
"flex gap-1 underline underline-offset-4 decoration-dotted text-nb-gray-100 hover:text-nb-gray-50"
"flex gap-1 underline underline-offset-4 decoration-dotted text-nb-gray-100 hover:text-white"
}
>
{policy?.name}
+22 -48
View File
@@ -17,43 +17,21 @@ const variants = cva("", {
blue: [
"bg-sky-100 border-sky-500 text-sky-800 border border-transparent",
],
blueDark: [
"bg-sky-100 border-sky-500 text-sky-800 border dark:bg-sky-900 dark:text-white",
],
"blue-darker": [
"bg-sky-100 border-sky-500 text-sky-800 border dark:bg-sky-900 dark:text-white",
],
red: [
"bg-red-100 border-red-500 border text-red-700 dark:bg-red-950/40 dark:text-red-500",
],
purple: [
"bg-purple-100 border-purple-500 border text-purple-700 dark:bg-purple-950/50 dark:text-purple-500",
],
yellow: [
"bg-yellow-100 border-yellow-500 border text-yellow-700 dark:bg-yellow-950 dark:text-yellow-400",
],
gray: [
"bg-white border-nb-gray-700 text-nb-gray-300 border",
"dark:bg-nb-gray-930/60 dark:border-nb-gray-800/40",
],
lightGray: [
"bg-white border-nb-gray-700 text-nb-gray-200 border",
"dark:bg-nb-gray-910 dark:border-nb-gray-900",
],
blueDark: ["bg-sky-900 border-sky-500 text-white border"],
"blue-darker": ["bg-sky-900 border-sky-500 text-white border"],
red: ["bg-red-950/40 border-red-500 border text-red-500"],
purple: ["bg-purple-950/50 border-purple-500 border text-purple-500"],
yellow: ["bg-yellow-950 border-yellow-500 border text-yellow-400"],
gray: ["bg-nb-gray-930/60 border-nb-gray-800/40 text-nb-gray-300 border"],
lightGray: ["bg-nb-gray-910 text-nb-gray-200 border border-nb-gray-900"],
grayer: [
"bg-white border-nb-gray-700 text-nb-gray-300 border",
"dark:bg-nb-gray-900/40 dark:border-nb-gray-800/40",
"bg-nb-gray-900/40 border-nb-gray-800/40 text-nb-gray-300 border",
],
"gray-ghost": [
"bg-white border-nb-gray-700 text-nb-gray-300 border",
"dark:bg-nb-gray-900 dark:border-nb-gray-800/50",
],
green: [
"bg-green-100 border-green-500 border text-green-700 dark:bg-green-950 dark:text-green-400",
],
netbird: [
"bg-netbird-100 border-netbird-500 border text-netbird-700 dark:bg-netbird-950 dark:text-netbird-500",
"bg-nb-gray-900 border-nb-gray-800 text-nb-gray-300 border border-nb-gray-800/50",
],
green: ["bg-green-950 border-green-500 border text-green-400"],
netbird: ["bg-netbird-950 border-netbird-500 border text-netbird-500"],
},
size: {
default: "text-[0.75rem] py-1.5 px-3",
@@ -62,21 +40,17 @@ const variants = cva("", {
hover: {
none: [],
blue: ["hover:bg-sky-200"],
purple: ["hover:bg-purple-200 dark:hover:bg-purple-950/40"],
yellow: ["hover:bg-yellow-200 dark:hover:bg-yellow-950/40"],
blueDark: ["hover:bg-sky-200 dark:hover:bg-sky-800"],
"blue-darker": ["hover:bg-sky-200 dark:hover:bg-sky-800"],
red: ["hover:bg-red-200 dark:hover:bg-red-950/40"],
/* Light mode hovers bg-neutral-100 to match Button's secondary variant —
white-based chips and buttons sit side by side in table rows. */
gray: ["hover:bg-neutral-100 dark:hover:bg-nb-gray-900"],
lightGray: ["hover:bg-neutral-100 dark:hover:bg-nb-gray-900"],
grayer: ["hover:bg-neutral-100 dark:hover:bg-nb-gray-900"],
"gray-ghost": [
"hover:bg-neutral-100 dark:hover:bg-nb-gray-800 cursor-pointer",
],
green: ["hover:bg-green-200 dark:hover:bg-green-950/50"],
netbird: ["hover:bg-netbird-200 dark:hover:bg-netbird-950/50"],
purple: ["hover:bg-purple-950/40"],
yellow: ["hover:bg-yellow-950/40"],
blueDark: ["hover:bg-sky-800"],
"blue-darker": ["hover:bg-sky-800"],
red: ["hover:bg-red-950/40"],
gray: ["hover:bg-nb-gray-900"],
lightGray: ["hover:bg-nb-gray-900"],
grayer: ["hover:bg-nb-gray-900"],
"gray-ghost": ["hover:bg-nb-gray-800 cursor-pointer"],
green: ["hover:bg-green-950/50"],
netbird: ["hover:bg-netbird-950/50"],
},
},
});
+19 -22
View File
@@ -24,65 +24,65 @@ export const buttonVariants = cva(
variants: {
variant: {
default: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
"dark:focus:ring-zinc-800/50 dark:bg-nb-gray dark:text-gray-400 dark:border-gray-700/30 dark:hover:text-white dark:hover:bg-zinc-800/50",
],
primary: [
"border-transparent",
"dark:focus:ring-netbird-600/50 dark:ring-offset-neutral-950/50 enabled:dark:bg-netbird disabled:dark:bg-nb-gray-910 dark:text-gray-100 enabled:dark:hover:text-white enabled:dark:hover:bg-netbird-500/80",
"enabled:bg-netbird enabled:text-white enabled:focus:ring-netbird-400/50 enabled:hover:bg-netbird-500",
],
secondary: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
"dark:bg-nb-gray-920 dark:text-gray-400 dark:border-gray-700/40 dark:hover:text-white dark:hover:bg-nb-gray-910",
],
secondaryLighter: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
"dark:bg-nb-gray-900/70 dark:text-gray-400 dark:border-gray-700/70 dark:hover:text-white dark:hover:bg-nb-gray-800/60",
],
input: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
"dark:bg-nb-gray-900 dark:text-gray-400 dark:border-nb-gray-700 dark:hover:bg-nb-gray-900/80",
],
dropdown: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
"dark:bg-nb-gray-900/40 dark:text-gray-400 dark:border-nb-gray-900 dark:hover:bg-nb-gray-900/50",
],
dotted: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900 border-dashed",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900 border-dashed",
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
"dark:bg-nb-gray-900/30 dark:text-gray-400 dark:border-gray-500/40 dark:hover:text-white dark:hover:bg-nb-gray-900/50",
],
tertiary: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
"dark:focus:ring-zinc-800/50 dark:bg-white dark:text-gray-800 dark:border-gray-700/40 dark:hover:bg-neutral-200 disabled:dark:bg-nb-gray-920 disabled:dark:text-nb-gray-300",
],
white: [
"focus:ring-white/50 bg-white text-neutral-800 border-white outline-none hover:bg-neutral-200 disabled:dark:bg-nb-gray-920 disabled:dark:text-nb-gray-300",
"focus:ring-white/50 bg-white text-gray-800 border-white outline-none hover:bg-neutral-200 disabled:dark:bg-nb-gray-920 disabled:dark:text-nb-gray-300",
"disabled:dark:bg-nb-gray-900 disabled:dark:text-nb-gray-300 disabled:dark:border-nb-gray-900",
],
outline: [
"bg-white hover:text-black focus:ring-neutral-200/50 hover:bg-neutral-100 border-nb-gray-700 text-neutral-900",
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
"dark:focus:ring-zinc-800/50 dark:bg-transparent dark:text-netbird dark:border-netbird dark:hover:bg-nb-gray-900/30",
],
"danger-outline": [
"bg-transparent text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400 focus:ring-red-500/30",
"enabled:dark:focus:ring-red-800/20 enabled:dark:focus:bg-red-950/40 enabled:hover:dark:bg-red-950/50 enabled:dark:hover:border-red-800/50 dark:bg-transparent dark:text-red-500 dark:border-transparent",
"", // TODO - add danger button styles for light mode
"enabled:dark:focus:ring-red-800/20 enabled:dark:focus:bg-red-950/40 enabled:hover:dark:bg-red-950/50 enabled:dark:hover:border-red-800/50 dark:bg-transparent dark:text-red-500",
"",
],
"danger-text": [
"bg-transparent text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-600 border-transparent !px-0 !shadow-none !py-0 focus:ring-red-500/30 ring-offset-nb-gray-950/50 rounded-sm",
"dark:bg-transparent dark:text-red-500 dark:hover:text-red-600 dark:border-transparent !px-0 !shadow-none !py-0 focus:ring-red-500/30 dark:ring-offset-neutral-950/50 rounded-sm",
],
"default-outline": [
"ring-offset-nb-gray-950/50 focus:ring-nb-gray-500/20",
"bg-transparent text-nb-gray-400 border-transparent hover:text-nb-gray-50 hover:bg-nb-gray-900/30 hover:border-nb-gray-800/50",
"data-[state=open]:text-nb-gray-50 data-[state=open]:bg-nb-gray-900/30 data-[state=open]:border-nb-gray-800/50",
"dark:ring-offset-nb-gray-950/50 dark:focus:ring-nb-gray-500/20",
"dark:bg-transparent dark:text-nb-gray-400 dark:border-transparent dark:hover:text-white dark:hover:bg-nb-gray-900/30 dark:hover:border-nb-gray-800/50",
"data-[state=open]:dark:text-white data-[state=open]:dark:bg-nb-gray-900/30 data-[state=open]:dark:border-nb-gray-800/50",
],
danger: [
"enabled:bg-red-600 enabled:text-white enabled:hover:bg-red-700 enabled:focus:bg-red-700 enabled:focus:ring-red-400/50 border-red-600 disabled:bg-red-300 disabled:text-white",
"", // TODO - add danger button styles for light mode
"dark:focus:ring-red-700/20 dark:focus:bg-red-700 hover:dark:bg-red-700 dark:hover:border-red-800/50 dark:bg-red-600 dark:text-red-100",
],
},
@@ -98,11 +98,8 @@ export const buttonVariants = cva(
false: "",
},
border: {
0: "border-0",
/* No border-transparent here: it compiles after the variants' border
colors and would override them, leaving light mode outline-less.
Variants without a border color set border-transparent themselves. */
1: "border",
0: "border",
1: "border border-transparent",
2: "border border-t-0 border-b-0",
},
},
Loaded 20 of 189 files, more files were not shown because too many files have changed in this diff. Show more