diff --git a/apps/mobile/app/services/client/requests/auth.ts b/apps/mobile/app/services/client/requests/auth.ts index 897435e88..9f3d4f1ff 100644 --- a/apps/mobile/app/services/client/requests/auth.ts +++ b/apps/mobile/app/services/client/requests/auth.ts @@ -60,10 +60,7 @@ type IUEmployeeParam = { * @param {IUEmployeeParam} The employee parameters, including bearer token and optional relations. * @returns A Promise resolving to the IUser object with the desired relations and employee details. */ -export const currentAuthenticatedUserRequest = ({ - bearer_token, - relations = ['role', 'tenant'] -}: IUEmployeeParam) => { +export const currentAuthenticatedUserRequest = ({ bearer_token, relations = ['role', 'tenant'] }: IUEmployeeParam) => { // Create a new instance of URLSearchParams for query string construction const query = new URLSearchParams(); @@ -104,9 +101,9 @@ export function sendAuthCodeRequest(email: string) { // auth/signin.email/confirm Gives response with tenantId's export function verifyAuthCodeRequest(email: string, code: string) { return serverFetch({ - path: '/auth/signin.email/confirm?includeTeams=true', + path: '/auth/signin.email/confirm', method: 'POST', - body: { email, code } + body: { email, code, includeTeams: true } }); } diff --git a/apps/web/app/[locale]/auth/error/page.tsx b/apps/web/app/[locale]/auth/error/page.tsx index a7f1e0f3d..9397278fb 100644 --- a/apps/web/app/[locale]/auth/error/page.tsx +++ b/apps/web/app/[locale]/auth/error/page.tsx @@ -1,30 +1,30 @@ 'use client'; +import { useSearchParams } from 'next/navigation'; import SadCry from '@components/ui/svgs/sad-cry'; import { Text } from 'lib/components'; -import { useTranslations } from 'next-intl'; import Link from 'next/link'; -/** - * Error page - * - * @description the page that will be shown if any social login failed. This is not related to Next.js error file - * @returns a custom component that shows error - */ -export default function Page() { - const t = useTranslations(); - return ( +import UnauthorizedPage from '@components/pages/unauthorized'; + +enum Error { + Configuration = 'Configuration', + AccessDenied = 'AccessDenied' +} + +const errorMap = { + [Error.Configuration]: (
- {t('pages.error.TITLE')} + Error !
- {t('pages.error.HEADING_TITLE')} + Something went wrong !
- {t('pages.error.HEADING_DESCRIPTION')} + If the problem persists, send a distress signal to our support team. Try again
@@ -33,5 +33,18 @@ export default function Page() {

Error on signing

- ); + ), + [Error.AccessDenied]: +}; + +/** + * Error page + * + * @description the page that will be shown if any social login failed. This is not related to Next.js error file + * @returns a custom component that shows error + */ +export default function Page() { + const search = useSearchParams(); + const error = search?.get('error') as Error; + return <>{errorMap[error]}; } diff --git a/apps/web/app/[locale]/auth/social-logins-buttons.tsx b/apps/web/app/[locale]/auth/social-logins-buttons.tsx index e8a06d091..3555b059b 100644 --- a/apps/web/app/[locale]/auth/social-logins-buttons.tsx +++ b/apps/web/app/[locale]/auth/social-logins-buttons.tsx @@ -12,7 +12,7 @@ export default function SocialLogins() { { name: 'twitter', icon: } ].filter((provider) => providerNames[provider.name] !== undefined); - return ( + return mappedProviders.length > 0 ? (
@@ -38,5 +38,7 @@ export default function SocialLogins() { ))}
+ ) : ( + <> ); } diff --git a/apps/web/app/api/auth/signin-email-social-login/route.ts b/apps/web/app/api/auth/signin-email-social-login/route.ts index 237a1207d..d42837f62 100644 --- a/apps/web/app/api/auth/signin-email-social-login/route.ts +++ b/apps/web/app/api/auth/signin-email-social-login/route.ts @@ -1,18 +1,12 @@ -import { validateForm } from '@app/helpers/validations'; import { signWithSocialLoginsRequest } from '@app/services/server/requests'; +import { ProviderEnum } from '@app/services/server/requests/OAuth'; import { NextResponse } from 'next/server'; export async function POST(req: Request) { - const body = (await req.json()) as { email: string; password: string }; + const body = (await req.json()) as { provider: ProviderEnum; access_token: string }; - const { errors, isValid } = validateForm(['email'], body); - - if (!isValid) { - return NextResponse.json({ errors }, { status: 400 }); - } - - const { data } = await signWithSocialLoginsRequest(body.email); + const { data } = await signWithSocialLoginsRequest(body.provider, body.access_token); return NextResponse.json(data); } diff --git a/apps/web/app/services/client/api/auth.ts b/apps/web/app/services/client/api/auth.ts index 3cfbfd34e..fe53873f5 100644 --- a/apps/web/app/services/client/api/auth.ts +++ b/apps/web/app/services/client/api/auth.ts @@ -13,6 +13,7 @@ import { } from '@app/constants'; import qs from 'qs'; import { signInEmailConfirmGauzy, signInWorkspaceGauzy } from './auth/invite-accept'; +import { ProviderEnum } from '@app/services/server/requests/OAuth'; /** * Fetches data of the authenticated user with specified relations and the option to include employee details. @@ -106,19 +107,14 @@ export const signInEmailAPI = (email: string) => { }; export function signInEmailPasswordAPI(email: string, password: string) { - const endpoint = GAUZY_API_BASE_SERVER_URL.value - ? '/auth/signin.email.password?includeTeams=true' - : `/auth/signin-email-password`; - - return post(endpoint, { email, password }); + const endpoint = GAUZY_API_BASE_SERVER_URL.value ? '/auth/signin.email.password' : `/auth/signin-email-password`; + return post(endpoint, { email, password, includeTeams: true }); } -export function signInEmailSocialLoginAPI(email: string) { - const endpoint = GAUZY_API_BASE_SERVER_URL.value - ? '/auth/signin.email.social?includeTeams=true' - : `/auth/signin-email-social`; +export function signInEmailSocialLoginAPI(provider: ProviderEnum, access_token: string) { + const endpoint = GAUZY_API_BASE_SERVER_URL.value ? '/auth/signin.provider.social' : `/auth/signin-email-social`; - return post(endpoint, { email }); + return post(endpoint, { provider, access_token, includeTeams: true }); } export const verifyUserEmailByTokenAPI = (email: string, token: string) => { diff --git a/apps/web/app/services/client/api/auth/invite-accept.ts b/apps/web/app/services/client/api/auth/invite-accept.ts index c07e6a5ea..e97106baa 100644 --- a/apps/web/app/services/client/api/auth/invite-accept.ts +++ b/apps/web/app/services/client/api/auth/invite-accept.ts @@ -95,7 +95,7 @@ export function getAllOrganizationTeamAPI(params: ITeamRequestParams, bearer_tok export const signInEmailConfirmAPI = (data: { code: string; email: string }) => { const { code, email } = data; - return post('/auth/signin.email/confirm?includeTeams=true', { code, email }); + return post('/auth/signin.email/confirm', { code, email, includeTeams: true }); }; export async function signInEmailCodeConfirmGauzy(email: string, code: string) { diff --git a/apps/web/app/services/server/requests/OAuth.ts b/apps/web/app/services/server/requests/OAuth.ts new file mode 100644 index 000000000..d2743e2d0 --- /dev/null +++ b/apps/web/app/services/server/requests/OAuth.ts @@ -0,0 +1,83 @@ +import { type Adapter } from '@auth/core/adapters'; +import { signWithSocialLoginsRequest } from '@app/services/server/requests'; +import { getUserOrganizationsRequest, signInWorkspaceAPI } from '@app/services/client/api/auth/invite-accept'; + +export enum ProviderEnum { + GITHUB = 'github', + GOOGLE = 'google', + FACEBOOK = 'facebook', + TWITTER = 'twitter' +} + +export const GauzyAdapter: Adapter = { + // Provide createUser and other related functions that will call Gauzy APIs when implementing social signup +}; + +async function signIn(provider: ProviderEnum, access_token: string) { + try { + const gauzyUser = await signWithSocialLoginsRequest(provider, access_token); + + if (!gauzyUser) { + return Promise.reject({ + errors: { + email: 'Your account is not yet ready to be used on the Ever Teams Platform' + } + }); + } + + const data = await signInWorkspaceAPI(gauzyUser?.data.confirmed_email, gauzyUser?.data.workspaces[0].token); + const tenantId = data.user?.tenantId || ''; + const token = data.token; + const userId = data.user?.id; + + const { data: organizations } = await getUserOrganizationsRequest({ + tenantId, + userId, + token + }); + + const organization = organizations?.items[0]; + + if (!organization) { + return Promise.reject({ + errors: { + email: 'Your account is not yet ready to be used on the Ever Teams Platform' + } + }); + } + return { data, gauzyUser, organization, tenantId, userId }; + } catch (error) { + throw new Error('Signin error', { cause: error }); + } +} + +export async function signInCallback(provider: ProviderEnum, access_token: string): Promise { + try { + const { gauzyUser, organization } = await signIn(provider, access_token); + return !!gauzyUser && !!organization; + } catch (error) { + return false; + } +} + +export async function jwtCallback(provider: ProviderEnum, access_token: string) { + try { + const { data, gauzyUser, organization, tenantId, userId } = await signIn(provider, access_token); + return { + access_token, + refresh_token: { + token: data.refresh_token + }, + teamId: gauzyUser?.data.workspaces[0].current_teams[0].team_id, + tenantId, + organizationId: organization?.organizationId, + languageId: 'en', + noTeamPopup: true, + userId, + workspaces: gauzyUser?.data.workspaces, + confirmed_mail: gauzyUser?.data.confirmed_email + }; + } catch (error) { + throw new Error('Signin error', { cause: error }); + } +} diff --git a/apps/web/app/services/server/requests/auth.ts b/apps/web/app/services/server/requests/auth.ts index 8d0c93e71..7364730ed 100644 --- a/apps/web/app/services/server/requests/auth.ts +++ b/apps/web/app/services/server/requests/auth.ts @@ -4,6 +4,7 @@ import { ILoginResponse, IRegisterDataRequest, ISigninEmailConfirmResponse } fro import { IUser } from '@app/interfaces/IUserData'; import { serverFetch } from '../fetch'; import qs from 'qs'; +import { ProviderEnum } from './OAuth'; const registerDefaultValue = { appName: APP_NAME, @@ -43,17 +44,17 @@ export function signInEmailRequest(email: string, callbackUrl: string) { export function signInEmailPasswordRequest(email: string, password: string) { return serverFetch({ - path: '/auth/signin.email.password?includeTeams=true', + path: '/auth/signin.email.password', method: 'POST', - body: { email, password } + body: { email, password, includeTeams: true } }); } -export function signWithSocialLoginsRequest(email: string) { +export function signWithSocialLoginsRequest(provider: ProviderEnum, token: string) { return serverFetch({ - path: '/auth/signin.email.social?includeTeams=true', + path: '/auth/signin.email.social', method: 'POST', - body: { email } + body: { provider, token, includeTeams: true } }); } @@ -61,9 +62,9 @@ export const signInEmailConfirmRequest = (data: { code: string; email: string }) const { code, email } = data; return serverFetch({ - path: '/auth/signin.email/confirm?includeTeams=true', + path: '/auth/signin.email/confirm', method: 'POST', - body: { code, email } + body: { code, email, includeTeams: true } }); }; diff --git a/apps/web/auth.ts b/apps/web/auth.ts index 9811ad026..c18f055fc 100644 --- a/apps/web/auth.ts +++ b/apps/web/auth.ts @@ -1,84 +1,29 @@ import NextAuth from 'next-auth'; - -import { signWithSocialLoginsRequest } from '@app/services/server/requests'; -import { getUserOrganizationsRequest, signInWorkspaceAPI } from '@app/services/client/api/auth/invite-accept'; import { filteredProviders } from '@app/utils/check-provider-env-vars'; +import { GauzyAdapter, jwtCallback, ProviderEnum, signInCallback } from '@app/services/server/requests/OAuth'; export const { handlers, signIn, signOut, auth } = NextAuth({ providers: filteredProviders, + adapter: GauzyAdapter, callbacks: { - async signIn({ user }) { - try { - const { email } = user; - const gauzyLoginUser = await signWithSocialLoginsRequest(email ?? ''); - const data = await signInWorkspaceAPI( - gauzyLoginUser?.data.confirmed_email, - gauzyLoginUser?.data.workspaces[0].token - ); - const tenantId = data.user?.tenantId || ''; - const access_token = data.token; - const userId = data.user?.id; - - const { data: organizations } = await getUserOrganizationsRequest({ - tenantId, - userId, - token: access_token - }); - - const organization = organizations?.items[0]; - - if (!organization) { - return false; + async signIn({ account }) { + if (account) { + const { provider, access_token } = account; + if (access_token) { + return await signInCallback(provider as ProviderEnum, access_token); } - - return !!gauzyLoginUser && !!organization; - } catch (error) { - return false; } + return true; }, - async jwt({ token, user, trigger, session }) { + async jwt({ token, user, trigger, session, account }) { if (user) { - const { email } = user; - const gauzyLoginUser = await signWithSocialLoginsRequest(email ?? ''); - const data = await signInWorkspaceAPI( - gauzyLoginUser?.data.confirmed_email, - gauzyLoginUser?.data.workspaces[0].token - ); - const tenantId = data.user?.tenantId || ''; - const access_token = data.token; - const userId = data.user?.id; - - const { data: organizations } = await getUserOrganizationsRequest({ - tenantId, - userId, - token: access_token - }); - - const organization = organizations?.items[0]; - - if (!organization) { - return Promise.reject({ - errors: { - email: 'Your account is not yet ready to be used on the Ever Teams Platform' - } - }); + if (account) { + const { access_token, provider } = account; + if (access_token) { + token.authCookie = await jwtCallback(provider as ProviderEnum, access_token); + } } - - token.authCookie = { - access_token, - refresh_token: { - token: data.refresh_token - }, - teamId: gauzyLoginUser?.data.workspaces[0].current_teams[0].team_id, - tenantId, - organizationId: organization?.organizationId, - languageId: 'en', - noTeamPopup: true, - userId, - workspaces: gauzyLoginUser?.data.workspaces, - confirmed_mail: gauzyLoginUser?.data.confirmed_email - }; } if (trigger === 'update' && session) { diff --git a/apps/web/components/pages/task/description-block/editor-toolbar.tsx b/apps/web/components/pages/task/description-block/editor-toolbar.tsx index b21aa931a..906cfe567 100644 --- a/apps/web/components/pages/task/description-block/editor-toolbar.tsx +++ b/apps/web/components/pages/task/description-block/editor-toolbar.tsx @@ -27,12 +27,20 @@ import { AlignFullIcon, ChevronDownIcon } from 'assets/svg'; +import { BsEmojiSmile } from 'react-icons/bs'; +import { clsxm } from '@app/utils'; +import data from '@emoji-mart/data'; +import Picker from '@emoji-mart/react'; +import { MdOutlineClose } from "react-icons/md"; + interface IToolbarProps { isMarkActive?: (editor: any, format: string) => boolean; isBlockActive?: (editor: any, format: any, blockType?: string) => boolean; + selectEmoji?: (emoji: { native: string }) => void; + showEmojiIcon?: boolean; } -const Toolbar = ({ isMarkActive, isBlockActive }: IToolbarProps) => { +const Toolbar = ({ isMarkActive, isBlockActive, selectEmoji, showEmojiIcon }: IToolbarProps) => { const t = useTranslations(); const editor = useSlateStatic(); const [showLinkPopup, setShowLinkPopup] = useState(false); @@ -43,9 +51,11 @@ const Toolbar = ({ isMarkActive, isBlockActive }: IToolbarProps) => { top: 0 }); const [showDropdown, setShowDropdown] = useState(false); + const [showEmoji, setShowEmoji] = useState(false); const popupRef = useRef(null); const inputRef = useRef(null); const dropdownRef = useRef(null); + const emojiRef = useRef(null); // const handleLinkIconClick = () => { // const selection = editor.selection; @@ -147,6 +157,27 @@ const Toolbar = ({ isMarkActive, isBlockActive }: IToolbarProps) => { }; }, [onClickOutsideOfDropdown]); + useEffect(() => { + const handleClickOutsideOfEmoji = (event: MouseEvent) => { + if (emojiRef.current && !emojiRef.current.contains(event.target as unknown as Node)) { + setShowEmoji(false); + } + }; + + document.addEventListener('mousedown', handleClickOutsideOfEmoji); + return () => { + document.removeEventListener('mousedown', handleClickOutsideOfEmoji); + }; + }, [setShowEmoji]); + + + const addEmoji = (emoji: { native: string }) => { + + if (showEmojiIcon) { + selectEmoji?.(emoji); + } + }; + // const isBlockActiveMemo = useMemo(() => { // return ( // isBlockActive && @@ -161,10 +192,11 @@ const Toolbar = ({ isMarkActive, isBlockActive }: IToolbarProps) => { // }, [editor, isBlockActive]); return ( -
+

{t('pages.taskDetails.DESCRIPTION')}

+ { icon={AlignFullIcon} isBlockActive={isBlockActive as (editor: any, format: any, blockType?: string | undefined) => boolean} /> + + setShowEmoji(true)} className={clsxm('mr-3')} /> + { + showEmoji &&
+
+ setShowEmoji(false)} + className="absolute right-5 cursor-pointer" + /> +
+ +
+ } +