@dudousxd/adonis-authkit-react 0.2.0 → 0.3.1
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/build/index.d.ts +16 -0
- package/build/index.js +8 -0
- package/build/src/components/organization_profile.d.ts +8 -0
- package/build/src/components/organization_profile.js +87 -0
- package/build/src/components/organization_switcher.d.ts +7 -0
- package/build/src/components/organization_switcher.js +57 -0
- package/build/src/components/password_strength_meter.d.ts +11 -0
- package/build/src/components/password_strength_meter.js +39 -0
- package/build/src/config.d.ts +2 -0
- package/build/src/config.js +4 -0
- package/build/src/hooks/use_org_invitations.d.ts +19 -0
- package/build/src/hooks/use_org_invitations.js +20 -0
- package/build/src/hooks/use_organization.d.ts +23 -0
- package/build/src/hooks/use_organization.js +8 -0
- package/build/src/hooks/use_organizations.d.ts +18 -0
- package/build/src/hooks/use_organizations.js +14 -0
- package/build/src/hooks/use_password_strength.d.ts +11 -0
- package/build/src/hooks/use_password_strength.js +34 -0
- package/build/src/hooks/use_switch_organization.d.ts +7 -0
- package/build/src/hooks/use_switch_organization.js +42 -0
- package/package.json +5 -5
- package/styles.css +105 -0
package/build/index.d.ts
CHANGED
|
@@ -17,8 +17,18 @@ export { useSessions } from './src/hooks/use_sessions.js';
|
|
|
17
17
|
export type { UseSessionsResult, AuthSession } from './src/hooks/use_sessions.js';
|
|
18
18
|
export { useAuthorizedApps } from './src/hooks/use_authorized_apps.js';
|
|
19
19
|
export type { UseAuthorizedAppsResult, AuthorizedApp } from './src/hooks/use_authorized_apps.js';
|
|
20
|
+
export { useOrganizations } from './src/hooks/use_organizations.js';
|
|
21
|
+
export type { UseOrganizationsResult, OrgEntry } from './src/hooks/use_organizations.js';
|
|
22
|
+
export { useOrganization } from './src/hooks/use_organization.js';
|
|
23
|
+
export type { UseOrganizationResult, ActiveOrgDetail, OrgMemberEntry } from './src/hooks/use_organization.js';
|
|
24
|
+
export { useSwitchOrganization } from './src/hooks/use_switch_organization.js';
|
|
25
|
+
export type { UseSwitchOrganizationResult } from './src/hooks/use_switch_organization.js';
|
|
26
|
+
export { useOrgInvitations } from './src/hooks/use_org_invitations.js';
|
|
27
|
+
export type { UseOrgInvitationsResult, OrgInvitationEntry } from './src/hooks/use_org_invitations.js';
|
|
20
28
|
export { jsonRequest, useResource } from './src/hooks/use_resource.js';
|
|
21
29
|
export type { ResourceState } from './src/hooks/use_resource.js';
|
|
30
|
+
export { usePasswordStrength, heuristicScorer, } from './src/hooks/use_password_strength.js';
|
|
31
|
+
export type { PasswordStrengthScore, PasswordStrengthResult, PasswordScorer, UsePasswordStrengthOptions, } from './src/hooks/use_password_strength.js';
|
|
22
32
|
export { deriveInitials, currentUrl } from './src/utils.js';
|
|
23
33
|
export { Authenticated, Guest } from './src/components/authenticated.js';
|
|
24
34
|
export type { AuthenticatedProps, GuestProps } from './src/components/authenticated.js';
|
|
@@ -36,5 +46,11 @@ export { UserProfile } from './src/components/user_profile.js';
|
|
|
36
46
|
export type { UserProfileProps } from './src/components/user_profile.js';
|
|
37
47
|
export { AuthorizedApps } from './src/components/authorized_apps.js';
|
|
38
48
|
export type { AuthorizedAppsProps } from './src/components/authorized_apps.js';
|
|
49
|
+
export { PasswordStrengthMeter } from './src/components/password_strength_meter.js';
|
|
50
|
+
export type { PasswordStrengthMeterProps } from './src/components/password_strength_meter.js';
|
|
51
|
+
export { OrganizationSwitcher } from './src/components/organization_switcher.js';
|
|
52
|
+
export type { OrganizationSwitcherProps } from './src/components/organization_switcher.js';
|
|
53
|
+
export { OrganizationProfile } from './src/components/organization_profile.js';
|
|
54
|
+
export type { OrganizationProfileProps } from './src/components/organization_profile.js';
|
|
39
55
|
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles, hasAppRole, hasAnyAppRole, hasAllAppRoles, } from './src/roles.js';
|
|
40
56
|
export type { AuthUser, AuthSharedProps, AuthState } from './src/types.js';
|
package/build/index.js
CHANGED
|
@@ -8,7 +8,12 @@ export { useUser } from './src/hooks/use_user.js';
|
|
|
8
8
|
export { useProfile } from './src/hooks/use_profile.js';
|
|
9
9
|
export { useSessions } from './src/hooks/use_sessions.js';
|
|
10
10
|
export { useAuthorizedApps } from './src/hooks/use_authorized_apps.js';
|
|
11
|
+
export { useOrganizations } from './src/hooks/use_organizations.js';
|
|
12
|
+
export { useOrganization } from './src/hooks/use_organization.js';
|
|
13
|
+
export { useSwitchOrganization } from './src/hooks/use_switch_organization.js';
|
|
14
|
+
export { useOrgInvitations } from './src/hooks/use_org_invitations.js';
|
|
11
15
|
export { jsonRequest, useResource } from './src/hooks/use_resource.js';
|
|
16
|
+
export { usePasswordStrength, heuristicScorer, } from './src/hooks/use_password_strength.js';
|
|
12
17
|
export { deriveInitials, currentUrl } from './src/utils.js';
|
|
13
18
|
export { Authenticated, Guest } from './src/components/authenticated.js';
|
|
14
19
|
export { Can } from './src/components/can.js';
|
|
@@ -18,4 +23,7 @@ export { Avatar } from './src/components/avatar.js';
|
|
|
18
23
|
export { UserButton } from './src/components/user_button.js';
|
|
19
24
|
export { UserProfile } from './src/components/user_profile.js';
|
|
20
25
|
export { AuthorizedApps } from './src/components/authorized_apps.js';
|
|
26
|
+
export { PasswordStrengthMeter } from './src/components/password_strength_meter.js';
|
|
27
|
+
export { OrganizationSwitcher } from './src/components/organization_switcher.js';
|
|
28
|
+
export { OrganizationProfile } from './src/components/organization_profile.js';
|
|
21
29
|
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles, hasAppRole, hasAnyAppRole, hasAllAppRoles, } from './src/roles.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface OrganizationProfileProps {
|
|
2
|
+
inviteLabel?: string;
|
|
3
|
+
leaveLabel?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function OrganizationProfile({ inviteLabel, leaveLabel, className, }: OrganizationProfileProps): import("react").DetailedReactHTMLElement<{
|
|
7
|
+
className: string;
|
|
8
|
+
}, HTMLElement> | null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createElement, useState } from 'react';
|
|
2
|
+
import { useOrganizations } from '../hooks/use_organizations.js';
|
|
3
|
+
import { useOrganization } from '../hooks/use_organization.js';
|
|
4
|
+
import { useAuth } from '../use_auth.js';
|
|
5
|
+
import { useAuthkitConfig } from '../config.js';
|
|
6
|
+
import { jsonRequest } from '../hooks/use_resource.js';
|
|
7
|
+
export function OrganizationProfile({ inviteLabel = 'Convidar membro', leaveLabel = 'Sair da organização', className, }) {
|
|
8
|
+
const { isAuthenticated } = useAuth();
|
|
9
|
+
const config = useAuthkitConfig();
|
|
10
|
+
const { activeOrgId, actions: orgListActions } = useOrganizations();
|
|
11
|
+
const { data: org, loading, error, actions } = useOrganization(activeOrgId);
|
|
12
|
+
const [inviteEmail, setInviteEmail] = useState('');
|
|
13
|
+
const [inviteRole, setInviteRole] = useState('member');
|
|
14
|
+
const [inviteLoading, setInviteLoading] = useState(false);
|
|
15
|
+
const [inviteError, setInviteError] = useState(null);
|
|
16
|
+
if (!isAuthenticated || !activeOrgId)
|
|
17
|
+
return null;
|
|
18
|
+
if (loading)
|
|
19
|
+
return createElement('div', { className: 'authkit-org-profile__loading' }, 'Carregando…');
|
|
20
|
+
if (error)
|
|
21
|
+
return createElement('div', { className: 'authkit-error' }, error.message);
|
|
22
|
+
if (!org)
|
|
23
|
+
return null;
|
|
24
|
+
const base = config.endpoints.orgs.replace('/json', '');
|
|
25
|
+
const handleInvite = async (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
if (!inviteEmail.trim())
|
|
28
|
+
return;
|
|
29
|
+
setInviteLoading(true);
|
|
30
|
+
setInviteError(null);
|
|
31
|
+
try {
|
|
32
|
+
await jsonRequest(`${base}/${encodeURIComponent(activeOrgId)}/invite`, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
body: JSON.stringify({ email: inviteEmail.trim(), role: inviteRole }),
|
|
35
|
+
csrfToken: config.csrfToken,
|
|
36
|
+
});
|
|
37
|
+
setInviteEmail('');
|
|
38
|
+
await actions.refetch();
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
setInviteError(err);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setInviteLoading(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const handleLeave = async () => {
|
|
48
|
+
try {
|
|
49
|
+
await jsonRequest(`${base}/${encodeURIComponent(activeOrgId)}/leave`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
csrfToken: config.csrfToken,
|
|
52
|
+
});
|
|
53
|
+
await orgListActions.refetch();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const memberList = createElement('div', { className: 'authkit-org-profile__members' }, createElement('h3', { className: 'authkit-org-profile__section-title' }, 'Membros'), ...(org.members.length === 0
|
|
59
|
+
? [createElement('p', { className: 'authkit-org-profile__empty' }, 'Nenhum membro.')]
|
|
60
|
+
: org.members.map((m) => createElement('div', { key: m.accountId, className: 'authkit-org-profile__member' }, createElement('div', { className: 'authkit-org-profile__member-info' }, createElement('span', { className: 'authkit-org-profile__member-email' }, m.email ?? m.accountId), createElement('span', { className: 'authkit-org-profile__member-role' }, m.role))))));
|
|
61
|
+
const inviteForm = org.canManage
|
|
62
|
+
? createElement('form', { className: 'authkit-org-profile__invite-form', onSubmit: handleInvite }, createElement('h3', { className: 'authkit-org-profile__section-title' }, inviteLabel), createElement('input', {
|
|
63
|
+
className: 'authkit-input',
|
|
64
|
+
type: 'email',
|
|
65
|
+
placeholder: 'Email',
|
|
66
|
+
value: inviteEmail,
|
|
67
|
+
onChange: (e) => setInviteEmail(e.target.value),
|
|
68
|
+
}), createElement('input', {
|
|
69
|
+
className: 'authkit-input',
|
|
70
|
+
placeholder: 'Papel (ex: member)',
|
|
71
|
+
value: inviteRole,
|
|
72
|
+
onChange: (e) => setInviteRole(e.target.value),
|
|
73
|
+
}), inviteError
|
|
74
|
+
? createElement('p', { className: 'authkit-error', role: 'alert' }, inviteError.message)
|
|
75
|
+
: null, createElement('button', {
|
|
76
|
+
type: 'submit',
|
|
77
|
+
className: 'authkit-button authkit-button--primary',
|
|
78
|
+
disabled: inviteLoading,
|
|
79
|
+
}, inviteLoading ? 'Enviando…' : inviteLabel))
|
|
80
|
+
: null;
|
|
81
|
+
const leaveButton = createElement('button', {
|
|
82
|
+
type: 'button',
|
|
83
|
+
className: 'authkit-button authkit-button--danger',
|
|
84
|
+
onClick: handleLeave,
|
|
85
|
+
}, leaveLabel);
|
|
86
|
+
return createElement('div', { className: ['authkit-card', 'authkit-org-profile', className].filter(Boolean).join(' ') }, createElement('div', { className: 'authkit-org-profile__header' }, createElement('div', { className: 'authkit-org-profile__name' }, org.name), createElement('div', { className: 'authkit-org-profile__slug' }, org.slug)), memberList, inviteForm, leaveButton);
|
|
87
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface OrganizationSwitcherProps {
|
|
2
|
+
personalAccountLabel?: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function OrganizationSwitcher({ personalAccountLabel, className, }: OrganizationSwitcherProps): import("react").DetailedReactHTMLElement<{
|
|
6
|
+
className: string;
|
|
7
|
+
}, HTMLElement> | null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createElement, useState } from 'react';
|
|
2
|
+
import { useOrganizations } from '../hooks/use_organizations.js';
|
|
3
|
+
import { useSwitchOrganization } from '../hooks/use_switch_organization.js';
|
|
4
|
+
import { useAuth } from '../use_auth.js';
|
|
5
|
+
export function OrganizationSwitcher({ personalAccountLabel = 'Conta pessoal', className, }) {
|
|
6
|
+
const { isAuthenticated } = useAuth();
|
|
7
|
+
const { data: orgs, activeOrgId, supported } = useOrganizations();
|
|
8
|
+
const { activate, deactivate, loading: switching } = useSwitchOrganization();
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
if (!isAuthenticated || !supported)
|
|
11
|
+
return null;
|
|
12
|
+
const activeOrg = orgs?.find((o) => o.id === activeOrgId) ?? null;
|
|
13
|
+
const label = activeOrg ? activeOrg.name : personalAccountLabel;
|
|
14
|
+
const trigger = createElement('button', {
|
|
15
|
+
type: 'button',
|
|
16
|
+
className: 'authkit-orgswitcher__trigger',
|
|
17
|
+
'aria-haspopup': 'listbox',
|
|
18
|
+
'aria-expanded': open,
|
|
19
|
+
disabled: switching,
|
|
20
|
+
onClick: () => setOpen((v) => !v),
|
|
21
|
+
}, createElement('span', { className: 'authkit-orgswitcher__label' }, label), createElement('span', { className: 'authkit-orgswitcher__chevron', 'aria-hidden': 'true' }, '▾'));
|
|
22
|
+
const menu = open
|
|
23
|
+
? createElement('div', { className: 'authkit-orgswitcher__menu', role: 'listbox' }, createElement('button', {
|
|
24
|
+
type: 'button',
|
|
25
|
+
className: [
|
|
26
|
+
'authkit-orgswitcher__item',
|
|
27
|
+
!activeOrgId ? 'authkit-orgswitcher__item--active' : '',
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' '),
|
|
31
|
+
role: 'option',
|
|
32
|
+
'aria-selected': !activeOrgId,
|
|
33
|
+
onClick: async () => {
|
|
34
|
+
setOpen(false);
|
|
35
|
+
if (activeOrgId)
|
|
36
|
+
await deactivate();
|
|
37
|
+
},
|
|
38
|
+
}, createElement('span', { className: 'authkit-orgswitcher__item-name' }, personalAccountLabel)), ...(orgs ?? []).map((org) => createElement('button', {
|
|
39
|
+
key: org.id,
|
|
40
|
+
type: 'button',
|
|
41
|
+
className: [
|
|
42
|
+
'authkit-orgswitcher__item',
|
|
43
|
+
org.isActive ? 'authkit-orgswitcher__item--active' : '',
|
|
44
|
+
]
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join(' '),
|
|
47
|
+
role: 'option',
|
|
48
|
+
'aria-selected': org.isActive,
|
|
49
|
+
onClick: async () => {
|
|
50
|
+
setOpen(false);
|
|
51
|
+
if (!org.isActive)
|
|
52
|
+
await activate(org.id);
|
|
53
|
+
},
|
|
54
|
+
}, createElement('span', { className: 'authkit-orgswitcher__item-name' }, org.name), createElement('span', { className: 'authkit-orgswitcher__item-role' }, org.role))))
|
|
55
|
+
: null;
|
|
56
|
+
return createElement('div', { className: ['authkit-orgswitcher', className].filter(Boolean).join(' ') }, trigger, menu);
|
|
57
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PasswordScorer } from '../hooks/use_password_strength.js';
|
|
2
|
+
export interface PasswordStrengthMeterProps {
|
|
3
|
+
password: string;
|
|
4
|
+
scorer?: PasswordScorer;
|
|
5
|
+
showFeedback?: boolean;
|
|
6
|
+
labels?: [string, string, string, string, string];
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function PasswordStrengthMeter({ password, scorer, showFeedback, labels, className, }: PasswordStrengthMeterProps): import("react").DetailedReactHTMLElement<{
|
|
10
|
+
className: string;
|
|
11
|
+
}, HTMLElement>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { usePasswordStrength, } from '../hooks/use_password_strength.js';
|
|
3
|
+
const DEFAULT_LABELS = [
|
|
4
|
+
'Very weak',
|
|
5
|
+
'Weak',
|
|
6
|
+
'Fair',
|
|
7
|
+
'Good',
|
|
8
|
+
'Strong',
|
|
9
|
+
];
|
|
10
|
+
export function PasswordStrengthMeter({ password, scorer, showFeedback = true, labels = DEFAULT_LABELS, className, }) {
|
|
11
|
+
const { score, feedback } = usePasswordStrength(password, { scorer });
|
|
12
|
+
const cls = ['authkit-strength', className].filter(Boolean).join(' ');
|
|
13
|
+
const segments = [0, 1, 2, 3].map((i) => createElement('span', {
|
|
14
|
+
key: i,
|
|
15
|
+
className: [
|
|
16
|
+
'authkit-strength__segment',
|
|
17
|
+
i < score ? 'authkit-strength__segment--filled' : '',
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' '),
|
|
21
|
+
}));
|
|
22
|
+
const children = [
|
|
23
|
+
createElement('div', {
|
|
24
|
+
key: 'bar',
|
|
25
|
+
className: 'authkit-strength__bar',
|
|
26
|
+
role: 'meter',
|
|
27
|
+
'aria-valuemin': 0,
|
|
28
|
+
'aria-valuemax': 4,
|
|
29
|
+
'aria-valuenow': score,
|
|
30
|
+
'aria-label': labels[score],
|
|
31
|
+
'data-score': score,
|
|
32
|
+
}, segments),
|
|
33
|
+
createElement('span', { key: 'label', className: 'authkit-strength__label' }, labels[score]),
|
|
34
|
+
];
|
|
35
|
+
if (showFeedback && feedback && feedback.length > 0) {
|
|
36
|
+
children.push(createElement('ul', { key: 'feedback', className: 'authkit-strength__feedback' }, feedback.map((tip, i) => createElement('li', { key: i }, tip))));
|
|
37
|
+
}
|
|
38
|
+
return createElement('div', { className: cls }, children);
|
|
39
|
+
}
|
package/build/src/config.d.ts
CHANGED
package/build/src/config.js
CHANGED
|
@@ -8,6 +8,8 @@ export const DEFAULT_CONFIG = {
|
|
|
8
8
|
sessions: '/account/security',
|
|
9
9
|
apps: '/account/apps',
|
|
10
10
|
passkeys: '/account/mfa/passkeys',
|
|
11
|
+
orgs: '/account/orgs/json',
|
|
12
|
+
orgInvitations: '/account/orgs/invitations/json',
|
|
11
13
|
},
|
|
12
14
|
};
|
|
13
15
|
export function resolveConfig(config) {
|
|
@@ -20,6 +22,8 @@ export function resolveConfig(config) {
|
|
|
20
22
|
sessions: config?.endpoints?.sessions ?? DEFAULT_CONFIG.endpoints.sessions,
|
|
21
23
|
apps: config?.endpoints?.apps ?? DEFAULT_CONFIG.endpoints.apps,
|
|
22
24
|
passkeys: config?.endpoints?.passkeys ?? DEFAULT_CONFIG.endpoints.passkeys,
|
|
25
|
+
orgs: config?.endpoints?.orgs ?? DEFAULT_CONFIG.endpoints.orgs,
|
|
26
|
+
orgInvitations: config?.endpoints?.orgInvitations ?? DEFAULT_CONFIG.endpoints.orgInvitations,
|
|
23
27
|
},
|
|
24
28
|
csrfToken: config?.csrfToken,
|
|
25
29
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgInvitationEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
organizationId: string;
|
|
5
|
+
orgName: string;
|
|
6
|
+
orgSlug: string;
|
|
7
|
+
email: string;
|
|
8
|
+
role: string;
|
|
9
|
+
expiresAt: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface UseOrgInvitationsResult extends ResourceState<OrgInvitationEntry[]> {
|
|
14
|
+
actions: {
|
|
15
|
+
refetch(): Promise<void>;
|
|
16
|
+
accept(token: string): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function useOrgInvitations(): UseOrgInvitationsResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest, useResource } from './use_resource.js';
|
|
4
|
+
export function useOrgInvitations() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.orgInvitations, config.csrfToken);
|
|
7
|
+
const accept = useCallback(async (token) => {
|
|
8
|
+
await jsonRequest(`/account/orgs/invitations/${encodeURIComponent(token)}/accept`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
csrfToken: config.csrfToken,
|
|
11
|
+
});
|
|
12
|
+
await refetch();
|
|
13
|
+
}, [config.csrfToken, refetch]);
|
|
14
|
+
return {
|
|
15
|
+
data: data?.invitations ?? null,
|
|
16
|
+
loading,
|
|
17
|
+
error,
|
|
18
|
+
actions: { refetch, accept },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgMemberEntry {
|
|
3
|
+
accountId: string;
|
|
4
|
+
email: string | null;
|
|
5
|
+
role: string;
|
|
6
|
+
joinedAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ActiveOrgDetail {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
logoUrl: string | null;
|
|
13
|
+
role: string;
|
|
14
|
+
canManage: boolean;
|
|
15
|
+
members: OrgMemberEntry[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface UseOrganizationResult extends ResourceState<ActiveOrgDetail> {
|
|
19
|
+
actions: {
|
|
20
|
+
refetch(): Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function useOrganization(orgId: string | null): UseOrganizationResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useAuthkitConfig } from '../config.js';
|
|
2
|
+
import { useResource } from './use_resource.js';
|
|
3
|
+
export function useOrganization(orgId) {
|
|
4
|
+
const config = useAuthkitConfig();
|
|
5
|
+
const url = orgId ? `${config.endpoints.orgs.replace('/json', '')}/${encodeURIComponent(orgId)}/json` : '';
|
|
6
|
+
const { data, loading, error, refetch } = useResource(url, config.csrfToken);
|
|
7
|
+
return { data, loading: url ? loading : false, error, actions: { refetch } };
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
logoUrl: string | null;
|
|
7
|
+
role: string;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface UseOrganizationsResult extends ResourceState<OrgEntry[]> {
|
|
12
|
+
activeOrgId: string | null;
|
|
13
|
+
supported: boolean;
|
|
14
|
+
actions: {
|
|
15
|
+
refetch(): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function useOrganizations(): UseOrganizationsResult;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useAuthkitConfig } from '../config.js';
|
|
2
|
+
import { useResource } from './use_resource.js';
|
|
3
|
+
export function useOrganizations() {
|
|
4
|
+
const config = useAuthkitConfig();
|
|
5
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.orgs, config.csrfToken);
|
|
6
|
+
return {
|
|
7
|
+
data: data?.orgs ?? null,
|
|
8
|
+
loading,
|
|
9
|
+
error,
|
|
10
|
+
activeOrgId: data?.activeOrgId ?? null,
|
|
11
|
+
supported: data?.supported ?? true,
|
|
12
|
+
actions: { refetch },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type PasswordStrengthScore = 0 | 1 | 2 | 3 | 4;
|
|
2
|
+
export interface PasswordStrengthResult {
|
|
3
|
+
score: PasswordStrengthScore;
|
|
4
|
+
feedback?: string[];
|
|
5
|
+
}
|
|
6
|
+
export type PasswordScorer = (password: string) => PasswordStrengthResult;
|
|
7
|
+
export interface UsePasswordStrengthOptions {
|
|
8
|
+
scorer?: PasswordScorer;
|
|
9
|
+
}
|
|
10
|
+
export declare function heuristicScorer(password: string): PasswordStrengthResult;
|
|
11
|
+
export declare function usePasswordStrength(password: string, options?: UsePasswordStrengthOptions): PasswordStrengthResult;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
export function heuristicScorer(password) {
|
|
3
|
+
if (!password)
|
|
4
|
+
return { score: 0, feedback: ['Enter a password.'] };
|
|
5
|
+
const feedback = [];
|
|
6
|
+
const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/];
|
|
7
|
+
const variety = classes.filter((re) => re.test(password)).length;
|
|
8
|
+
const length = password.length;
|
|
9
|
+
let points = 0;
|
|
10
|
+
if (length >= 8)
|
|
11
|
+
points += 1;
|
|
12
|
+
if (length >= 12)
|
|
13
|
+
points += 1;
|
|
14
|
+
if (length >= 16)
|
|
15
|
+
points += 1;
|
|
16
|
+
if (variety >= 2)
|
|
17
|
+
points += 1;
|
|
18
|
+
if (variety >= 3)
|
|
19
|
+
points += 1;
|
|
20
|
+
if (length < 12)
|
|
21
|
+
feedback.push('Use at least 12 characters.');
|
|
22
|
+
if (!/[A-Z]/.test(password))
|
|
23
|
+
feedback.push('Add an uppercase letter.');
|
|
24
|
+
if (!/[0-9]/.test(password))
|
|
25
|
+
feedback.push('Add a number.');
|
|
26
|
+
if (!/[^A-Za-z0-9]/.test(password))
|
|
27
|
+
feedback.push('Add a symbol.');
|
|
28
|
+
const score = Math.max(0, Math.min(4, points));
|
|
29
|
+
return { score, feedback: feedback.length ? feedback : undefined };
|
|
30
|
+
}
|
|
31
|
+
export function usePasswordStrength(password, options = {}) {
|
|
32
|
+
const scorer = options.scorer ?? heuristicScorer;
|
|
33
|
+
return useMemo(() => scorer(password), [password, scorer]);
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest } from './use_resource.js';
|
|
4
|
+
export function useSwitchOrganization() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const base = config.endpoints.orgs.replace('/json', '');
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const activate = useCallback(async (orgId) => {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
setError(null);
|
|
12
|
+
try {
|
|
13
|
+
await jsonRequest(`${base}/${encodeURIComponent(orgId)}/activate`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
csrfToken: config.csrfToken,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
setError(err);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
setLoading(false);
|
|
23
|
+
}
|
|
24
|
+
}, [base, config.csrfToken]);
|
|
25
|
+
const deactivate = useCallback(async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
await jsonRequest(`${base}/deactivate`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
csrfToken: config.csrfToken,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
setError(err);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}, [base, config.csrfToken]);
|
|
41
|
+
return { loading, error, activate, deactivate };
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/adonis-authkit-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Frontend ergonomics over AuthKit for AdonisJS + Inertia + React apps: a typed useAuth() hook, role-gating hooks and gating components.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
|
7
|
-
"homepage": "https://github.com/DavideCarvalho/
|
|
7
|
+
"homepage": "https://github.com/DavideCarvalho/adonis-authkit/tree/main/packages/authkit-react#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/DavideCarvalho/
|
|
10
|
+
"url": "https://github.com/DavideCarvalho/adonis-authkit.git",
|
|
11
11
|
"directory": "packages/authkit-react"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/DavideCarvalho/
|
|
14
|
+
"url": "https://github.com/DavideCarvalho/adonis-authkit/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"adonisjs",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"react-dom": "19.2.6"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@dudousxd/adonis-authkit-core": "0.
|
|
46
|
+
"@dudousxd/adonis-authkit-core": "0.3.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@adonisjs/tsconfig": "2.0.0",
|
package/styles.css
CHANGED
|
@@ -187,3 +187,108 @@
|
|
|
187
187
|
color: var(--authkit-muted);
|
|
188
188
|
font-size: 0.9rem;
|
|
189
189
|
}
|
|
190
|
+
|
|
191
|
+
/* Medidor de força de senha (PasswordStrengthMeter). */
|
|
192
|
+
.authkit-strength {
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
gap: 0.35rem;
|
|
196
|
+
}
|
|
197
|
+
.authkit-strength__bar {
|
|
198
|
+
display: flex;
|
|
199
|
+
gap: 0.25rem;
|
|
200
|
+
}
|
|
201
|
+
.authkit-strength__segment {
|
|
202
|
+
flex: 1 1 0;
|
|
203
|
+
height: 5px;
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
background: var(--authkit-border);
|
|
206
|
+
transition: background-color 0.15s ease;
|
|
207
|
+
}
|
|
208
|
+
/* Cor por score, do mais fraco ao mais forte (via data-score no bar). */
|
|
209
|
+
.authkit-strength__bar[data-score='1'] .authkit-strength__segment--filled {
|
|
210
|
+
background: var(--authkit-danger);
|
|
211
|
+
}
|
|
212
|
+
.authkit-strength__bar[data-score='2'] .authkit-strength__segment--filled {
|
|
213
|
+
background: #f5a524;
|
|
214
|
+
}
|
|
215
|
+
.authkit-strength__bar[data-score='3'] .authkit-strength__segment--filled {
|
|
216
|
+
background: #d6c20a;
|
|
217
|
+
}
|
|
218
|
+
.authkit-strength__bar[data-score='4'] .authkit-strength__segment--filled {
|
|
219
|
+
background: #2fb344;
|
|
220
|
+
}
|
|
221
|
+
.authkit-strength__label {
|
|
222
|
+
font-size: 0.8rem;
|
|
223
|
+
color: var(--authkit-muted);
|
|
224
|
+
}
|
|
225
|
+
.authkit-strength__feedback {
|
|
226
|
+
margin: 0;
|
|
227
|
+
padding-left: 1.1rem;
|
|
228
|
+
font-size: 0.8rem;
|
|
229
|
+
color: var(--authkit-muted);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ─── OrganizationSwitcher ─────────────────────────────────────────────── */
|
|
233
|
+
.authkit-orgswitcher {
|
|
234
|
+
position: relative;
|
|
235
|
+
display: inline-block;
|
|
236
|
+
}
|
|
237
|
+
.authkit-orgswitcher__trigger {
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 0.4rem;
|
|
241
|
+
padding: 0.4rem 0.75rem;
|
|
242
|
+
background: var(--authkit-bg);
|
|
243
|
+
border: 1px solid var(--authkit-border);
|
|
244
|
+
border-radius: var(--authkit-radius);
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
font-size: 0.875rem;
|
|
247
|
+
color: var(--authkit-fg);
|
|
248
|
+
}
|
|
249
|
+
.authkit-orgswitcher__trigger:disabled { opacity: 0.6; cursor: default; }
|
|
250
|
+
.authkit-orgswitcher__chevron { font-size: 0.65rem; color: var(--authkit-muted); }
|
|
251
|
+
.authkit-orgswitcher__menu {
|
|
252
|
+
position: absolute;
|
|
253
|
+
top: calc(100% + 4px);
|
|
254
|
+
left: 0;
|
|
255
|
+
min-width: 180px;
|
|
256
|
+
background: var(--authkit-bg);
|
|
257
|
+
border: 1px solid var(--authkit-border);
|
|
258
|
+
border-radius: var(--authkit-radius);
|
|
259
|
+
box-shadow: 0 4px 16px rgba(0,0,0,.1);
|
|
260
|
+
z-index: 50;
|
|
261
|
+
overflow: hidden;
|
|
262
|
+
}
|
|
263
|
+
.authkit-orgswitcher__item {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
width: 100%;
|
|
268
|
+
padding: 0.5rem 1rem;
|
|
269
|
+
background: transparent;
|
|
270
|
+
border: none;
|
|
271
|
+
cursor: pointer;
|
|
272
|
+
font-size: 0.875rem;
|
|
273
|
+
color: var(--authkit-fg);
|
|
274
|
+
text-align: left;
|
|
275
|
+
}
|
|
276
|
+
.authkit-orgswitcher__item:hover { background: color-mix(in srgb, var(--authkit-primary) 8%, transparent); }
|
|
277
|
+
.authkit-orgswitcher__item--active { font-weight: 600; }
|
|
278
|
+
.authkit-orgswitcher__item-name { flex: 1; }
|
|
279
|
+
.authkit-orgswitcher__item-role { font-size: 0.75rem; color: var(--authkit-muted); margin-left: 0.5rem; }
|
|
280
|
+
|
|
281
|
+
/* ─── OrganizationProfile ──────────────────────────────────────────────── */
|
|
282
|
+
.authkit-org-profile { display: flex; flex-direction: column; gap: var(--authkit-gap); }
|
|
283
|
+
.authkit-org-profile__header { margin-bottom: 0.5rem; }
|
|
284
|
+
.authkit-org-profile__name { font-weight: 600; font-size: 1rem; color: var(--authkit-fg); }
|
|
285
|
+
.authkit-org-profile__slug { font-size: 0.8rem; color: var(--authkit-muted); }
|
|
286
|
+
.authkit-org-profile__section-title { font-size: 0.85rem; font-weight: 600; color: var(--authkit-fg); margin-bottom: 0.4rem; }
|
|
287
|
+
.authkit-org-profile__members { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
288
|
+
.authkit-org-profile__member { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; border-bottom: 1px solid var(--authkit-border); }
|
|
289
|
+
.authkit-org-profile__member:last-child { border-bottom: none; }
|
|
290
|
+
.authkit-org-profile__member-info { display: flex; flex-direction: column; }
|
|
291
|
+
.authkit-org-profile__member-email { font-size: 0.875rem; color: var(--authkit-fg); }
|
|
292
|
+
.authkit-org-profile__member-role { font-size: 0.75rem; color: var(--authkit-muted); }
|
|
293
|
+
.authkit-org-profile__invite-form { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.75rem; }
|
|
294
|
+
.authkit-org-profile__empty { font-size: 0.875rem; color: var(--authkit-muted); }
|