@workos-inc/widgets 1.1.4 → 1.2.0

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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/lib/organization-switcher.d.ts +10 -1
  3. package/dist/cjs/lib/organization-switcher.d.ts.map +1 -1
  4. package/dist/cjs/lib/organization-switcher.js +31 -3
  5. package/dist/cjs/lib/organization-switcher.js.map +1 -1
  6. package/dist/cjs/workos-widgets.client.d.ts +6 -0
  7. package/dist/cjs/workos-widgets.client.d.ts.map +1 -1
  8. package/dist/cjs/workos-widgets.client.js +23 -3
  9. package/dist/cjs/workos-widgets.client.js.map +1 -1
  10. package/dist/esm/lib/organization-switcher.d.ts +10 -1
  11. package/dist/esm/lib/organization-switcher.d.ts.map +1 -1
  12. package/dist/esm/lib/organization-switcher.js +31 -3
  13. package/dist/esm/lib/organization-switcher.js.map +1 -1
  14. package/dist/esm/workos-widgets.client.d.ts +6 -0
  15. package/dist/esm/workos-widgets.client.d.ts.map +1 -1
  16. package/dist/esm/workos-widgets.client.js +25 -5
  17. package/dist/esm/workos-widgets.client.js.map +1 -1
  18. package/package.json +40 -47
  19. package/src/api/api-provider.tsx +0 -158
  20. package/src/api/constants.ts +0 -1
  21. package/src/api/endpoint.ts +0 -3097
  22. package/src/api/errors.ts +0 -48
  23. package/src/api/index.ts +0 -2
  24. package/src/api/utils.ts +0 -42
  25. package/src/api/widgets-api-client.ts +0 -87
  26. package/src/card-list.tsx +0 -26
  27. package/src/index.ts +0 -9
  28. package/src/lib/add-mfa-dialog.tsx +0 -379
  29. package/src/lib/api/config.ts +0 -9
  30. package/src/lib/api/user.ts +0 -98
  31. package/src/lib/change-password-dialog.tsx +0 -290
  32. package/src/lib/constants.ts +0 -3
  33. package/src/lib/copy-button.tsx +0 -53
  34. package/src/lib/delete-user-dialog.tsx +0 -110
  35. package/src/lib/edit-user-profile-dialog.tsx +0 -181
  36. package/src/lib/edit-user-role-dialog.tsx +0 -178
  37. package/src/lib/elements.tsx +0 -428
  38. package/src/lib/elevated-access.tsx +0 -261
  39. package/src/lib/error-boundary.tsx +0 -166
  40. package/src/lib/errors.ts +0 -49
  41. package/src/lib/generic-error.tsx +0 -70
  42. package/src/lib/icon-panel.tsx +0 -26
  43. package/src/lib/icons.tsx +0 -21
  44. package/src/lib/invite-user-dialog.tsx +0 -327
  45. package/src/lib/logout-all-sessions-dialog.tsx +0 -82
  46. package/src/lib/logout-dialog.tsx +0 -85
  47. package/src/lib/marker.tsx +0 -39
  48. package/src/lib/oauth-icons.tsx +0 -138
  49. package/src/lib/organization-switcher.tsx +0 -156
  50. package/src/lib/otp-input.tsx +0 -276
  51. package/src/lib/resend-invite-dialog.tsx +0 -145
  52. package/src/lib/reset-mfa-dialog.tsx +0 -104
  53. package/src/lib/revoke-invite-dialog.tsx +0 -111
  54. package/src/lib/save-button.tsx +0 -113
  55. package/src/lib/search-provider.tsx +0 -51
  56. package/src/lib/set-password-dialog.tsx +0 -204
  57. package/src/lib/use-dialog-close.tsx +0 -19
  58. package/src/lib/use-is-hydrated.ts +0 -13
  59. package/src/lib/use-layout-effect.ts +0 -6
  60. package/src/lib/use-security-settings.tsx +0 -49
  61. package/src/lib/user-actions-dropdown.tsx +0 -157
  62. package/src/lib/user-profile.tsx +0 -227
  63. package/src/lib/user-security.tsx +0 -187
  64. package/src/lib/user-sessions.tsx +0 -204
  65. package/src/lib/users-filter.tsx +0 -62
  66. package/src/lib/users-management-context.tsx +0 -74
  67. package/src/lib/users-management-state.ts +0 -165
  68. package/src/lib/users-management.tsx +0 -594
  69. package/src/lib/users-search.tsx +0 -73
  70. package/src/lib/utils.ts +0 -131
  71. package/src/lib/widgets-context.ts +0 -29
  72. package/src/organization-switcher.client.tsx +0 -81
  73. package/src/user-profile.client.tsx +0 -55
  74. package/src/user-security.client.tsx +0 -55
  75. package/src/user-sessions.client.tsx +0 -100
  76. package/src/users-management.client.tsx +0 -73
  77. package/src/workos-widgets.client.tsx +0 -75
  78. /package/{src → dist/css}/base.css +0 -0
  79. /package/{src → dist/css}/lib/card-list.css +0 -0
  80. /package/{src → dist/css}/lib/marker.css +0 -0
  81. /package/{src → dist/css}/lib/save-button.css +0 -0
  82. /package/{src → dist/css}/styles.css +0 -0
  83. /package/{src → dist/css}/users-management.css +0 -0
@@ -1,261 +0,0 @@
1
- import * as Form from "@radix-ui/react-form";
2
- import { AlertDialog, Callout, Dialog, Flex, Text } from "@radix-ui/themes";
3
- import {
4
- useElevatedAccessToken,
5
- useMe,
6
- useSendVerification,
7
- useVerify,
8
- } from "../api";
9
- import { PropsWithChildren, useEffect, useRef, useState } from "react";
10
- import { PrimaryButton, SecondaryButton } from "./elements";
11
- import * as Otp from "./otp-input";
12
-
13
- interface ElevatedAccessProps extends PropsWithChildren {
14
- onVerified?: () => Promise<unknown>;
15
- type?: "dialog" | "alert";
16
- }
17
-
18
- export function ElevatedAccess({
19
- type = "dialog",
20
- children,
21
- onVerified,
22
- }: ElevatedAccessProps) {
23
- const { elevatedAccess } = useElevatedAccessToken();
24
- const [authenticationChallengeId, setAuthenticationChallengeId] =
25
- useState<string>();
26
-
27
- const prevAccessToken = useRef(elevatedAccess);
28
-
29
- useEffect(() => {
30
- prevAccessToken.current = elevatedAccess;
31
- }, [elevatedAccess]);
32
-
33
- if (elevatedAccess) {
34
- return <>{children}</>;
35
- }
36
-
37
- if (!authenticationChallengeId) {
38
- const hasTokenExpired = !!prevAccessToken.current;
39
-
40
- return (
41
- <SendVerificationEmailForm
42
- type={type}
43
- hasTokenExpired={hasTokenExpired}
44
- onSuccess={(challengeId) => {
45
- setAuthenticationChallengeId(challengeId);
46
- }}
47
- />
48
- );
49
- }
50
-
51
- if (authenticationChallengeId) {
52
- return (
53
- <VerificationIdentityForm
54
- type={type}
55
- authenticationChallengeId={authenticationChallengeId}
56
- onSuccess={() => {
57
- // Reset the challenge id
58
- setAuthenticationChallengeId(undefined);
59
-
60
- return onVerified?.();
61
- }}
62
- />
63
- );
64
- }
65
-
66
- return null;
67
- }
68
-
69
- interface SendVerificationEmailFormProps {
70
- onSuccess: (challengeId: string) => unknown | Promise<unknown>;
71
- type: "dialog" | "alert";
72
- hasTokenExpired: boolean;
73
- }
74
-
75
- function SendVerificationEmailForm({
76
- onSuccess,
77
- type,
78
- hasTokenExpired,
79
- }: SendVerificationEmailFormProps) {
80
- const { data: me } = useMe();
81
- const sendVerification = useSendVerification({
82
- mutation: { onSuccess: (data) => onSuccess(data.authenticationChallenge) },
83
- });
84
-
85
- const Title = type === "dialog" ? Dialog.Title : AlertDialog.Title;
86
- const Description =
87
- type === "dialog" ? Dialog.Description : AlertDialog.Description;
88
- const Close = type === "dialog" ? Dialog.Close : AlertDialog.Cancel;
89
-
90
- const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
91
- event.preventDefault();
92
-
93
- sendVerification.mutate();
94
- };
95
-
96
- return (
97
- <form onSubmit={handleSubmit}>
98
- <Title>
99
- {hasTokenExpired
100
- ? "Your verification token has expired"
101
- : "Verify your identity"}
102
- </Title>
103
-
104
- <Description color="gray" mb="5">
105
- To continue, we need to confirm your identity. We'll send a temporary
106
- verification code to{" "}
107
- <Text weight="bold" highContrast>
108
- {me?.email}
109
- </Text>
110
- .
111
- </Description>
112
-
113
- {sendVerification.error && (
114
- <Callout.Root color="red" mt="-2" mb="0">
115
- <Callout.Text>
116
- {getMutationErrorMessage(sendVerification.error)}
117
- </Callout.Text>
118
- </Callout.Root>
119
- )}
120
-
121
- <Flex justify="end" align="center" gap="3" mt="5">
122
- <Close>
123
- <SecondaryButton type="button" disabled={sendVerification.isPending}>
124
- Cancel
125
- </SecondaryButton>
126
- </Close>
127
- <PrimaryButton type="submit" loading={sendVerification.isPending}>
128
- Send verification code
129
- </PrimaryButton>
130
- </Flex>
131
- </form>
132
- );
133
- }
134
-
135
- interface VerificationIdentityFormProps {
136
- onSuccess?: () => unknown | Promise<unknown>;
137
- authenticationChallengeId: string;
138
- type: "dialog" | "alert";
139
- }
140
-
141
- function VerificationIdentityForm({
142
- onSuccess,
143
- authenticationChallengeId,
144
- type,
145
- }: VerificationIdentityFormProps) {
146
- const { data: me } = useMe();
147
- const { setElevatedAccess } = useElevatedAccessToken();
148
- const verifyIdentity = useVerify();
149
- const [isSubmitting, setIsSubmitting] = useState(false);
150
-
151
- const Title = type === "dialog" ? Dialog.Title : AlertDialog.Title;
152
- const Description =
153
- type === "dialog" ? Dialog.Description : AlertDialog.Description;
154
- const Close = type === "dialog" ? Dialog.Close : AlertDialog.Cancel;
155
- const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
156
- event.preventDefault();
157
-
158
- const formData = new FormData(event.currentTarget);
159
- const code = formData.get("otp-code")?.toString() ?? "";
160
-
161
- setIsSubmitting(true);
162
-
163
- try {
164
- // Mutate async so we can wait for the onSuccess callback as well
165
- const newAuthState = await verifyIdentity.mutateAsync({
166
- data: {
167
- code,
168
- authenticationChallengeId,
169
- },
170
- });
171
-
172
- const in10Seconds = new Date(Date.now() + 5 * 1000);
173
-
174
- setElevatedAccess({
175
- token: newAuthState.elevatedAccessToken,
176
- // expiresAt: newAuthState.expiresAt,
177
- expiresAt: in10Seconds.toISOString(),
178
- });
179
-
180
- if (onSuccess) {
181
- await new Promise((resolve) => setTimeout(resolve, 200));
182
- await onSuccess();
183
- }
184
- } catch (error) {
185
- console.error(error);
186
- }
187
-
188
- setIsSubmitting(false);
189
- };
190
-
191
- return (
192
- <Form.Root onSubmit={handleSubmit}>
193
- <Title>Verify your identity</Title>
194
-
195
- <Description color="gray">
196
- A verification code was sent to{" "}
197
- <Text weight="bold" highContrast>
198
- {me?.email}
199
- </Text>
200
- . Please enter it below.
201
- </Description>
202
-
203
- <Flex direction="column" gap="2" mt="5" mx="auto" width="fit-content">
204
- <Otp.Root
205
- autoSubmit
206
- gap="2"
207
- justify="center"
208
- columns="repeat(6, 48px)"
209
- width="fit-content"
210
- rows="48px"
211
- name="otp-code"
212
- readOnly={isSubmitting}
213
- >
214
- <Otp.Input required autoFocus autoComplete="off" />
215
- <Otp.Input required />
216
- <Otp.Input required />
217
- <Otp.Input required />
218
- <Otp.Input required />
219
- <Otp.Input required />
220
- </Otp.Root>
221
-
222
- {verifyIdentity.error && (
223
- <Text color="red" size="2" as="p">
224
- {getMutationErrorMessage(verifyIdentity.error)}
225
- </Text>
226
- )}
227
- </Flex>
228
-
229
- <Flex justify="end" align="center" gap="3" mt="5">
230
- <Close>
231
- <SecondaryButton type="button">Cancel</SecondaryButton>
232
- </Close>
233
-
234
- <PrimaryButton type="submit" loading={isSubmitting}>
235
- Confirm
236
- </PrimaryButton>
237
- </Flex>
238
- </Form.Root>
239
- );
240
- }
241
-
242
- function getMutationErrorMessage(error: unknown) {
243
- let message = typeof error === "string" ? error : "";
244
-
245
- if (error instanceof Error) {
246
- message = error.message;
247
- } else if (
248
- typeof error === "object" &&
249
- error !== null &&
250
- "message" in error &&
251
- typeof error.message === "string"
252
- ) {
253
- message = error.message;
254
- }
255
-
256
- if (!message || message === "Bad Request") {
257
- message = "Invalid code, please try again.";
258
- }
259
-
260
- return message;
261
- }
@@ -1,166 +0,0 @@
1
- // Modified from https://github.com/bvaughn/react-error-boundary
2
- // Copyright (c) 2020 Brian Vaughn, MIT License
3
-
4
- import * as React from "react";
5
-
6
- type ErrorBoundaryState =
7
- | { didCatch: true; error: any }
8
- | { didCatch: false; error: null };
9
-
10
- export interface ErrorBoundaryContextValue {
11
- didCatch: boolean;
12
- error: any;
13
- resetErrorBoundary: (...args: any[]) => void;
14
- }
15
-
16
- const ErrorBoundaryContext =
17
- React.createContext<ErrorBoundaryContextValue | null>(null);
18
- ErrorBoundaryContext.displayName = "ErrorBoundaryContext";
19
-
20
- const initialState: ErrorBoundaryState = {
21
- didCatch: false,
22
- error: null,
23
- } satisfies ErrorBoundaryState;
24
-
25
- export class ErrorBoundary extends React.Component<
26
- ErrorBoundaryProps,
27
- ErrorBoundaryState
28
- > {
29
- constructor(props: ErrorBoundaryProps) {
30
- super(props);
31
-
32
- this.resetErrorBoundary = this.resetErrorBoundary.bind(this);
33
- this.state = initialState;
34
- }
35
-
36
- static getDerivedStateFromError(error: Error) {
37
- return { didCatch: true, error };
38
- }
39
-
40
- resetErrorBoundary(...args: any[]) {
41
- const { error } = this.state;
42
-
43
- if (error !== null) {
44
- this.props.onReset?.({
45
- args,
46
- reason: "imperative-api",
47
- });
48
-
49
- this.setState(initialState);
50
- }
51
- }
52
-
53
- componentDidCatch(error: Error, info: React.ErrorInfo) {
54
- this.props.onError?.(error, info);
55
- }
56
-
57
- componentDidUpdate(
58
- prevProps: ErrorBoundaryProps,
59
- prevState: ErrorBoundaryState,
60
- ) {
61
- const { didCatch } = this.state;
62
- const { resetKeys } = this.props;
63
-
64
- // There's an edge case where if the thing that triggered the error happens
65
- // to *also* be in the resetKeys array, we'd end up resetting the error
66
- // boundary immediately.
67
- //
68
- // This would likely trigger a second error to be thrown. So we make sure
69
- // that we don't check the resetKeys on the first call of cDU after the
70
- // error is set.
71
- if (
72
- didCatch &&
73
- prevState.error !== null &&
74
- hasArrayChanged(prevProps.resetKeys, resetKeys)
75
- ) {
76
- this.props.onReset?.({
77
- next: resetKeys,
78
- prev: prevProps.resetKeys,
79
- reason: "keys",
80
- });
81
-
82
- this.setState(initialState);
83
- }
84
- }
85
-
86
- render() {
87
- const { children, fallbackRender, FallbackComponent, fallback } =
88
- this.props;
89
- const { didCatch, error } = this.state;
90
-
91
- let childToRender = children;
92
-
93
- if (didCatch) {
94
- const props: FallbackProps = {
95
- error,
96
- resetErrorBoundary: this.resetErrorBoundary,
97
- };
98
-
99
- if (typeof fallbackRender === "function") {
100
- childToRender = fallbackRender(props);
101
- } else if (FallbackComponent) {
102
- childToRender = React.createElement(FallbackComponent, props);
103
- } else if (fallback !== undefined) {
104
- childToRender = fallback;
105
- } else {
106
- throw error;
107
- }
108
- }
109
-
110
- return (
111
- <ErrorBoundaryContext.Provider
112
- value={{
113
- didCatch,
114
- error,
115
- resetErrorBoundary: this.resetErrorBoundary,
116
- }}
117
- >
118
- {childToRender}
119
- </ErrorBoundaryContext.Provider>
120
- );
121
- }
122
- }
123
-
124
- function hasArrayChanged(a: any[] = [], b: any[] = []) {
125
- return (
126
- a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]))
127
- );
128
- }
129
-
130
- export type FallbackProps = {
131
- error: any;
132
- resetErrorBoundary: (...args: any[]) => void;
133
- };
134
-
135
- type ErrorBoundarySharedProps = React.PropsWithChildren<{
136
- onError?: (error: Error, info: React.ErrorInfo) => void;
137
- onReset?: (
138
- details:
139
- | { reason: "imperative-api"; args: any[] }
140
- | { reason: "keys"; prev: any[] | undefined; next: any[] | undefined },
141
- ) => void;
142
- resetKeys?: any[];
143
- }>;
144
-
145
- export type ErrorBoundaryPropsWithComponent = ErrorBoundarySharedProps & {
146
- fallback?: never;
147
- FallbackComponent: React.ComponentType<FallbackProps>;
148
- fallbackRender?: never;
149
- };
150
-
151
- export type ErrorBoundaryPropsWithRender = ErrorBoundarySharedProps & {
152
- fallback?: never;
153
- FallbackComponent?: never;
154
- fallbackRender: (props: FallbackProps) => React.ReactNode;
155
- };
156
-
157
- export type ErrorBoundaryPropsWithFallback = ErrorBoundarySharedProps & {
158
- fallback: React.ReactNode;
159
- FallbackComponent?: never;
160
- fallbackRender?: never;
161
- };
162
-
163
- export type ErrorBoundaryProps =
164
- | ErrorBoundaryPropsWithFallback
165
- | ErrorBoundaryPropsWithComponent
166
- | ErrorBoundaryPropsWithRender;
package/src/lib/errors.ts DELETED
@@ -1,49 +0,0 @@
1
- type QueryType = "query" | "mutation";
2
- type RecordType = "users" | "roles";
3
-
4
- export class FetchError extends Error {
5
- queryType: QueryType;
6
- recordType: RecordType;
7
- context: unknown;
8
- constructor(args: {
9
- message: string;
10
- queryType: QueryType;
11
- recordType: RecordType;
12
- context?: unknown;
13
- }) {
14
- super(args.message);
15
- this.name = "FetchError";
16
- this.queryType = args.queryType;
17
- this.recordType = args.recordType;
18
- this.context = args.context;
19
- }
20
- }
21
-
22
- export class ApiError extends Error {
23
- status: number;
24
- queryType: QueryType;
25
- recordType: RecordType;
26
- context: unknown;
27
- constructor(args: {
28
- message: string;
29
- queryType: QueryType;
30
- recordType: RecordType;
31
- status: number;
32
- context?: unknown;
33
- }) {
34
- super(args.message);
35
- this.name = "ApiError";
36
- this.status = args.status;
37
- this.queryType = args.queryType;
38
- this.recordType = args.recordType;
39
- }
40
- }
41
-
42
- export class NoAuthTokenError extends Error {
43
- context: unknown;
44
- constructor(args?: { context?: unknown }) {
45
- super("No auth token provided");
46
- this.name = "NoAuthTokenError";
47
- this.context = args?.context;
48
- }
49
- }
@@ -1,70 +0,0 @@
1
- import * as React from "react";
2
- import { Flex, Heading, Text } from "@radix-ui/themes";
3
- import { ApiError, FetchError, NoAuthTokenError } from "./errors";
4
- import { Cross2Icon } from "@radix-ui/react-icons";
5
-
6
- export function GenericError({ error }: { error: unknown }) {
7
- React.useEffect(() => {
8
- console.error(error);
9
- }, [error]);
10
-
11
- const render = (heading: string, message: React.ReactNode) => (
12
- <Flex p="6" justify="center" align="center" direction="column">
13
- <Flex
14
- align="center"
15
- justify="center"
16
- width="32px"
17
- height="32px"
18
- mb="2"
19
- style={{
20
- borderRadius: "9999px",
21
- backgroundColor: "var(--red-a4)",
22
- color: "var(--red-a11)",
23
- }}
24
- >
25
- <Cross2Icon width="24px" height="24px" />
26
- </Flex>
27
-
28
- <Flex direction="column" gap="1" maxWidth="420px">
29
- <Heading size="5" align="center" mb="1" wrap="balance" as="h3">
30
- {heading}
31
- </Heading>
32
-
33
- <Text as="p" align="center" wrap="balance" color="gray">
34
- {message}
35
- </Text>
36
- </Flex>
37
- </Flex>
38
- );
39
-
40
- if (error instanceof FetchError) {
41
- return render(
42
- "Error fetching data",
43
- "An error occurred. You may need to configure CORS in the WorkOS Dashboard. " +
44
- "Contact your organization admin for support.",
45
- );
46
- }
47
-
48
- if (error instanceof NoAuthTokenError) {
49
- return render(
50
- "Error fetching data",
51
- "Authorization error. You likely forgot to provide an authorization " +
52
- "token to the Users Management Widget.",
53
- );
54
- }
55
-
56
- if (error instanceof ApiError && error.status === 404) {
57
- // The widgets API treats all authorization errors as 404s. If there is a
58
- // legitimate 404, it's a bug on our end but there's currently no way to
59
- // distinguish between the two.
60
- return render(
61
- "Error fetching data",
62
- "Authorization error. Contact your organization admin for support.",
63
- );
64
- }
65
-
66
- return render(
67
- "Error fetching data",
68
- "An unknown error occurred. If the problem continues, contact the site owner.",
69
- );
70
- }
@@ -1,26 +0,0 @@
1
- import { Flex, FlexProps } from "@radix-ui/themes";
2
- import { forwardRef } from "react";
3
-
4
- export const IconPanel = forwardRef<HTMLDivElement, FlexProps>(
5
- function IconPanel({ children, ...props }, ref) {
6
- return (
7
- <Flex
8
- ref={ref}
9
- width="32px"
10
- height="32px"
11
- align="center"
12
- justify="center"
13
- style={{
14
- borderWidth: 1,
15
- borderStyle: "solid",
16
- borderColor: "var(--gray-4)",
17
- borderRadius: "var(--radius-3)",
18
- backgroundColor: "var(--gray-2)",
19
- }}
20
- {...props}
21
- >
22
- {children}
23
- </Flex>
24
- );
25
- },
26
- );
package/src/lib/icons.tsx DELETED
@@ -1,21 +0,0 @@
1
- import { SVGProps } from "react";
2
-
3
- export function PasskeyIcon(props: SVGProps<SVGSVGElement>) {
4
- return (
5
- <svg
6
- xmlns="http://www.w3.org/2000/svg"
7
- width={16}
8
- height={16}
9
- viewBox="0 0 16 16"
10
- fill="none"
11
- {...props}
12
- >
13
- <path
14
- fill="currentColor"
15
- fillRule="evenodd"
16
- d="M5.86 1.005a2.86 2.86 0 1 0 0 5.719 2.86 2.86 0 0 0 0-5.72ZM2 3.865a3.86 3.86 0 1 1 4.899 3.717c.974.166 1.699.58 2.368 1.196a.5.5 0 1 1-.678.735C7.882 8.862 7.136 8.5 5.859 8.5c-2.036 0-3.195.693-3.867 1.613-.602.824-.86 1.893-.917 2.986h8.618a.5.5 0 0 1 0 1H.563a.5.5 0 0 1-.5-.5c0-1.356.248-2.88 1.121-4.076.743-1.018 1.897-1.741 3.582-1.957A3.861 3.861 0 0 1 2 3.864Zm12.185 6.509a2.892 2.892 0 1 0-2.212-.024v4.428L13.197 16l2.036-2.037-1.271-1.27 1.271-1.272-1.046-1.047Zm-.212-3.423a.847.847 0 1 1-1.695 0 .847.847 0 0 1 1.695 0Z"
17
- clipRule="evenodd"
18
- />
19
- </svg>
20
- );
21
- }