-
Notifications
You must be signed in to change notification settings - Fork 180
Configure Identity Providers in the UI #523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 29 commits
4328635
153c795
2131c06
205573f
0761955
9271f6d
f14ef91
876f803
a38d364
92528d8
cdbd9ba
07f538b
cb93781
ee18fab
561826f
34214df
46d95ff
7775709
375c60b
4b6c02c
0dd2f2e
ed579f1
9f830e8
b961966
90a0ed6
506fb65
fb003a9
b401a49
99b4f0e
6adc5b2
02dce01
5826b3f
aec7364
32eef30
3c6dc92
49cf8fc
b0b253f
340fe18
2324d53
9270b60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { globalMetaTitle } from "@utils/meta"; | ||
| import type { Metadata } from "next"; | ||
| import BlankLayout from "@/layouts/BlankLayout"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: `Instance Setup - ${globalMetaTitle}`, | ||
| }; | ||
| export default BlankLayout; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| "use client"; | ||
|
|
||
| import InstanceSetupWizard from "@/modules/instance-setup/InstanceSetupWizard"; | ||
|
|
||
| export default function SetupPage() { | ||
| return <InstanceSetupWizard />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { iconProperties, IconProps } from "@/assets/icons/IconProperties"; | ||
|
|
||
| export default function MicrosoftIcon(props: Readonly<IconProps>) { | ||
| return ( | ||
| <svg | ||
| viewBox="0 0 221 221" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| {...iconProperties(props)} | ||
| > | ||
| <path fill="#F1511B" d="M104.868 104.868H0V0h104.868z" /> | ||
| <path fill="#80CC28" d="M220.654 104.868H115.788V0h104.866z" /> | ||
| <path fill="#00ADEF" d="M104.865 220.695H0V115.828h104.865z" /> | ||
| <path fill="#FBBC09" d="M220.654 220.695H115.788V115.828h104.866z" /> | ||
| </svg> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { iconProperties, IconProps } from "@/assets/icons/IconProperties"; | ||
|
|
||
| export default function PocketIdIcon(props: Readonly<IconProps>) { | ||
| return ( | ||
| <svg | ||
| viewBox="0 0 512 512" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| {...iconProperties(props)} | ||
| > | ||
| <circle cx="256" cy="256" r="256" fill="#fff" /> | ||
| <path | ||
| d="M268.6 102.4c64.4 0 116.8 52.4 116.8 116.7 0 25.3-8 49.4-23 69.6-14.8 19.9-35 34.3-58.4 41.7l-6.5 2-15.5-76.2 4.3-2c14-6.7 23-21.1 23-36.6 0-22.4-18.2-40.6-40.6-40.6S228 195.2 228 217.6c0 15.5 9 29.8 23 36.6l4.2 2-25 153.4h-69.5V102.4z" | ||
| fill="#191919" | ||
| /> | ||
| </svg> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { iconProperties, IconProps } from "@/assets/icons/IconProperties"; | ||
|
|
||
| export default function ZitadelIcon(props: Readonly<IconProps>) { | ||
| return ( | ||
| <svg | ||
| viewBox="0 0 80 79" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| {...iconProperties(props)} | ||
| > | ||
| <defs> | ||
| <linearGradient | ||
| id="zitadel-grad" | ||
| x1="3.86" | ||
| x2="76.88" | ||
| y1="47.89" | ||
| y2="47.89" | ||
| gradientUnits="userSpaceOnUse" | ||
| > | ||
| <stop stopColor="#FF8F00" /> | ||
| <stop offset="1" stopColor="#FE00FF" /> | ||
| </linearGradient> | ||
| </defs> | ||
| <path | ||
| fill="url(#zitadel-grad)" | ||
| fillRule="evenodd" | ||
| d="M17.12 39.17l1.42 5.32-6.68 6.68 9.12 2.44 1.43 5.32-19.77-5.3L17.12 39.17zM58.82 22.41l-5.32-1.43-2.44-9.12-6.68 6.68-5.32-1.43 14.47-14.47 5.3 19.77zM52.65 67.11l3.89-3.89 9.12 2.44-2.44-9.12 3.9-3.9 5.29 19.77-19.76-5.3zM36.43 69.54l-1.18-12.07 8.23 2.21-7.05 9.86zM23 23.84l5.02 11.04 6.02-6.02L23 23.84zM69.32 36.2l-12.07-1.18 2.2 8.23 9.87-7.05z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| "use client"; | ||
|
|
||
| import { usePathname, useRouter } from "next/navigation"; | ||
| import React, { createContext, useContext, useEffect, useState } from "react"; | ||
| import FullScreenLoading from "@/components/ui/FullScreenLoading"; | ||
| import { fetchInstanceStatus } from "@/utils/unauthenticatedApi"; | ||
| import { isNetBirdHosted } from "@utils/netbird"; | ||
|
|
||
| interface InstanceSetupContextType { | ||
| setupRequired: boolean; | ||
| loading: boolean; | ||
| } | ||
|
|
||
| const InstanceSetupContext = createContext<InstanceSetupContextType>({ | ||
| setupRequired: false, | ||
| loading: true, | ||
| }); | ||
|
|
||
| export const useInstanceSetup = () => useContext(InstanceSetupContext); | ||
|
|
||
| // Check if we're in an OIDC callback flow (hash-based routing) | ||
| const isOIDCCallback = () => { | ||
| if (typeof window === "undefined") return false; | ||
| const hash = window.location.hash; | ||
| return hash.startsWith("#callback") || hash.startsWith("#silent-callback"); | ||
| }; | ||
|
|
||
| export default function InstanceSetupProvider({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| const [setupRequired, setSetupRequired] = useState(false); | ||
| const [loading, setLoading] = useState(true); | ||
| const router = useRouter(); | ||
| const pathname = usePathname(); | ||
|
|
||
| // Routes that don't need setup check | ||
| const bypassRoutes = ["/setup", "/install"]; | ||
| const shouldBypass = | ||
| bypassRoutes.includes(pathname) || isOIDCCallback(); | ||
|
|
||
| // Skip setup check for NetBird hosted (cloud) deployments | ||
| const isCloud = isNetBirdHosted(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential hydration mismatch with
🔎 Suggested fix export default function InstanceSetupProvider({
children,
}: {
children: React.ReactNode;
}) {
const [setupRequired, setSetupRequired] = useState(false);
const [loading, setLoading] = useState(true);
+ const [isCloud, setIsCloud] = useState(false);
const router = useRouter();
const pathname = usePathname();
// Routes that don't need setup check
const bypassRoutes = ["/setup", "/install"];
const shouldBypass =
bypassRoutes.includes(pathname) || isOIDCCallback();
- // Skip setup check for NetBird hosted (cloud) deployments
- const isCloud = isNetBirdHosted();
+ // Set cloud status after mount to avoid hydration mismatch
+ useEffect(() => {
+ setIsCloud(isNetBirdHosted());
+ }, []);
// Check instance status on mount
useEffect(() => {
🤖 Prompt for AI Agents |
||
|
|
||
| // Check instance status on mount | ||
| useEffect(() => { | ||
| // Skip check for cloud deployments or bypass routes | ||
| if (isCloud || shouldBypass) { | ||
| setLoading(false); | ||
| return; | ||
| } | ||
|
|
||
| // Check if instance setup is required | ||
| fetchInstanceStatus() | ||
| .then((status) => { | ||
| if (status.setup_required) { | ||
| setSetupRequired(true); | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| // If API fails (e.g., endpoint doesn't exist on older versions), | ||
| // assume setup is not required and continue normally | ||
| console.warn("Instance status check failed:", err); | ||
| }) | ||
| .finally(() => { | ||
| setLoading(false); | ||
| }); | ||
| }, [shouldBypass, isCloud]); | ||
|
|
||
| // Handle redirect separately to avoid setState during render conflicts | ||
| useEffect(() => { | ||
| if (setupRequired && !shouldBypass) { | ||
| router.replace("/setup"); | ||
| } | ||
| }, [setupRequired, shouldBypass, router]); | ||
|
|
||
| // Show loading while checking (only for non-cloud, non-bypass routes) | ||
| if (loading && !shouldBypass && !isCloud) { | ||
| return <FullScreenLoading />; | ||
| } | ||
|
|
||
| // If setup required and not on setup page, wait for redirect | ||
| if (setupRequired && !shouldBypass) { | ||
| return <FullScreenLoading />; | ||
| } | ||
|
|
||
| return ( | ||
| <InstanceSetupContext.Provider value={{ setupRequired, loading }}> | ||
| {children} | ||
| </InstanceSetupContext.Provider> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| export interface InstanceStatus { | ||
| setup_required: boolean; | ||
| } | ||
|
|
||
| export interface SetupRequest { | ||
| email: string; | ||
| password: string; | ||
| name: string; | ||
| } | ||
|
|
||
| export interface SetupResponse { | ||
| user_id: string; | ||
| email: string; | ||
| } | ||
|
|
||
| export interface ApiError { | ||
| code: number; | ||
| message: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical accessibility issues with the password toggle button.
The password toggle implementation has two major accessibility problems:
tabIndex={-1}excludes keyboard users from accessing the toggle. Keyboard-only users (e.g., those with motor disabilities) cannot toggle password visibility.aria-labelprevents screen reader users from understanding the button's purpose.These violations block users with disabilities from using a core feature.
🔎 Proposed accessibility fixes
const passwordToggle = isPasswordType && showPasswordToggle ? ( <button type="button" onClick={() => setShowPassword(!showPassword)} className="text-nb-gray-400 hover:text-nb-gray-200 transition-colors" - tabIndex={-1} + aria-label={showPassword ? "Hide password" : "Show password"} > {showPassword ? <EyeOff size={16} /> : <Eye size={16} />} </button> ) : null;Removing
tabIndex={-1}allows keyboard navigation, and addingaria-labelprovides screen reader context.📝 Committable suggestion
🤖 Prompt for AI Agents