@robosystems/core 0.5.5 → 0.5.7

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.
@@ -11,5 +11,12 @@ export interface SignUpFormProps {
11
11
  apiUrl: string;
12
12
  currentApp?: string;
13
13
  turnstileSiteKey?: string;
14
+ /**
15
+ * Organization invitation token, from the `?invite=` link in the email.
16
+ * When present the form registers the user into the inviting organization
17
+ * instead of creating a personal one, and the invited address is fixed —
18
+ * the token is only valid for that email.
19
+ */
20
+ inviteToken?: string;
14
21
  }
15
- export declare function SignUpForm({ onSuccess, onRedirect, redirectTo, className, showConfirmPassword, showTermsAcceptance, autoSignIn, apiUrl, currentApp, turnstileSiteKey, }: SignUpFormProps): React.JSX.Element;
22
+ export declare function SignUpForm({ onSuccess, onRedirect, redirectTo, className, showConfirmPassword, showTermsAcceptance, autoSignIn, apiUrl, currentApp, turnstileSiteKey, inviteToken, }: SignUpFormProps): React.JSX.Element;
@@ -5,7 +5,7 @@ import { RoboSystemsAuthClient } from '../auth-core/client';
5
5
  import { getAppConfig } from '../auth-core/config';
6
6
  import { LogoBadge, Spinner } from '../ui-components';
7
7
  import { TurnstileWidget } from './TurnstileWidget';
8
- export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', className = '', showConfirmPassword = true, showTermsAcceptance = true, autoSignIn = false, apiUrl, currentApp, turnstileSiteKey, }) {
8
+ export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', className = '', showConfirmPassword = true, showTermsAcceptance = true, autoSignIn = false, apiUrl, currentApp, turnstileSiteKey, inviteToken, }) {
9
9
  const [formData, setFormData] = useState({
10
10
  name: '',
11
11
  email: '',
@@ -18,8 +18,39 @@ export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', class
18
18
  const [captchaError, setCaptchaError] = useState(null);
19
19
  const [passwordStrength, setPasswordStrength] = useState(null);
20
20
  const [checkingPassword, setCheckingPassword] = useState(false);
21
+ const [invitation, setInvitation] = useState(null);
22
+ const [inviteChecked, setInviteChecked] = useState(!inviteToken);
21
23
  const debounceRef = useRef(null);
22
24
  const authClientRef = useRef(new RoboSystemsAuthClient(apiUrl));
25
+ // Resolve the invitation before the form is usable. A token that no longer
26
+ // resolves (revoked, expired, already accepted) degrades to ordinary
27
+ // registration with a notice rather than a dead end.
28
+ useEffect(() => {
29
+ if (!inviteToken)
30
+ return;
31
+ let cancelled = false;
32
+ setInviteChecked(false);
33
+ authClientRef.current
34
+ .getInvitation(inviteToken)
35
+ .then((preview) => {
36
+ if (cancelled)
37
+ return;
38
+ if (preview) {
39
+ setInvitation(preview);
40
+ setFormData((prev) => (Object.assign(Object.assign({}, prev), { email: preview.email })));
41
+ }
42
+ else {
43
+ setError('This invitation link is no longer valid. It may have expired or already been used. You can still create an account below.');
44
+ }
45
+ })
46
+ .finally(() => {
47
+ if (!cancelled)
48
+ setInviteChecked(true);
49
+ });
50
+ return () => {
51
+ cancelled = true;
52
+ };
53
+ }, [inviteToken]);
23
54
  const checkPassword = useCallback(async (password, email) => {
24
55
  if (password.length < 4) {
25
56
  setPasswordStrength(null);
@@ -100,7 +131,7 @@ export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', class
100
131
  return;
101
132
  }
102
133
  try {
103
- const result = await authClientRef.current.register(formData.email, formData.password, formData.name, captchaToken || undefined);
134
+ const result = await authClientRef.current.register(formData.email, formData.password, formData.name, captchaToken || undefined, invitation ? inviteToken : undefined);
104
135
  // Call onSuccess callback if provided
105
136
  if (onSuccess) {
106
137
  onSuccess(result.user);
@@ -142,7 +173,12 @@ export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', class
142
173
  };
143
174
  // Determine app name based on currentApp prop
144
175
  const appName = getAppConfig(currentApp || 'robosystems').displayName;
145
- return (_jsx("div", { className: "via-primary-950 flex min-h-screen items-center justify-center bg-linear-to-br from-black to-zinc-900 px-4 py-12 sm:px-6 lg:px-8", children: _jsxs("div", { className: "w-full max-w-lg space-y-8", children: [_jsxs("div", { className: "text-center", children: [_jsx(LogoBadge, { animate: "once", className: "mx-auto h-14 w-14" }), _jsx("h1", { className: "font-heading mt-4 text-center text-2xl font-semibold tracking-tight text-white", children: appName }), _jsx("h2", { className: "mt-6 text-center text-xl font-semibold tracking-tight text-gray-300", children: "Create your account" })] }), _jsxs("form", { className: ['mt-8 space-y-6', className].filter(Boolean).join(' '), onSubmit: handleSubmit, children: [error && (_jsx("div", { className: "rounded-md border border-red-800 bg-red-900/50 p-4", children: _jsx("div", { className: "text-sm text-red-300", children: error }) })), captchaError && (_jsx("div", { className: "rounded-md border border-red-800 bg-red-900/50 p-4", children: _jsx("div", { className: "text-sm text-red-300", children: captchaError }) })), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "name", className: "sr-only", children: "Full name" }), _jsx("input", { id: "name", name: "name", type: "text", autoComplete: "name", required: true, value: formData.name, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Full name", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "email", className: "sr-only", children: "Email address" }), _jsx("input", { id: "email", name: "email", type: "email", autoComplete: "email", required: true, value: formData.email, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Email address", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "password", className: "sr-only", children: "Password" }), _jsx("input", { id: "password", name: "password", type: "password", autoComplete: "new-password", required: true, value: formData.password, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Password", disabled: loading })] }), showConfirmPassword && (_jsxs("div", { children: [_jsx("label", { htmlFor: "confirmPassword", className: "sr-only", children: "Confirm password" }), _jsx("input", { id: "confirmPassword", name: "confirmPassword", type: "password", autoComplete: "new-password", required: true, value: formData.confirmPassword, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Confirm password", disabled: loading })] })), _jsx("div", { className: "min-h-[2.75rem]", children: passwordStrength && (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-1.5 flex-1 overflow-hidden rounded-full bg-gray-700", children: _jsx("div", { className: [
176
+ return (_jsx("div", { className: "via-primary-950 flex min-h-screen items-center justify-center bg-linear-to-br from-black to-zinc-900 px-4 py-12 sm:px-6 lg:px-8", children: _jsxs("div", { className: "w-full max-w-lg space-y-8", children: [_jsxs("div", { className: "text-center", children: [_jsx(LogoBadge, { animate: "once", className: "mx-auto h-14 w-14" }), _jsx("h1", { className: "font-heading mt-4 text-center text-2xl font-semibold tracking-tight text-white", children: appName }), _jsx("h2", { className: "mt-6 text-center text-xl font-semibold tracking-tight text-gray-300", children: invitation ? `Join ${invitation.org_name}` : 'Create your account' }), invitation && (_jsxs("p", { className: "mt-2 text-center text-sm text-gray-400", children: ["You've been invited as", ' ', _jsx("span", { className: "font-medium text-gray-200", children: invitation.role }), ". Create your account to accept."] }))] }), _jsxs("form", { className: ['mt-8 space-y-6', className].filter(Boolean).join(' '), onSubmit: handleSubmit, children: [error && (_jsx("div", { className: "rounded-md border border-red-800 bg-red-900/50 p-4", children: _jsx("div", { className: "text-sm text-red-300", children: error }) })), captchaError && (_jsx("div", { className: "rounded-md border border-red-800 bg-red-900/50 p-4", children: _jsx("div", { className: "text-sm text-red-300", children: captchaError }) })), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "name", className: "sr-only", children: "Full name" }), _jsx("input", { id: "name", name: "name", type: "text", autoComplete: "name", required: true, value: formData.name, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Full name", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "email", className: "sr-only", children: "Email address" }), _jsx("input", { id: "email", name: "email", type: "email", autoComplete: "email", required: true, value: formData.email, onChange: handleInputChange, readOnly: !!invitation, className: [
177
+ 'focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset',
178
+ invitation ? 'cursor-not-allowed text-gray-300' : '',
179
+ ]
180
+ .filter(Boolean)
181
+ .join(' '), placeholder: "Email address", disabled: loading }), invitation && (_jsx("p", { className: "mt-1 text-xs text-gray-400", children: "The invitation was sent to this address and can only be accepted with it." }))] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "password", className: "sr-only", children: "Password" }), _jsx("input", { id: "password", name: "password", type: "password", autoComplete: "new-password", required: true, value: formData.password, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Password", disabled: loading })] }), showConfirmPassword && (_jsxs("div", { children: [_jsx("label", { htmlFor: "confirmPassword", className: "sr-only", children: "Confirm password" }), _jsx("input", { id: "confirmPassword", name: "confirmPassword", type: "password", autoComplete: "new-password", required: true, value: formData.confirmPassword, onChange: handleInputChange, className: "focus:ring-primary-500 relative block w-full rounded-md border-0 bg-gray-800 px-5 py-4 text-base leading-7 text-white ring-1 ring-gray-600 ring-inset placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset", placeholder: "Confirm password", disabled: loading })] })), _jsx("div", { className: "min-h-[2.75rem]", children: passwordStrength && (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-1.5 flex-1 overflow-hidden rounded-full bg-gray-700", children: _jsx("div", { className: [
146
182
  'h-full rounded-full transition-all duration-300',
147
183
  passwordStrength.score < 30
148
184
  ? 'bg-red-500'
@@ -163,5 +199,12 @@ export function SignUpForm({ onSuccess, onRedirect, redirectTo = '/login', class
163
199
  ].join(' '), children: passwordStrength.strength.replace('-', ' ') })] }), passwordStrength.errors.length > 0 && (_jsx("p", { className: "text-xs text-red-400", children: passwordStrength.errors[0] })), passwordStrength.errors.length === 0 &&
164
200
  passwordStrength.suggestions.length > 0 && (_jsx("p", { className: "text-xs text-gray-400", children: passwordStrength.suggestions[0] }))] })) })] }), turnstileSiteKey && (_jsx("div", { className: "flex justify-center", children: _jsx(TurnstileWidget, { siteKey: turnstileSiteKey, onVerify: handleCaptchaVerify, onError: handleCaptchaError, onExpire: handleCaptchaExpire, theme: "dark", className: "flex justify-center", disabled: loading }) })), _jsx("div", { children: _jsxs("button", { type: "submit", disabled: loading ||
165
201
  checkingPassword ||
166
- (turnstileSiteKey && !captchaToken), className: "group bg-primary-600 hover:bg-primary-700 focus-visible:outline-primary-500 relative flex w-full justify-center rounded-md px-4 py-3 text-sm font-semibold text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid disabled:cursor-not-allowed disabled:opacity-50", children: [loading && _jsx(Spinner, { size: "sm", className: "mr-2 text-white" }), loading ? 'Creating account...' : 'Create account'] }) }), showTermsAcceptance && (_jsxs("div", { className: "text-center text-xs text-gray-400", children: ["By creating an account, you agree to our", ' ', _jsx("a", { href: `${process.env.NEXT_PUBLIC_ROBOSYSTEMS_APP_URL || 'https://robosystems.ai'}/pages/terms`, target: "_blank", rel: "noopener noreferrer", className: "hover:text-primary-400 font-medium text-gray-300", children: "Terms of Service" }), ' ', "and", ' ', _jsx("a", { href: `${process.env.NEXT_PUBLIC_ROBOSYSTEMS_APP_URL || 'https://robosystems.ai'}/pages/privacy`, target: "_blank", rel: "noopener noreferrer", className: "hover:text-primary-400 font-medium text-gray-300", children: "Privacy Policy" })] })), _jsx("div", { className: "text-center", children: _jsx("button", { type: "button", onClick: handleSignInClick, className: "hover:text-primary-400 font-medium text-gray-300", children: "Already have an account? Sign in" }) })] })] }) }));
202
+ !inviteChecked ||
203
+ (turnstileSiteKey && !captchaToken), className: "group bg-primary-600 hover:bg-primary-700 focus-visible:outline-primary-500 relative flex w-full justify-center rounded-md px-4 py-3 text-sm font-semibold text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid disabled:cursor-not-allowed disabled:opacity-50", children: [(loading || !inviteChecked) && (_jsx(Spinner, { size: "sm", className: "mr-2 text-white" })), loading
204
+ ? 'Creating account...'
205
+ : !inviteChecked
206
+ ? 'Checking invitation...'
207
+ : invitation
208
+ ? 'Accept invitation'
209
+ : 'Create account'] }) }), showTermsAcceptance && (_jsxs("div", { className: "text-center text-xs text-gray-400", children: ["By creating an account, you agree to our", ' ', _jsx("a", { href: `${process.env.NEXT_PUBLIC_ROBOSYSTEMS_APP_URL || 'https://robosystems.ai'}/pages/terms`, target: "_blank", rel: "noopener noreferrer", className: "hover:text-primary-400 font-medium text-gray-300", children: "Terms of Service" }), ' ', "and", ' ', _jsx("a", { href: `${process.env.NEXT_PUBLIC_ROBOSYSTEMS_APP_URL || 'https://robosystems.ai'}/pages/privacy`, target: "_blank", rel: "noopener noreferrer", className: "hover:text-primary-400 font-medium text-gray-300", children: "Privacy Policy" })] })), _jsx("div", { className: "text-center", children: _jsx("button", { type: "button", onClick: handleSignInClick, className: "hover:text-primary-400 font-medium text-gray-300", children: "Already have an account? Sign in" }) })] })] }) }));
167
210
  }
@@ -35,7 +35,7 @@ export declare class RoboSystemsAuthClient {
35
35
  */
36
36
  private syncClientConfigToken;
37
37
  login(email: string, password: string): Promise<AuthResponse>;
38
- register(email: string, password: string, name?: string, captchaToken?: string): Promise<AuthResponse>;
38
+ register(email: string, password: string, name?: string, captchaToken?: string, inviteToken?: string): Promise<AuthResponse>;
39
39
  logout(): Promise<void>;
40
40
  getCurrentUser(): Promise<AuthUser>;
41
41
  private performAuthCheck;
@@ -105,6 +105,20 @@ export declare class RoboSystemsAuthClient {
105
105
  /**
106
106
  * Check password strength
107
107
  */
108
+ /**
109
+ * Look up an organization invitation by its token.
110
+ *
111
+ * Unauthenticated by design — the token is the credential, and the sign-up
112
+ * page has to render who invited you before an account exists. Returns null
113
+ * for a token that is unknown, revoked, expired, or already accepted, so the
114
+ * caller can fall back to ordinary registration rather than dead-ending.
115
+ */
116
+ getInvitation(token: string): Promise<{
117
+ org_name: string;
118
+ email: string;
119
+ role: string;
120
+ expires_at: string;
121
+ } | null>;
108
122
  checkPasswordStrength(password: string, email?: string): Promise<{
109
123
  score: number;
110
124
  strength: 'very-weak' | 'weak' | 'fair' | 'good' | 'strong';
@@ -1,5 +1,5 @@
1
1
  var _a;
2
- import { checkPasswordStrength, client, completeSsoAuth, createUserApiKey, forgotPassword, generateSsoToken, getCurrentAuthUser, getPasswordPolicy, listUserApiKeys, loginUser, logoutUser, refreshAuthSession, registerUser, resendVerificationEmail, resetPassword, revokeUserApiKey, ssoTokenExchange, validateResetToken, verifyEmail, } from '@robosystems/client';
2
+ import { checkPasswordStrength, client, completeSsoAuth, createUserApiKey, forgotPassword, generateSsoToken, getCurrentAuthUser, getInvitationPreview, getPasswordPolicy, listUserApiKeys, loginUser, logoutUser, refreshAuthSession, registerUser, resendVerificationEmail, resetPassword, revokeUserApiKey, ssoTokenExchange, validateResetToken, verifyEmail, } from '@robosystems/client';
3
3
  import * as sdkClientsModule from '@robosystems/client/clients';
4
4
  import { getToken, getValidToken } from './token-storage';
5
5
  // Global config hook for the clients package's lazy default singleton
@@ -200,7 +200,7 @@ export class RoboSystemsAuthClient {
200
200
  refresh_threshold: sdkResponse.refresh_threshold,
201
201
  };
202
202
  }
203
- async register(email, password, name, captchaToken) {
203
+ async register(email, password, name, captchaToken, inviteToken) {
204
204
  const response = await registerUser({
205
205
  client: this.client,
206
206
  body: {
@@ -208,6 +208,7 @@ export class RoboSystemsAuthClient {
208
208
  password,
209
209
  name: name || '',
210
210
  captcha_token: captchaToken || undefined,
211
+ invite_token: inviteToken || undefined,
211
212
  },
212
213
  });
213
214
  // Check for error responses (4xx/5xx)
@@ -668,6 +669,30 @@ export class RoboSystemsAuthClient {
668
669
  /**
669
670
  * Check password strength
670
671
  */
672
+ /**
673
+ * Look up an organization invitation by its token.
674
+ *
675
+ * Unauthenticated by design — the token is the credential, and the sign-up
676
+ * page has to render who invited you before an account exists. Returns null
677
+ * for a token that is unknown, revoked, expired, or already accepted, so the
678
+ * caller can fall back to ordinary registration rather than dead-ending.
679
+ */
680
+ async getInvitation(token) {
681
+ try {
682
+ const response = await getInvitationPreview({
683
+ client: this.client,
684
+ path: { token },
685
+ });
686
+ if (response.error || !response.data) {
687
+ return null;
688
+ }
689
+ const data = response.data;
690
+ return data;
691
+ }
692
+ catch (_a) {
693
+ return null;
694
+ }
695
+ }
671
696
  async checkPasswordStrength(password, email) {
672
697
  try {
673
698
  const response = await checkPasswordStrength({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -27,7 +27,7 @@
27
27
  "tailwind-merge": "^3"
28
28
  },
29
29
  "peerDependencies": {
30
- "@robosystems/client": ">=0.3.2 <1",
30
+ "@robosystems/client": ">=0.6.2 <1",
31
31
  "flowbite-react": "^0.12.5",
32
32
  "next": ">=15 <17",
33
33
  "react": ">=18 <20",
@@ -24,11 +24,11 @@ export function LandingFooter({ tagline, productLinks, contactModal: ContactModa
24
24
  const companyLinkProps = isRoboSystems
25
25
  ? {}
26
26
  : { target: '_blank', rel: 'noopener noreferrer' };
27
- return (_jsxs("footer", { className: "border-t border-gray-800 bg-zinc-950", children: [_jsxs("div", { className: "mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-5 lg:gap-6", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsxs("div", { className: "mb-4 flex items-center gap-2", children: [_jsx(Image, { src: `/images/logos/${CURRENT_APP}.png`, alt: current.displayName, width: 32, height: 32, className: "rounded-lg" }), _jsx("span", { className: "font-heading text-xl font-semibold text-white", children: current.displayName })] }), _jsx("p", { className: "mb-4 max-w-md text-sm text-gray-400", children: tagline }), _jsxs("div", { className: "flex gap-4", children: [_jsx(Link, { href: "https://github.com/RoboFinSystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on GitHub", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" }) }) }), _jsx(Link, { href: "https://x.com/robofinsystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on X", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" }) }) }), _jsx(Link, { href: "https://www.linkedin.com/company/robofinsystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on LinkedIn", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" }) }) }), _jsx(Link, { href: "https://www.youtube.com/@RoboSystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on YouTube", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" }) }) })] })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Product" }), _jsx("ul", { className: "space-y-2 text-sm", children: productLinks.map((link) => (_jsx("li", { children: _jsx(Link, { href: link.href, className: linkClass, children: link.label }) }, link.href))) })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Applications" }), _jsx("ul", { className: "space-y-2 text-sm", children: APP_ORDER.map((app) => {
27
+ return (_jsxs("footer", { className: "border-t border-gray-800 bg-zinc-950", children: [_jsxs("div", { className: "mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-5 lg:gap-6", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsxs("div", { className: "mb-4 flex items-center gap-2", children: [_jsx(Image, { src: `/images/logos/${CURRENT_APP}.png`, alt: current.displayName, width: 32, height: 32, className: "rounded-lg" }), _jsx("span", { className: "font-heading text-xl font-semibold text-white", children: current.displayName })] }), _jsx("p", { className: "mb-4 max-w-md text-sm text-gray-400", children: tagline }), _jsxs("div", { className: "flex gap-4", children: [_jsx(Link, { href: "https://github.com/RoboFinSystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on GitHub", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" }) }) }), _jsx(Link, { href: "https://x.com/robofinsystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on X", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" }) }) }), _jsx(Link, { href: "https://www.linkedin.com/company/robosystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on LinkedIn", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" }) }) }), _jsx(Link, { href: "https://www.youtube.com/@RoboSystems", target: "_blank", rel: "noopener noreferrer", "aria-label": "RoboSystems on YouTube", className: linkClass, children: _jsx("svg", { className: "h-6 w-6", fill: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { d: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" }) }) })] })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Product" }), _jsx("ul", { className: "space-y-2 text-sm", children: productLinks.map((link) => (_jsx("li", { children: _jsx(Link, { href: link.href, className: linkClass, children: link.label }) }, link.href))) })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Applications" }), _jsx("ul", { className: "space-y-2 text-sm", children: APP_ORDER.map((app) => {
28
28
  const cfg = getAppConfig(app);
29
29
  const isCurrent = app === CURRENT_APP;
30
30
  return (_jsx("li", { children: _jsx(Link, Object.assign({ href: cfg.url }, (isCurrent
31
31
  ? {}
32
32
  : { target: '_blank', rel: 'noopener noreferrer' }), { className: linkClass, children: cfg.displayName })) }, app));
33
- }) })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Company" }), _jsxs("ul", { className: "space-y-2 text-sm", children: [_jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/research` }, companyLinkProps, { className: linkClass, children: "Research" })) }), _jsx("li", { children: _jsx(Link, { href: "https://holon.robosystems.ai", target: "_blank", rel: "noopener noreferrer", className: linkClass, children: "Holon Viewer" }) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/blog` }, companyLinkProps, { className: linkClass, children: "Blog" })) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/pages/privacy` }, companyLinkProps, { className: linkClass, children: "Privacy" })) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/pages/terms` }, companyLinkProps, { className: linkClass, children: "Terms" })) }), _jsx("li", { children: _jsx(Link, { href: "https://app.vanta.com/robosystems.ai/trust/lzitnsl1mo27b6zahy0ksy", target: "_blank", rel: "noopener noreferrer", className: linkClass, children: "Trust Center" }) }), ContactModal && (_jsx("li", { children: _jsx("button", { onClick: () => setShowContact(true), className: linkClass, children: "Contact" }) }))] })] })] }), _jsx("div", { className: "mt-8 border-t border-gray-800 pt-8 text-center text-sm text-gray-400", children: _jsxs("p", { children: ["\u00A9 ", new Date().getFullYear(), " RFS LLC. All rights reserved."] }) })] }), ContactModal && (_jsx(ContactModal, { isOpen: showContact, onClose: () => setShowContact(false) }))] }));
33
+ }) })] }), _jsxs("div", { children: [_jsx("h3", { className: headingClass, children: "Company" }), _jsxs("ul", { className: "space-y-2 text-sm", children: [_jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/research` }, companyLinkProps, { className: linkClass, children: "Research" })) }), _jsx("li", { children: _jsx(Link, { href: "https://holon.robosystems.ai", target: "_blank", rel: "noopener noreferrer", className: linkClass, children: "Holon Viewer" }) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/blog` }, companyLinkProps, { className: linkClass, children: "Blog" })) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/pages/privacy` }, companyLinkProps, { className: linkClass, children: "Privacy" })) }), _jsx("li", { children: _jsx(Link, Object.assign({ href: `${companyBase}/pages/terms` }, companyLinkProps, { className: linkClass, children: "Terms" })) }), _jsx("li", { children: _jsx(Link, { href: "https://trust.robosystems.ai", target: "_blank", rel: "noopener noreferrer", className: linkClass, children: "Trust Center" }) }), _jsx("li", { children: _jsx(Link, { href: "https://status.robosystems.ai", target: "_blank", rel: "noopener noreferrer", className: linkClass, children: "Status" }) }), ContactModal && (_jsx("li", { children: _jsx("button", { onClick: () => setShowContact(true), className: linkClass, children: "Contact" }) }))] })] })] }), _jsx("div", { className: "mt-8 border-t border-gray-800 pt-8 text-center text-sm text-gray-400", children: _jsxs("p", { children: ["\u00A9 ", new Date().getFullYear(), " RFS LLC. All rights reserved."] }) })] }), ContactModal && (_jsx(ContactModal, { isOpen: showContact, onClose: () => setShowContact(false) }))] }));
34
34
  }