-
Notifications
You must be signed in to change notification settings - Fork 326
/
Copy pathcontrolComponents.tsx
143 lines (117 loc) · 3.91 KB
/
controlComponents.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import type { CheckAuthorizationWithCustomPermissions, HandleOAuthCallbackParams } from '@clerk/types';
import { computed } from 'nanostores';
import type { PropsWithChildren } from 'react';
import React, { useEffect, useState } from 'react';
import { $csrState } from '../stores/internal';
import type { ProtectProps as _ProtectProps } from '../types';
import { useAuth } from './hooks';
import type { WithClerkProp } from './utils';
import { withClerk } from './utils';
export function SignedOut(props: PropsWithChildren) {
const { userId } = useAuth();
if (userId) {
return null;
}
return props.children;
}
export function SignedIn(props: PropsWithChildren) {
const { userId } = useAuth();
if (!userId) {
return null;
}
return props.children;
}
const $isLoadingClerkStore = computed($csrState, state => state.isLoaded);
/*
* It is not guaranteed that hydration will occur before clerk-js has loaded. If Clerk is loaded by the time a React component hydrates,
* then we **hydration error** will be thrown for any control component that renders conditionally.
*
* This hook ensures that `isLoaded` will always be false on the first render,
* preventing potential hydration errors and race conditions.
*/
const useSafeIsLoaded = () => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const unsub = $isLoadingClerkStore.subscribe(() => {
setIsLoaded(true);
});
return () => unsub();
}, []);
return isLoaded;
};
export const ClerkLoaded = ({ children }: React.PropsWithChildren): JSX.Element | null => {
const isLoaded = useSafeIsLoaded();
if (!isLoaded) {
return null;
}
return <>{children}</>;
};
export const ClerkLoading = ({ children }: React.PropsWithChildren): JSX.Element | null => {
const isLoaded = useSafeIsLoaded();
if (isLoaded) {
return null;
}
return <>{children}</>;
};
export type ProtectProps = React.PropsWithChildren<_ProtectProps & { fallback?: React.ReactNode }>;
/**
* Use `<Protect/>` in order to prevent unauthenticated or unauthorized users from accessing the children passed to the component.
*
* Examples:
* ```
* <Protect permission="a_permission_key" />
* <Protect role="a_role_key" />
* <Protect condition={(has) => has({permission:"a_permission_key"})} />
* <Protect condition={(has) => has({role:"a_role_key"})} />
* <Protect fallback={<p>Unauthorized</p>} />
* ```
*/
export const Protect = ({ children, fallback, ...restAuthorizedParams }: ProtectProps) => {
const { isLoaded, has, userId } = useAuth();
/**
* Avoid flickering children or fallback while clerk is loading sessionId or userId
*/
if (!isLoaded) {
return null;
}
/**
* Fallback to UI provided by user or `null` if authorization checks failed
*/
const unauthorized = <>{fallback ?? null}</>;
const authorized = <>{children}</>;
if (!userId) {
return unauthorized;
}
/**
* Check against the results of `has` called inside the callback
*/
if (typeof restAuthorizedParams.condition === 'function') {
if (restAuthorizedParams.condition(has as CheckAuthorizationWithCustomPermissions)) {
return authorized;
}
return unauthorized;
}
if (restAuthorizedParams.role || restAuthorizedParams.permission) {
if (has?.(restAuthorizedParams)) {
return authorized;
}
return unauthorized;
}
/**
* If neither of the authorization params are passed behave as the `<SignedIn/>`.
* If fallback is present render that instead of rendering nothing.
*/
return authorized;
};
/**
* Use `<AuthenticateWithRedirectCallback/>` to complete a custom OAuth flow.
*/
export const AuthenticateWithRedirectCallback = withClerk(
({ clerk, ...handleRedirectCallbackParams }: WithClerkProp<HandleOAuthCallbackParams>) => {
React.useEffect(() => {
void clerk?.handleRedirectCallback(handleRedirectCallbackParams);
}, []);
return null;
},
'AuthenticateWithRedirectCallback',
);