diff --git a/sdks/js/packages/core/package.json b/sdks/js/packages/core/package.json index 13124bc5f..1c6b648ab 100644 --- a/sdks/js/packages/core/package.json +++ b/sdks/js/packages/core/package.json @@ -72,7 +72,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.3.1", - "@raystack/apsara": "0.11.2", + "@raystack/apsara": "0.11.3", "@tanstack/react-router": "0.0.1-beta.174", "axios": "^1.5.0", "class-variance-authority": "^0.7.0", diff --git a/sdks/js/packages/core/react/components/onboarding/magiclink-verify.tsx b/sdks/js/packages/core/react/components/onboarding/magiclink-verify.tsx index 950301935..b13c89e63 100644 --- a/sdks/js/packages/core/react/components/onboarding/magiclink-verify.tsx +++ b/sdks/js/packages/core/react/components/onboarding/magiclink-verify.tsx @@ -15,7 +15,6 @@ import { hasWindow } from '~/utils/index'; // @ts-ignore import styles from './onboarding.module.css'; - type MagicLinkVerifyProps = ComponentPropsWithRef & { logo?: React.ReactNode; title?: string; @@ -51,32 +50,36 @@ export const MagicLinkVerify = ({ codeParam && setCodeParam(codeParam); }, []); - const OTPVerifyClickHandler = useCallback(async () => { - setLoading(true); - try { - if (!client) return; + const OTPVerifyHandler = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + if (!client) return; - await client.frontierServiceAuthCallback({ - strategyName: 'mailotp', - code: otp, - state: stateParam - }); + await client.frontierServiceAuthCallback({ + strategyName: 'mailotp', + code: otp, + state: stateParam + }); - const searchParams = new URLSearchParams( - hasWindow() ? window.location.search : `` - ); - const redirectURL = - searchParams.get('redirect_uri') || searchParams.get('redirectURL'); + const searchParams = new URLSearchParams( + hasWindow() ? window.location.search : `` + ); + const redirectURL = + searchParams.get('redirect_uri') || searchParams.get('redirectURL'); - // @ts-ignore - window.location = redirectURL ? redirectURL : window.location.origin; - } catch (error) { - console.log(error); - setSubmitError('Please enter a valid verification code'); - } finally { - setLoading(false); - } - }, [otp]); + // @ts-ignore + window.location = redirectURL ? redirectURL : window.location.origin; + } catch (error) { + console.log(error); + setSubmitError('Please enter a valid verification code'); + } finally { + setLoading(false); + } + }, + [otp] + ); return ( @@ -99,7 +102,11 @@ export const MagicLinkVerify = ({ Enter code manually ) : ( - +
- Continue with login code + + {loading ? 'Submitting...' : 'Continue with login code'} + - +
)} Back to login diff --git a/sdks/js/packages/core/react/components/onboarding/magiclink.tsx b/sdks/js/packages/core/react/components/onboarding/magiclink.tsx index ece894c5c..346f2ec0f 100644 --- a/sdks/js/packages/core/react/components/onboarding/magiclink.tsx +++ b/sdks/js/packages/core/react/components/onboarding/magiclink.tsx @@ -26,28 +26,32 @@ export const MagicLink = ({ children, ...props }: MagicLinkProps) => { const [email, setEmail] = useState(''); const [state, setState] = useState(''); - const magicLinkClickHandler = useCallback(async () => { - setLoading(true); - try { - if (!client) return; + const magicLinkHandler = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + if (!client) return; - const { - data: { state = '' } - } = await client.frontierServiceAuthenticate('mailotp', { - email, - callbackUrl: config.callbackUrl - }); + const { + data: { state = '' } + } = await client.frontierServiceAuthenticate('mailotp', { + email, + callbackUrl: config.callbackUrl + }); - const searchParams = new URLSearchParams({ state, email }); + const searchParams = new URLSearchParams({ state, email }); - // @ts-ignore - window.location = `${ - config.redirectMagicLinkVerify - }?${searchParams.toString()}`; - } finally { - setLoading(false); - } - }, [client, config.callbackUrl, config.redirectMagicLinkVerify, email]); + // @ts-ignore + window.location = `${ + config.redirectMagicLinkVerify + }?${searchParams.toString()}`; + } finally { + setLoading(false); + } + }, + [client, config.callbackUrl, config.redirectMagicLinkVerify, email] + ); const handleChange = (event: React.ChangeEvent) => { setEmail(event.target.value); @@ -66,7 +70,10 @@ export const MagicLink = ({ children, ...props }: MagicLinkProps) => { ); return ( -
+
{ ...(!email ? styles.disabled : {}) }} disabled={!email} - onClick={magicLinkClickHandler} + type="submit" > - {loading ? 'loading...' : 'Continue with Emails'} + {loading ? 'loading...' : 'Continue with Email'} -
+ ); }; diff --git a/sdks/js/packages/core/react/components/organization/domain/domain.columns.tsx b/sdks/js/packages/core/react/components/organization/domain/domain.columns.tsx index 4086cec62..66d03290e 100644 --- a/sdks/js/packages/core/react/components/organization/domain/domain.columns.tsx +++ b/sdks/js/packages/core/react/components/organization/domain/domain.columns.tsx @@ -12,6 +12,7 @@ export const getColumns: ( isLoading?: boolean ) => ColumnDef[] = (canCreateDomain, isLoading) => [ { + header: 'Name', accessorKey: 'name', meta: { style: { @@ -29,6 +30,7 @@ export const getColumns: ( } }, { + header: 'Created at', accessorKey: 'created_at', cell: isLoading ? () => diff --git a/sdks/js/packages/core/react/components/organization/members/invite.tsx b/sdks/js/packages/core/react/components/organization/members/invite.tsx index 97bedb27b..4a4821acc 100644 --- a/sdks/js/packages/core/react/components/organization/members/invite.tsx +++ b/sdks/js/packages/core/react/components/organization/members/invite.tsx @@ -11,7 +11,7 @@ import { import { yupResolver } from '@hookform/resolvers/yup'; import { useNavigate } from '@tanstack/react-router'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import Skeleton from 'react-loading-skeleton'; import { toast } from 'sonner'; @@ -19,10 +19,11 @@ import * as yup from 'yup'; import cross from '~/react/assets/cross.svg'; import { useFrontier } from '~/react/contexts/FrontierContext'; import { V1Beta1Group, V1Beta1Role } from '~/src'; +import { PERMISSIONS } from '~/utils'; const inviteSchema = yup.object({ type: yup.string().required(), - team: yup.string().required(), + team: yup.string(), emails: yup.string().required() }); @@ -44,35 +45,45 @@ export const InviteMember = () => { const navigate = useNavigate({ from: '/members/modal' }); const { client, activeOrganization: organization } = useFrontier(); - async function onSubmit({ emails, type, team }: InviteSchemaType) { - const emailList = emails - .split(',') - .map(e => e.trim()) - .filter(str => str.length > 0); - - if (!organization?.id) return; - if (!emailList.length) return; - if (!type) return; - if (!team) return; - - try { - await client?.frontierServiceCreateOrganizationInvitation( - organization?.id, - { - userIds: emailList, - groupIds: [team], - roleIds: [type] - } - ); - toast.success('memebers added'); - - navigate({ to: '/members' }); - } catch ({ error }: any) { - toast.error('Something went wrong', { - description: error.message - }); - } - } + const values = watch(['emails', 'team', 'type']); + + const isGroupRole = useMemo(() => { + const role = values[2] && roles.find(r => r.id === values[2]); + return role && role.scopes?.includes(PERMISSIONS.GroupNamespace); + }, [roles, values]); + + const onSubmit = useCallback( + async ({ emails, type, team }: InviteSchemaType) => { + const emailList = emails + .split(',') + .map(e => e.trim()) + .filter(str => str.length > 0); + + if (!organization?.id) return; + if (!emailList.length) return; + if (!type) return; + if (isGroupRole && !team) return; + + try { + await client?.frontierServiceCreateOrganizationInvitation( + organization?.id, + { + userIds: emailList, + groupIds: isGroupRole && team ? [team] : undefined, + roleIds: [type] + } + ); + toast.success('memebers added'); + + navigate({ to: '/members' }); + } catch ({ error }: any) { + toast.error('Something went wrong', { + description: error.message + }); + } + }, + [client, navigate, organization?.id, isGroupRole] + ); useEffect(() => { async function getInformation() { @@ -83,11 +94,24 @@ export const InviteMember = () => { const { // @ts-ignore data: { roles: orgRoles } - } = await client?.frontierServiceListOrganizationRoles(organization.id); + } = await client?.frontierServiceListOrganizationRoles( + organization.id, + { + scopes: [ + PERMISSIONS.OrganizationNamespace, + PERMISSIONS.GroupNamespace + ] + } + ); const { // @ts-ignore data: { roles } - } = await client?.frontierServiceListRoles(); + } = await client?.frontierServiceListRoles({ + scopes: [ + PERMISSIONS.OrganizationNamespace, + PERMISSIONS.GroupNamespace + ] + }); const { // @ts-ignore data: { groups } @@ -105,8 +129,6 @@ export const InviteMember = () => { getInformation(); }, [client, organization?.id]); - const values = watch(['emails', 'team', 'type']); - const isDisabled = useMemo(() => { const [emails, team, type] = values; const emailList = @@ -114,8 +136,10 @@ export const InviteMember = () => { ?.split(',') .map((e: string) => e.trim()) .filter(str => str.length > 0) || []; - return emailList.length <= 0 || !team || !type || isSubmitting; - }, [isSubmitting, values]); + return ( + emailList.length <= 0 || !type || isSubmitting || (isGroupRole && !team) + ); + }, [isGroupRole, isSubmitting, values]); return ( @@ -141,7 +165,7 @@ export const InviteMember = () => { gap="medium" style={{ padding: '24px 32px' }} > - + (