@mehdad67/apitogo 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cli.js +51 -36
- package/dist/declarations/config/config.d.ts +3 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +12 -0
- package/dist/declarations/lib/authentication/components/ProductionUnlockPage.d.ts +1 -0
- package/dist/declarations/lib/authentication/hook.d.ts +2 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +16 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +9 -0
- package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +36 -0
- package/dist/declarations/lib/authentication/state.d.ts +3 -0
- package/dist/declarations/lib/ui/ActionButton.d.ts +1 -1
- package/dist/declarations/lib/ui/Command.d.ts +1 -1
- package/dist/declarations/lib/util/MdxComponents.d.ts +1 -1
- package/dist/flat-config.d.ts +6 -0
- package/docs/configuration/authentication.md +32 -2
- package/package.json +6 -1
- package/src/app/main.tsx +2 -2
- package/src/config/config.ts +4 -0
- package/src/config/validators/ZudokuConfig.ts +7 -0
- package/src/lib/auth/issuer.ts +3 -0
- package/src/lib/authentication/components/CallbackHandler.tsx +1 -1
- package/src/lib/authentication/components/ProductionUnlockPage.tsx +27 -0
- package/src/lib/authentication/components/SignIn.tsx +2 -2
- package/src/lib/authentication/components/SignUp.tsx +2 -2
- package/src/lib/authentication/hook.ts +2 -0
- package/src/lib/authentication/providers/clerk.tsx +1 -1
- package/src/lib/authentication/providers/dev-portal-constants.ts +21 -0
- package/src/lib/authentication/providers/dev-portal-utils.ts +107 -0
- package/src/lib/authentication/providers/dev-portal.tsx +218 -0
- package/src/lib/authentication/state.ts +12 -0
- package/src/lib/authentication/ui/AuthCard.tsx +1 -1
- package/src/lib/authentication/ui/EmailLinkCallbackUi.tsx +5 -5
- package/src/lib/authentication/ui/EmailLinkSentUi.tsx +3 -3
- package/src/lib/authentication/ui/EmailLinkSignInUi.tsx +4 -4
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +4 -4
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +6 -6
- package/src/lib/components/AiAssistantMenuItems.tsx +1 -1
- package/src/lib/components/Autocomplete.tsx +2 -2
- package/src/lib/components/DeveloperHint.tsx +1 -1
- package/src/lib/components/Header.tsx +4 -4
- package/src/lib/components/LandingLayout.tsx +1 -1
- package/src/lib/components/Layout.tsx +1 -1
- package/src/lib/components/Main.tsx +1 -1
- package/src/lib/components/Mermaid.tsx +1 -1
- package/src/lib/components/MobileTopNavigation.tsx +6 -5
- package/src/lib/components/MultiSelect.tsx +1 -1
- package/src/lib/components/TopNavigation.tsx +1 -1
- package/src/lib/components/context/ZudokuContext.ts +1 -1
- package/src/lib/components/navigation/NavigationCategory.tsx +1 -1
- package/src/lib/components/navigation/NavigationFilterInput.tsx +1 -1
- package/src/lib/components/navigation/NavigationItem.tsx +2 -2
- package/src/lib/core/RouteGuard.tsx +13 -3
- package/src/lib/errors/ErrorMessage.tsx +2 -2
- package/src/lib/plugins/api-catalog/Catalog.tsx +1 -1
- package/src/lib/plugins/api-keys/CreateApiKey.tsx +4 -4
- package/src/lib/plugins/api-keys/CreateApiKeyDialog.tsx +1 -1
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +3 -3
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +5 -5
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +3 -3
- package/src/lib/plugins/markdown/MdxPage.tsx +3 -3
- package/src/lib/plugins/openapi/ApiHeader.tsx +1 -1
- package/src/lib/plugins/openapi/CollapsibleCode.tsx +2 -2
- package/src/lib/plugins/openapi/DownloadSchemaButton.tsx +3 -3
- package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +2 -2
- package/src/lib/plugins/openapi/OperationListItem.tsx +2 -2
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -2
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +4 -4
- package/src/lib/plugins/openapi/SchemaInfo.tsx +1 -1
- package/src/lib/plugins/openapi/SchemaList.tsx +2 -2
- package/src/lib/plugins/openapi/Sidecar.tsx +4 -4
- package/src/lib/plugins/openapi/SidecarExamples.tsx +2 -2
- package/src/lib/plugins/openapi/components/EnumValues.tsx +1 -1
- package/src/lib/plugins/openapi/components/ResponseContent.tsx +3 -3
- package/src/lib/plugins/openapi/playground/BodyPanel.tsx +4 -4
- package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +1 -1
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +2 -2
- package/src/lib/plugins/openapi/playground/Headers.tsx +3 -3
- package/src/lib/plugins/openapi/playground/IdentityDialog.tsx +5 -5
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +2 -2
- package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +2 -2
- package/src/lib/plugins/openapi/playground/Playground.tsx +4 -4
- package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +3 -3
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +2 -2
- package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +4 -4
- package/src/lib/plugins/openapi/playground/request-panel/MultipartField.tsx +2 -2
- package/src/lib/plugins/openapi/playground/result-panel/AudioPlayer.tsx +1 -1
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +5 -5
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +2 -2
- package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +1 -1
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +2 -2
- package/src/lib/plugins/search-pagefind/IndexingDialog.tsx +2 -2
- package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +4 -4
- package/src/lib/plugins/search-pagefind/ResultList.tsx +1 -1
- package/src/lib/ui/ActionButton.tsx +1 -1
- package/src/lib/ui/Command.tsx +1 -1
- package/src/lib/util/MdxComponents.tsx +2 -2
- package/src/vite/config.ts +4 -4
- package/src/vite/plugin-api-keys.ts +1 -1
- package/src/vite/plugin-api.ts +2 -2
- package/src/vite/plugin-auth.ts +1 -1
- package/src/vite/plugin-component.ts +5 -5
- package/src/vite/plugin-config.ts +1 -1
- package/src/vite/plugin-custom-pages.ts +1 -1
- package/src/vite/plugin-docs.ts +1 -1
- package/src/vite/plugin-mdx.ts +1 -1
- package/src/vite/plugin-modules.ts +2 -2
- package/src/vite/plugin-navigation.ts +1 -1
- package/src/vite/plugin-search.ts +2 -2
- package/src/vite/plugin.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Clerk } from "@clerk/clerk-js";
|
|
2
|
-
import type { ApitogoPlugin } from "@
|
|
2
|
+
import type { ApitogoPlugin } from "@mehdad67/apitogo/plugins";
|
|
3
3
|
import type { ClerkAuthenticationConfig } from "../../../config/config.js";
|
|
4
4
|
import type {
|
|
5
5
|
AuthActionContext,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Placeholder backend URL written by MCP before publish. */
|
|
2
|
+
export const DEV_PORTAL_PLACEHOLDER_API_URL =
|
|
3
|
+
"https://dp-example.azurecontainerapps.io";
|
|
4
|
+
|
|
5
|
+
export type DevPortalAuthMode = "live" | "preview";
|
|
6
|
+
|
|
7
|
+
export type EndUserMeResponse = {
|
|
8
|
+
userId: string;
|
|
9
|
+
displayName: string | null;
|
|
10
|
+
subscriptions: Array<{
|
|
11
|
+
planSku: string;
|
|
12
|
+
status: string;
|
|
13
|
+
entitlementState: string | null;
|
|
14
|
+
currentPeriodEnd: string | null;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type DevPortalAuthStatusResponse = {
|
|
19
|
+
available: boolean;
|
|
20
|
+
oidcConfigured: boolean;
|
|
21
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { DevPortalAuthenticationConfig } from "../../../config/config.js";
|
|
2
|
+
import {
|
|
3
|
+
DEV_PORTAL_PLACEHOLDER_API_URL,
|
|
4
|
+
type DevPortalAuthStatusResponse,
|
|
5
|
+
type EndUserMeResponse,
|
|
6
|
+
} from "./dev-portal-constants.js";
|
|
7
|
+
|
|
8
|
+
declare const VITE_APITOGO_DEV_PORTAL_API_URL: string | undefined;
|
|
9
|
+
|
|
10
|
+
export function resolveDevPortalApiBaseUrl(
|
|
11
|
+
config: Pick<DevPortalAuthenticationConfig, "apiBaseUrl">,
|
|
12
|
+
): string | undefined {
|
|
13
|
+
const fromConfig = config.apiBaseUrl?.trim();
|
|
14
|
+
if (fromConfig) {
|
|
15
|
+
return fromConfig.replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fromEnv =
|
|
19
|
+
typeof VITE_APITOGO_DEV_PORTAL_API_URL === "string"
|
|
20
|
+
? VITE_APITOGO_DEV_PORTAL_API_URL.trim()
|
|
21
|
+
: "";
|
|
22
|
+
|
|
23
|
+
return fromEnv ? fromEnv.replace(/\/+$/, "") : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isPlaceholderDevPortalApiUrl(
|
|
27
|
+
apiBaseUrl: string | undefined,
|
|
28
|
+
): boolean {
|
|
29
|
+
if (!apiBaseUrl) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
apiBaseUrl === DEV_PORTAL_PLACEHOLDER_API_URL ||
|
|
35
|
+
apiBaseUrl.includes("dp-example.azurecontainerapps.io")
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function toAbsoluteReturnUrl(pathOrUrl: string): string {
|
|
40
|
+
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
41
|
+
return pathOrUrl;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof window === "undefined") {
|
|
45
|
+
return pathOrUrl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new URL(pathOrUrl, window.location.origin).href;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildDevPortalLoginUrl(
|
|
52
|
+
apiBaseUrl: string,
|
|
53
|
+
returnUrl: string,
|
|
54
|
+
): string {
|
|
55
|
+
const url = new URL("/api/v1/auth/login", apiBaseUrl);
|
|
56
|
+
url.searchParams.set("returnUrl", toAbsoluteReturnUrl(returnUrl));
|
|
57
|
+
return url.toString();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildDevPortalLogoutUrl(
|
|
61
|
+
apiBaseUrl: string,
|
|
62
|
+
returnUrl: string,
|
|
63
|
+
): string {
|
|
64
|
+
const url = new URL("/api/v1/auth/logout", apiBaseUrl);
|
|
65
|
+
url.searchParams.set("returnUrl", toAbsoluteReturnUrl(returnUrl));
|
|
66
|
+
return url.toString();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function probeDevPortalBackend(
|
|
70
|
+
apiBaseUrl: string,
|
|
71
|
+
fetchImpl: typeof fetch = fetch,
|
|
72
|
+
): Promise<boolean> {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timeout = setTimeout(() => controller.abort(), 8_000);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetchImpl(`${apiBaseUrl}/api/v1/auth/status`, {
|
|
78
|
+
method: "GET",
|
|
79
|
+
headers: { Accept: "application/json" },
|
|
80
|
+
credentials: "include",
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const body = (await response.json()) as DevPortalAuthStatusResponse;
|
|
89
|
+
return body.available === true;
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
} finally {
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function mapEndUserMeToProfile(
|
|
98
|
+
me: EndUserMeResponse,
|
|
99
|
+
): import("../state.js").UserProfile {
|
|
100
|
+
return {
|
|
101
|
+
sub: me.userId,
|
|
102
|
+
email: undefined,
|
|
103
|
+
emailVerified: false,
|
|
104
|
+
name: me.displayName ?? undefined,
|
|
105
|
+
pictureUrl: undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { DevPortalAuthenticationConfig } from "../../../config/config.js";
|
|
2
|
+
import type { ZudokuContext } from "../../core/ZudokuContext.js";
|
|
3
|
+
import type {
|
|
4
|
+
AuthActionContext,
|
|
5
|
+
AuthActionOptions,
|
|
6
|
+
AuthenticationPlugin,
|
|
7
|
+
AuthenticationProviderInitializer,
|
|
8
|
+
} from "../authentication.js";
|
|
9
|
+
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
10
|
+
import { type UserProfile, useAuthState } from "../state.js";
|
|
11
|
+
import type {
|
|
12
|
+
DevPortalAuthMode,
|
|
13
|
+
EndUserMeResponse,
|
|
14
|
+
} from "./dev-portal-constants.js";
|
|
15
|
+
import {
|
|
16
|
+
buildDevPortalLoginUrl,
|
|
17
|
+
buildDevPortalLogoutUrl,
|
|
18
|
+
isPlaceholderDevPortalApiUrl,
|
|
19
|
+
mapEndUserMeToProfile,
|
|
20
|
+
probeDevPortalBackend,
|
|
21
|
+
resolveDevPortalApiBaseUrl,
|
|
22
|
+
} from "./dev-portal-utils.js";
|
|
23
|
+
|
|
24
|
+
export type DevPortalProviderData = {
|
|
25
|
+
type: "dev-portal";
|
|
26
|
+
apiBaseUrl: string;
|
|
27
|
+
authMode: DevPortalAuthMode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
declare module "../state.js" {
|
|
31
|
+
interface ProviderDataRegistry {
|
|
32
|
+
"dev-portal": DevPortalProviderData;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class DevPortalAuthenticationProvider
|
|
37
|
+
extends CoreAuthenticationPlugin
|
|
38
|
+
implements AuthenticationPlugin
|
|
39
|
+
{
|
|
40
|
+
private readonly apiBaseUrl: string | undefined;
|
|
41
|
+
private readonly redirectToAfterSignIn: string | undefined;
|
|
42
|
+
private readonly redirectToAfterSignUp: string | undefined;
|
|
43
|
+
private readonly redirectToAfterSignOut: string;
|
|
44
|
+
private authMode: DevPortalAuthMode = "preview";
|
|
45
|
+
private backendAvailable = false;
|
|
46
|
+
|
|
47
|
+
constructor({
|
|
48
|
+
apiBaseUrl,
|
|
49
|
+
redirectToAfterSignIn,
|
|
50
|
+
redirectToAfterSignUp,
|
|
51
|
+
redirectToAfterSignOut = "/",
|
|
52
|
+
}: DevPortalAuthenticationConfig) {
|
|
53
|
+
super();
|
|
54
|
+
this.apiBaseUrl = resolveDevPortalApiBaseUrl({ apiBaseUrl });
|
|
55
|
+
this.redirectToAfterSignIn = redirectToAfterSignIn;
|
|
56
|
+
this.redirectToAfterSignUp = redirectToAfterSignUp;
|
|
57
|
+
this.redirectToAfterSignOut = redirectToAfterSignOut;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async initialize(_context: ZudokuContext): Promise<void> {
|
|
61
|
+
await this.syncBackendAvailability();
|
|
62
|
+
if (!this.backendAvailable) {
|
|
63
|
+
this.setPreviewState();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await this.refreshUserProfile();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onPageLoad = async () => {
|
|
71
|
+
if (!this.backendAvailable) {
|
|
72
|
+
this.setPreviewState();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await this.refreshUserProfile();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
private async syncBackendAvailability(): Promise<void> {
|
|
80
|
+
if (!this.apiBaseUrl || isPlaceholderDevPortalApiUrl(this.apiBaseUrl)) {
|
|
81
|
+
this.backendAvailable = false;
|
|
82
|
+
this.authMode = "preview";
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.backendAvailable = await probeDevPortalBackend(this.apiBaseUrl);
|
|
87
|
+
this.authMode = this.backendAvailable ? "live" : "preview";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private setPreviewState(): void {
|
|
91
|
+
useAuthState.setState({
|
|
92
|
+
isAuthenticated: false,
|
|
93
|
+
isPending: false,
|
|
94
|
+
profile: null,
|
|
95
|
+
providerData: this.apiBaseUrl
|
|
96
|
+
? {
|
|
97
|
+
type: "dev-portal",
|
|
98
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
99
|
+
authMode: "preview",
|
|
100
|
+
}
|
|
101
|
+
: null,
|
|
102
|
+
isBackendAvailable: false,
|
|
103
|
+
authMode: "preview",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private ensureLiveBackend(): string {
|
|
108
|
+
if (!this.apiBaseUrl || !this.backendAvailable) {
|
|
109
|
+
throw new Error("Dev portal authentication is not available in preview.");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return this.apiBaseUrl;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async refreshUserProfile(): Promise<boolean> {
|
|
116
|
+
if (!this.apiBaseUrl) {
|
|
117
|
+
this.setPreviewState();
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isPlaceholderDevPortalApiUrl(this.apiBaseUrl)) {
|
|
122
|
+
this.setPreviewState();
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this.syncBackendAvailability();
|
|
127
|
+
if (!this.backendAvailable) {
|
|
128
|
+
this.setPreviewState();
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(`${this.apiBaseUrl}/api/v1/auth/me`, {
|
|
134
|
+
method: "GET",
|
|
135
|
+
headers: { Accept: "application/json" },
|
|
136
|
+
credentials: "include",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (response.status === 401) {
|
|
140
|
+
useAuthState.setState({
|
|
141
|
+
isAuthenticated: false,
|
|
142
|
+
isPending: false,
|
|
143
|
+
profile: null,
|
|
144
|
+
providerData: {
|
|
145
|
+
type: "dev-portal",
|
|
146
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
147
|
+
authMode: "live",
|
|
148
|
+
},
|
|
149
|
+
isBackendAvailable: true,
|
|
150
|
+
authMode: "live",
|
|
151
|
+
});
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
this.setPreviewState();
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const me = (await response.json()) as EndUserMeResponse;
|
|
161
|
+
const profile: UserProfile = mapEndUserMeToProfile(me);
|
|
162
|
+
|
|
163
|
+
useAuthState.getState().setLoggedIn({
|
|
164
|
+
profile,
|
|
165
|
+
providerData: {
|
|
166
|
+
type: "dev-portal",
|
|
167
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
168
|
+
authMode: "live",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
useAuthState.setState({
|
|
172
|
+
isBackendAvailable: true,
|
|
173
|
+
authMode: "live",
|
|
174
|
+
});
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
this.setPreviewState();
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async signIn(
|
|
183
|
+
_: AuthActionContext,
|
|
184
|
+
{ redirectTo }: AuthActionOptions = {},
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
const apiBaseUrl = this.ensureLiveBackend();
|
|
187
|
+
const target =
|
|
188
|
+
redirectTo ?? this.redirectToAfterSignIn ?? window.location.href;
|
|
189
|
+
window.location.assign(buildDevPortalLoginUrl(apiBaseUrl, target));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async signUp(
|
|
193
|
+
_: AuthActionContext,
|
|
194
|
+
{ redirectTo }: AuthActionOptions = {},
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
const apiBaseUrl = this.ensureLiveBackend();
|
|
197
|
+
const target =
|
|
198
|
+
redirectTo ?? this.redirectToAfterSignUp ?? window.location.href;
|
|
199
|
+
window.location.assign(buildDevPortalLoginUrl(apiBaseUrl, target));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async signOut(_: AuthActionContext): Promise<void> {
|
|
203
|
+
const apiBaseUrl = this.ensureLiveBackend();
|
|
204
|
+
window.location.assign(
|
|
205
|
+
buildDevPortalLogoutUrl(apiBaseUrl, this.redirectToAfterSignOut),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async signRequest(request: Request): Promise<Request> {
|
|
210
|
+
return request;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const devPortalAuth: AuthenticationProviderInitializer<
|
|
215
|
+
DevPortalAuthenticationConfig
|
|
216
|
+
> = (options) => new DevPortalAuthenticationProvider(options);
|
|
217
|
+
|
|
218
|
+
export default devPortalAuth;
|
|
@@ -21,11 +21,17 @@ export type ProviderData = [keyof ProviderDataRegistry] extends [never]
|
|
|
21
21
|
? unknown
|
|
22
22
|
: ProviderDataRegistry[keyof ProviderDataRegistry];
|
|
23
23
|
|
|
24
|
+
export type AuthMode = "live" | "preview";
|
|
25
|
+
|
|
24
26
|
export interface AuthState {
|
|
25
27
|
isAuthenticated: boolean;
|
|
26
28
|
isPending: boolean;
|
|
27
29
|
profile: UserProfile | null;
|
|
28
30
|
providerData: ProviderData | null;
|
|
31
|
+
/** False when dev-portal backend is unavailable (local preview). Defaults true. */
|
|
32
|
+
isBackendAvailable: boolean;
|
|
33
|
+
/** Live when backend auth is reachable; preview otherwise. */
|
|
34
|
+
authMode: AuthMode;
|
|
29
35
|
setAuthenticationPending: () => void;
|
|
30
36
|
setLoggedOut: () => void;
|
|
31
37
|
setLoggedIn: (args: {
|
|
@@ -41,12 +47,16 @@ export const authState = create<AuthState>()(
|
|
|
41
47
|
isPending: true,
|
|
42
48
|
profile: null,
|
|
43
49
|
providerData: null,
|
|
50
|
+
isBackendAvailable: true,
|
|
51
|
+
authMode: "live",
|
|
44
52
|
setAuthenticationPending: () =>
|
|
45
53
|
set(() => ({
|
|
46
54
|
isAuthenticated: false,
|
|
47
55
|
isPending: false,
|
|
48
56
|
profile: null,
|
|
49
57
|
providerData: null,
|
|
58
|
+
isBackendAvailable: true,
|
|
59
|
+
authMode: "live",
|
|
50
60
|
})),
|
|
51
61
|
setLoggedOut: () =>
|
|
52
62
|
set(() => ({
|
|
@@ -54,6 +64,8 @@ export const authState = create<AuthState>()(
|
|
|
54
64
|
isPending: false,
|
|
55
65
|
profile: null,
|
|
56
66
|
providerData: null,
|
|
67
|
+
isBackendAvailable: true,
|
|
68
|
+
authMode: "live",
|
|
57
69
|
})),
|
|
58
70
|
setLoggedIn: ({ profile, providerData }) =>
|
|
59
71
|
set(() => ({
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Spinner } from "@
|
|
2
|
-
import { ActionButton } from "@
|
|
1
|
+
import { Spinner } from "@mehdad67/apitogo/components";
|
|
2
|
+
import { ActionButton } from "@mehdad67/apitogo/ui/ActionButton.js";
|
|
3
3
|
import {
|
|
4
4
|
Alert,
|
|
5
5
|
AlertDescription,
|
|
6
6
|
AlertTitle,
|
|
7
|
-
} from "@
|
|
7
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
8
8
|
import {
|
|
9
9
|
CardContent,
|
|
10
10
|
CardDescription,
|
|
11
11
|
CardHeader,
|
|
12
12
|
CardTitle,
|
|
13
|
-
} from "@
|
|
14
|
-
import { Input } from "@
|
|
13
|
+
} from "@mehdad67/apitogo/ui/Card.js";
|
|
14
|
+
import { Input } from "@mehdad67/apitogo/ui/Input.js";
|
|
15
15
|
import { useMutation } from "@tanstack/react-query";
|
|
16
16
|
import { useEffect, useRef, useState } from "react";
|
|
17
17
|
import { useNavigate, useSearchParams } from "react-router";
|
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
Alert,
|
|
3
3
|
AlertDescription,
|
|
4
4
|
AlertTitle,
|
|
5
|
-
} from "@
|
|
6
|
-
import { Button } from "@
|
|
5
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
6
|
+
import { Button } from "@mehdad67/apitogo/ui/Button.js";
|
|
7
7
|
import {
|
|
8
8
|
CardContent,
|
|
9
9
|
CardDescription,
|
|
10
10
|
CardHeader,
|
|
11
11
|
CardTitle,
|
|
12
|
-
} from "@
|
|
12
|
+
} from "@mehdad67/apitogo/ui/Card.js";
|
|
13
13
|
import { useMutation } from "@tanstack/react-query";
|
|
14
14
|
import { Mail, RefreshCw } from "lucide-react";
|
|
15
15
|
import { useState } from "react";
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { ActionButton } from "@
|
|
1
|
+
import { ActionButton } from "@mehdad67/apitogo/ui/ActionButton.js";
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
AlertDescription,
|
|
5
5
|
AlertTitle,
|
|
6
|
-
} from "@
|
|
6
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
7
7
|
import {
|
|
8
8
|
CardContent,
|
|
9
9
|
CardDescription,
|
|
10
10
|
CardHeader,
|
|
11
11
|
CardTitle,
|
|
12
|
-
} from "@
|
|
13
|
-
import { Input } from "@
|
|
12
|
+
} from "@mehdad67/apitogo/ui/Card.js";
|
|
13
|
+
import { Input } from "@mehdad67/apitogo/ui/Input.js";
|
|
14
14
|
import { useMutation } from "@tanstack/react-query";
|
|
15
15
|
import { useForm } from "react-hook-form";
|
|
16
16
|
import { Link, useNavigate, useSearchParams } from "react-router";
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { ActionButton } from "@
|
|
1
|
+
import { ActionButton } from "@mehdad67/apitogo/ui/ActionButton.js";
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
AlertDescription,
|
|
5
5
|
AlertTitle,
|
|
6
|
-
} from "@
|
|
7
|
-
import { Button } from "@
|
|
6
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
7
|
+
import { Button } from "@mehdad67/apitogo/ui/Button.js";
|
|
8
8
|
import {
|
|
9
9
|
Card,
|
|
10
10
|
CardContent,
|
|
11
11
|
CardDescription,
|
|
12
12
|
CardHeader,
|
|
13
13
|
CardTitle,
|
|
14
|
-
} from "@
|
|
14
|
+
} from "@mehdad67/apitogo/ui/Card.js";
|
|
15
15
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
16
16
|
import { CheckIcon, MailCheck, RefreshCw } from "lucide-react";
|
|
17
17
|
import { Navigate, useSearchParams } from "react-router";
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { ActionButton } from "@
|
|
1
|
+
import { ActionButton } from "@mehdad67/apitogo/ui/ActionButton.js";
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
AlertDescription,
|
|
5
5
|
AlertTitle,
|
|
6
|
-
} from "@
|
|
7
|
-
import { Button, type ButtonProps } from "@
|
|
6
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
7
|
+
import { Button, type ButtonProps } from "@mehdad67/apitogo/ui/Button.js";
|
|
8
8
|
import {
|
|
9
9
|
CardContent,
|
|
10
10
|
CardDescription,
|
|
11
11
|
CardHeader,
|
|
12
12
|
CardTitle,
|
|
13
|
-
} from "@
|
|
14
|
-
import { Input } from "@
|
|
15
|
-
import { Separator } from "@
|
|
13
|
+
} from "@mehdad67/apitogo/ui/Card.js";
|
|
14
|
+
import { Input } from "@mehdad67/apitogo/ui/Input.js";
|
|
15
|
+
import { Separator } from "@mehdad67/apitogo/ui/Separator.js";
|
|
16
16
|
import { useMutation } from "@tanstack/react-query";
|
|
17
17
|
import React from "react";
|
|
18
18
|
import { useForm } from "react-hook-form";
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
CommandInlineInput,
|
|
4
4
|
CommandItem,
|
|
5
5
|
CommandList,
|
|
6
|
-
} from "@
|
|
7
|
-
import { Popover, PopoverContent } from "@
|
|
6
|
+
} from "@mehdad67/apitogo/ui/Command.js";
|
|
7
|
+
import { Popover, PopoverContent } from "@mehdad67/apitogo/ui/Popover.js";
|
|
8
8
|
import { PopoverAnchor } from "@radix-ui/react-popover";
|
|
9
9
|
import { useCommandState } from "cmdk";
|
|
10
10
|
import { type KeyboardEvent, type Ref, useRef, useState } from "react";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Button } from "@
|
|
2
|
-
import { Skeleton } from "@
|
|
1
|
+
import { Button } from "@mehdad67/apitogo/ui/Button.js";
|
|
2
|
+
import { Skeleton } from "@mehdad67/apitogo/ui/Skeleton.js";
|
|
3
3
|
import { LogOutIcon } from "lucide-react";
|
|
4
4
|
import { lazy, memo, Suspense } from "react";
|
|
5
5
|
import { Link } from "react-router";
|
|
@@ -73,9 +73,9 @@ const ProfileMenu = () => {
|
|
|
73
73
|
const context = useZudoku();
|
|
74
74
|
const profileItems = context.getProfileMenuItems();
|
|
75
75
|
const auth = useAuth();
|
|
76
|
-
const { isAuthEnabled, isAuthenticated, profile } = auth;
|
|
76
|
+
const { isAuthEnabled, isAuthenticated, profile, isBackendAvailable } = auth;
|
|
77
77
|
|
|
78
|
-
if (!isAuthEnabled) return null;
|
|
78
|
+
if (!isAuthEnabled || !isBackendAvailable) return null;
|
|
79
79
|
|
|
80
80
|
return (
|
|
81
81
|
<ClientOnly fallback={<Skeleton className="rounded-sm h-5 w-24 mr-4" />}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TooltipProvider } from "@
|
|
1
|
+
import { TooltipProvider } from "@mehdad67/apitogo/ui/Tooltip.js";
|
|
2
2
|
import { type ReactNode, Suspense, useEffect } from "react";
|
|
3
3
|
import { Outlet } from "react-router";
|
|
4
4
|
import { cn } from "../util/cn.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Drawer, DrawerTrigger } from "@
|
|
1
|
+
import { Drawer, DrawerTrigger } from "@mehdad67/apitogo/ui/Drawer.js";
|
|
2
2
|
import { PanelLeftIcon } from "lucide-react";
|
|
3
3
|
import { type PropsWithChildren, useState } from "react";
|
|
4
4
|
import { useNavigation } from "react-router";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
Alert,
|
|
3
3
|
AlertDescription,
|
|
4
4
|
AlertTitle,
|
|
5
|
-
} from "@
|
|
5
|
+
} from "@mehdad67/apitogo/ui/Alert.js";
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
7
|
import type { MermaidConfig } from "mermaid";
|
|
8
8
|
import type { ComponentProps } from "react";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Button } from "@
|
|
2
|
-
import { Separator } from "@
|
|
3
|
-
import { Skeleton } from "@
|
|
1
|
+
import { Button } from "@mehdad67/apitogo/ui/Button.js";
|
|
2
|
+
import { Separator } from "@mehdad67/apitogo/ui/Separator.js";
|
|
3
|
+
import { Skeleton } from "@mehdad67/apitogo/ui/Skeleton.js";
|
|
4
4
|
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
|
5
5
|
import { deepEqual } from "fast-equals";
|
|
6
6
|
import {
|
|
@@ -130,7 +130,8 @@ export const MobileTopNavigation = () => {
|
|
|
130
130
|
getProfileMenuItems,
|
|
131
131
|
} = context;
|
|
132
132
|
const headerNavigation = header?.navigation ?? [];
|
|
133
|
-
const { isAuthenticated, profile, isAuthEnabled } =
|
|
133
|
+
const { isAuthenticated, profile, isAuthEnabled, isBackendAvailable } =
|
|
134
|
+
authState;
|
|
134
135
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
135
136
|
|
|
136
137
|
const accountItems = getProfileMenuItems();
|
|
@@ -232,7 +233,7 @@ export const MobileTopNavigation = () => {
|
|
|
232
233
|
</div>
|
|
233
234
|
<div className="border-t shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] px-4 pt-3 flex flex-col gap-2">
|
|
234
235
|
<div className="flex items-center justify-between">
|
|
235
|
-
{isAuthEnabled && (
|
|
236
|
+
{isAuthEnabled && isBackendAvailable && (
|
|
236
237
|
<ClientOnly
|
|
237
238
|
fallback={<Skeleton className="rounded-sm h-8 w-16" />}
|
|
238
239
|
>
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
Popover,
|
|
3
3
|
PopoverContent,
|
|
4
4
|
PopoverTrigger,
|
|
5
|
-
} from "@
|
|
5
|
+
} from "@mehdad67/apitogo/ui/Popover.js";
|
|
6
6
|
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
7
7
|
import { useState } from "react";
|
|
8
8
|
import { cn } from "../util/cn.js";
|
|
@@ -14,7 +14,7 @@ const useApitogoContext = () => {
|
|
|
14
14
|
|
|
15
15
|
if (!context) {
|
|
16
16
|
throw new Error(
|
|
17
|
-
"useApitogo must be used within an ApitogoProvider (from @
|
|
17
|
+
"useApitogo must be used within an ApitogoProvider (from @mehdad67/apitogo/components).",
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|