Skip to content

Commit

Permalink
Feat: invite users to workspace (tegonhq#129)
Browse files Browse the repository at this point in the history
* Feat: invite users to workspace

* Feat: added member invitation in UI

* Fix: labels is replaced with label

* Fix: default to light

* Feat: invite functionality

* Fix: comments ui

* Fix: comments ui

* Fix: signup and invite flow

* Fix: signup and invite flow

* Fix: signup and invite flow

* Fix: infinite reload

* Fix: infinite reload

* fix: remove unused files

---------

Co-authored-by: Manoj K <saimanoj58@gmail.com>
  • Loading branch information
harshithmullapudi and saimanoj authored Jul 3, 2024
1 parent 89102fd commit 3837d57
Show file tree
Hide file tree
Showing 57 changed files with 1,066 additions and 107 deletions.
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
POSTGRESQL_CONNECTION_URI: ${SUPERTOKEN_DATABASE_URL}
REFRESH_TOKEN_VALIDITY: 2592000 # 30 days in seconds
ACCESS_TOKEN_VALIDITY: 2592000 # 30 days in seconds
PASSWORD_RESET_TOKEN_LIFETIME: 86400
ports:
- 3567:3567
networks:
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@tiptap/extension-heading": "^2.2.4",
"@tiptap/extension-placeholder": "^2.2.4",
"@tiptap/extension-text": "^2.2.4",
"@typeform/embed-react": "^3.17.0",
"@types/lodash.clonedeep": "^4.5.9",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/common/layouts/app-layout/workspace-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useContextStore } from 'store/global-context-provider';

export const WorkspaceDropdown = observer(() => {
const { workspaceStore } = useContextStore();
const { query, replace } = useRouter();
const { query, push, replace } = useRouter();

return (
<DropdownMenu>
Expand All @@ -42,7 +42,7 @@ export const WorkspaceDropdown = observer(() => {
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => {
replace(`/${query.workspaceSlug}/settings/account/profile`);
push(`/${query.workspaceSlug}/settings/account/profile`);
}}
>
Preferences
Expand All @@ -52,12 +52,18 @@ export const WorkspaceDropdown = observer(() => {
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => {
replace(`/${query.workspaceSlug}/settings/overview`);
push(`/${query.workspaceSlug}/settings/overview`);
}}
>
Workspace settings
</DropdownMenuItem>
<DropdownMenuItem>Invite & manage members</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
push(`/${query.workspaceSlug}/settings/members`);
}}
>
Invite & manage members
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function SidebarNav({ className, ...props }: SidebarNavProps) {
`/${query.workspaceSlug}/team/${teamsStore.teams[0].identifier}/all`,
);
}}
className="group px-3 py-4 flex justify-start"
className="group my-2 px-4 flex justify-start"
>
<ChevronLeft className="mr-2 " size={20} />
Settings
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const buttonVariants = cva(
variants: {
variant: {
default:
'bg-primary shadow hover:bg-primary/90 dark:hover:bg-primary/90',
'bg-primary text-white shadow hover:bg-primary/90 dark:hover:bg-primary/90',
destructive: 'text-red-500 bg-grayAlpha-100 border-none',
outline: 'border shadow-sm hover:bg-gray-100 shadow-none',
secondary: 'bg-grayAlpha-100 border-none',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return (
<textarea
className={cn(
'flex min-h-[30px] w-full rounded-md border border-input bg-white dark:bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex min-h-[30px] w-full rounded-md bg-input px-3 py-2 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
id={id}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Toast = React.forwardRef<
className={cn(
toastVariants({ variant }),
className,
'font-sans bg-grayAlpha-100 backdrop-blur-md shadow-md border rounded-md',
'font-sans bg-gray-100 backdrop-blur-md shadow-md border-0 rounded-md',
)}
{...props}
/>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/modules/auth/invites/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** Copyright (c) 2024, Tegon, all rights reserved. **/

export * from './invites';
114 changes: 114 additions & 0 deletions frontend/src/modules/auth/invites/invites.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/** Copyright (c) 2024, Tegon, all rights reserved. **/

import { useRouter } from 'next/router';
import React from 'react';
import { SessionAuth, signOut } from 'supertokens-auth-react/recipe/session';

import { AuthLayout } from 'common/layouts/auth-layout';
import { UserDataWrapper } from 'common/wrappers/user-data-wrapper';

import { AvatarText } from 'components/ui/avatar';
import { Button } from 'components/ui/button';
import { useToast } from 'components/ui/use-toast';

import { useInviteActionMutation } from 'services/workspace';

import { UserContext, type Invite } from 'store/user-context';

export function Invites() {
const context = React.useContext(UserContext);
const { toast } = useToast();
const router = useRouter();
const { mutate: inviteAction, isLoading } = useInviteActionMutation({
onSuccess: (data: Invite) => {
if (data.status === 'ACCEPTED') {
router.replace('/');
toast({
title: 'Invitation accepted',
description: 'Current invitation for the workspace has been accepted',
});
}
},
});

React.useEffect(() => {
if (context?.workspaces.length > 0) {
router.replace(`/${context.workspaces[0].slug}`);
} else if (context?.invites.length === 0) {
router.replace(`/waitlist`);
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [context?.workspaces]);

const onAction = (accept: boolean, inviteId: string) => {
inviteAction({
accept,
inviteId,
});
};

return (
<AuthLayout>
<div className="flex flex-col gap-2 items-center">
<div className="flex flex-col min-w-[500px] bg-background-2 rounded p-4">
<h2 className="text-lg"> Join the workspaces</h2>
<p className="text-muted-foreground">
You have been invited to these workspaces
</p>

<div className="flex flex-col gap-2 mt-2">
{context.invites.map((invite: Invite) => {
return (
<div
key={invite.id}
className="bg-background-3 p-3 rounded flex justify-between items-center"
>
<div className="flex gap-2">
<AvatarText text={invite.workspace.name} />
{invite.workspace.name}
</div>

<div className="flex gap-2">
<Button
variant="ghost"
disabled={isLoading}
onClick={() => onAction(false, invite.id)}
>
Decline
</Button>
<Button
variant="secondary"
disabled={isLoading}
onClick={() => onAction(true, invite.id)}
>
Accept
</Button>
</div>
</div>
);
})}
</div>
</div>
<Button
variant="secondary"
onClick={async () => {
await signOut();

router.replace('/auth/signin');
}}
>
Log out
</Button>
</div>
</AuthLayout>
);
}

Invites.getLayout = function getLayout(page: React.ReactElement) {
return (
<SessionAuth>
<UserDataWrapper>{page}</UserDataWrapper>
</SessionAuth>
);
};
17 changes: 14 additions & 3 deletions frontend/src/modules/auth/signin-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,20 @@ export function SignForm() {
Forgot password?
</Link>

<Button type="submit" full isLoading={isLoading}>
Continue
</Button>
<div className="flex flex-col gap-2 justify-end">
<Button type="submit" size="lg" full isLoading={isLoading}>
Continue
</Button>

<Link
className={cn(
buttonVariants({ variant: 'secondary', size: 'lg', full: true }),
)}
href="/auth/signup"
>
Create a new account
</Link>
</div>
</form>
</Form>
);
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/modules/auth/signin.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
/** Copyright (c) 2024, Tegon, all rights reserved. **/
'use client';

import { RiGoogleFill } from '@remixicon/react';
import getConfig from 'next/config';
import { getAuthorisationURLWithQueryParamsAndSetState } from 'supertokens-auth-react/recipe/thirdpartyemailpassword';

import { AuthLayout } from 'common/layouts/auth-layout';
import { AuthGuard } from 'common/wrappers/auth-guard';

import { Button } from 'components/ui/button';
import { Separator } from 'components/ui/separator';

import { SignForm } from './signin-form';

const { publicRuntimeConfig } = getConfig();

export function SignIn() {
async function googleSignInClicked() {
try {
const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({
thirdPartyId: 'google',

frontendRedirectURI: `${publicRuntimeConfig.NEXT_PUBLIC_BASE_HOST}/auth/callback/google`,
});

window.location.assign(authUrl);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
if (err.isSuperTokensGeneralError === true) {
// this may be a custom error message sent from the API by you.
console.error(err.message);
} else {
console.error('Oops! Something went wrong.');
}
}
}

return (
<AuthLayout>
<div className="flex flex-col w-[360px]">
Expand All @@ -15,6 +44,12 @@ export function SignIn() {
to continue to Tegon
</div>

<Button className="flex gap-2" size="lg" onClick={googleSignInClicked}>
<RiGoogleFill size={18} /> Sign in with google
</Button>

<Separator className="mt-4" />

<SignForm />

<div className="mt-4 text-xs text-muted-foreground">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/modules/auth/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function SignUp() {
}
}
}

return (
<AuthLayout>
<div className="flex flex-col w-[360px]">
Expand All @@ -39,7 +40,7 @@ export function SignUp() {
to continue to Tegon
</div>

<Button className="flex gap-2" onClick={googleSignInClicked}>
<Button className="flex gap-2" size="lg" onClick={googleSignInClicked}>
<RiGoogleFill size={18} /> Sign up with google
</Button>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,17 @@ export function GenericCommentActivity(props: GenericCommentActivityProps) {
) : (
<div
className={cn(
'text-base mt-2',
!comment.parentId && 'p-3 pt-0',
comment.parentId && 'pb-3',
'text-base',
!comment.parentId && 'p-3 py-2 pt-0',
comment.parentId && 'pb-2',
)}
>
<Editor value={comment.body} editable={false} className="mb-0" />
</div>
)}

{childComments.length > 0 && (
<div className="w-full border-t p-3 pb-0">
<div className="w-full border-t px-3 py-2 pb-0">
{childComments.map(
(subComment: IssueCommentType, index: number) => (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function ReplyComment({
};

return (
<div className="flex items-start w-full border-t p-3 pb-0">
<div className="flex items-start w-full border-t px-3 py-2 pb-0">
<AvatarText text={currentUser.fullname} className="text-[9px]" />

<div className="w-full relative">
Expand All @@ -52,7 +52,7 @@ export function ReplyComment({
!commentValue && setShowReplyButton(false);
}}
onChange={(e) => setCommentValue(e)}
className="w-full bg-transparent p-3 pt-0"
className="w-full bg-transparent px-3 py-2 pt-0"
/>
<div className="flex justify-between items-center">
{showReplyButton && (
Expand Down
Loading

0 comments on commit 3837d57

Please sign in to comment.