Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/DEAR-117' into DEAR-122-Frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
smuefsmuef committed Jul 19, 2024
2 parents daa615d + 688e80e commit b9d4621
Show file tree
Hide file tree
Showing 19 changed files with 729 additions and 128 deletions.
16 changes: 11 additions & 5 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Metadata } from 'next';
import 'styles/globals.css';
import outfit from 'styles/fonts';
import ThemeProvider from '@providers/ThemeProvider';
import NextAuthProvider from '@providers/NextAuthProvider';
import { AuthProvider } from '@providers/AuthProvider';

export const metadata: Metadata = {
title: 'yappi',
Expand All @@ -13,11 +15,15 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<html lang="en" suppressHydrationWarning>
<body className={`${outfit.variable} font-outfit`}>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
<main>
<div>{children}</div>
</main>
</ThemeProvider>
<NextAuthProvider>
<AuthProvider>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
<main>
<div>{children}</div>
</main>
</ThemeProvider>
</AuthProvider>
</NextAuthProvider>
</body>
</html>
);
Expand Down
54 changes: 32 additions & 22 deletions app/(main)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use client';

import * as React from 'react';
import useSWRClient from '@hooks/useSWRClient';
import { useRouter } from 'next/navigation';
import Loading from '@components/Loading/Loading';
import Error from '@components/Error/Error';
import { useAuth } from '@providers/AuthProvider';
import { User } from '@/types/UserType';
import { Alert, AlertDescription, AlertTitle } from '@components/ui/Alert/Alert';
import { Megaphone } from 'lucide-react';
import WorkItemHappiness from '@components/Surveys/WorkItemHappiness';
Expand All @@ -14,31 +13,42 @@ import Feedback from '@components/Surveys/Feedback';
import OverallHappinessWeather from '@components/Surveys/OverallHappinessWeather';

const Home: React.FC = () => {
const { userId } = useAuth();
const { data: user, isLoading, error } = useSWRClient<User>(`/v1/user/${userId}`);
const { user, isLoading, error } = useAuth();
const router = useRouter();

React.useEffect(() => {
if (!isLoading && user && !user.hasTeam) {
router.push('/onboarding');
}
}, [isLoading, user, router]);

if (isLoading) return <Loading />;
if (error) return <Error errorMessage="It seems there was a problem loading your account." action="/" showContact />;

return (
<div className="space-y-4">
<div className="flex w-full">
<h1>{user ? `Welcome, ${user?.name}` : 'Welcome'}</h1>
</div>
<Alert variant="informative">
<Megaphone className="h-4 w-4" />
<AlertTitle>We think you forgot something?</AlertTitle>
<AlertDescription>You have not tracked your Happines since 2 days.</AlertDescription>
</Alert>

<div className="grid grid-cols-2 gap-10">
<OverallHappiness />
<OverallHappinessWeather />
</div>
<div className="grid grid-cols-2 gap-10">
<WorkItemHappiness />
<Feedback />
</div>
<div>
{user && user.hasTeam ? (
<div className="space-y-4">
<div className="flex w-full">
<h1>{user ? `Welcome, ${user?.name}` : 'Welcome'}</h1>
</div>
<Alert variant="informative">
<Megaphone className="h-4 w-4" />
<AlertTitle>We think you forgot something?</AlertTitle>
<AlertDescription>You have not tracked your Happiness since 2 days.</AlertDescription>
</Alert>
<div className="grid grid-cols-2 gap-10">
<OverallHappiness />
<OverallHappinessWeather />
</div>
<div className="grid grid-cols-2 gap-10">
<WorkItemHappiness />
<Feedback />
</div>
</div>
) : (
<Loading />
)}
</div>
);
};
Expand Down
30 changes: 30 additions & 0 deletions app/(onboarding)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import type { Metadata } from 'next';
import 'styles/globals.css';
import outfit from 'styles/fonts';
import ThemeProvider from '@providers/ThemeProvider';
import NextAuthProvider from '@providers/NextAuthProvider';
import { AuthProvider } from '@providers/AuthProvider';

export const metadata: Metadata = {
title: 'yappi',
description: 'Your personal productivity assistant',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${outfit.variable} font-outfit`}>
<NextAuthProvider>
<AuthProvider>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
<main>
<div>{children}</div>
</main>
</ThemeProvider>
</AuthProvider>
</NextAuthProvider>
</body>
</html>
);
}
130 changes: 130 additions & 0 deletions app/(onboarding)/onboarding/components/CreateTeamDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use client';

import * as React from 'react';
import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@components/ui/Dialog/Dialog';
import { Button } from '@components/ui/Buttons/Button';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useForm, SubmitHandler } from 'react-hook-form';
import Input from '@components/ui/Input/Input';
import { useAuth } from '@providers/AuthProvider';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@components/ui/Form/Form';
import useTeamClient from '@hooks/useTeamClient';
import { BadgeCheck, Loader2, Clipboard, Check } from 'lucide-react';
import { Team } from '@/types/TeamType';

const FormSchema = z.object({
name: z.string().nonempty('Team name is required'),
});

type FormValues = z.infer<typeof FormSchema>;

const CreateTeamDialog: React.FC = () => {
const { userId } = useAuth();
const { createTeam } = useTeamClient();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [copied, setCopied] = React.useState<boolean>(false);
const [createdTeam, setCreatedTeam] = React.useState<Team | null>(null);
const inputRef = React.useRef<HTMLInputElement>(null);

const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
defaultValues: {
name: '',
},
mode: 'onSubmit',
});

const handleSubmit: SubmitHandler<FormValues> = async (data) => {
try {
setIsLoading(true);
await createTeam({ name: data.name, userId }).then((response) => {
setCreatedTeam(response.data);
});
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};

const copyToClipboard = () => {
const inputValue = inputRef.current?.value;
if (inputValue) {
navigator.clipboard.writeText(inputValue).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
}
};

return (
<>
<DialogHeader>
<DialogTitle>
{createdTeam ? (
<div className="flex items-center">
<BadgeCheck className="mr-2 h-5 w-5" />
Woho!
</div>
) : (
'Create a team'
)}
</DialogTitle>
<DialogDescription>
{createdTeam ? (
<span className="max-w-md">
Your yappi team was created successfully. Start your happiness journey and invite new team members with
the code below:
</span>
) : (
'Give your team a name and get started.'
)}
</DialogDescription>
</DialogHeader>
{!createdTeam ? (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Team name</FormLabel>
<FormControl>
<Input className="resize-none text-sm font-light" placeholder="Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
{!isLoading ? (
<Button type="submit">Create Team</Button>
) : (
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button>
)}
</DialogFooter>
</form>
</Form>
) : (
<>
<div className="flex w-full items-center space-x-2">
<Button variant="outline" onClick={() => copyToClipboard()}>
{copied ? <Check className="h-4 w-4" /> : <Clipboard className="h-4 w-4" />}
</Button>
<Input ref={inputRef} disabled value={createdTeam?.code} />
</div>
<DialogFooter>
<Button className="mt-8 px-8">Continue</Button>
</DialogFooter>
</>
)}
</>
);
};

export default CreateTeamDialog;
114 changes: 114 additions & 0 deletions app/(onboarding)/onboarding/components/JoinTeamDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use client';

import * as React from 'react';
import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@components/ui/Dialog/Dialog';
import { Button } from '@components/ui/Buttons/Button';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useForm, SubmitHandler } from 'react-hook-form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@components/ui/Form/Form';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@components/ui/InputOTP/InputOTP';
import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
import { useAuth } from '@providers/AuthProvider';
import useTeamClient from '@hooks/useTeamClient';
import { BadgeCheck, Loader2 } from 'lucide-react';
import { Team } from '@/types/TeamType';

const FormSchema = z.object({
code: z.string().min(4, {
message: 'Code must be 4 digits',
}),
});

type FormValues = z.infer<typeof FormSchema>;

const JoinTeamDialog: React.FC = () => {
const { userId } = useAuth();
const { joinTeam } = useTeamClient();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [joinedTeam, setJoinedTeam] = React.useState<Team | null>(null);

const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
defaultValues: {
code: '',
},
mode: 'onSubmit',
});

const handleSubmit: SubmitHandler<FormValues> = async (data) => {
try {
setIsLoading(true);
await joinTeam({ code: data.code, userId }).then((response) => {
setJoinedTeam(response.data);
});
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};

return (
<>
<DialogHeader>
<DialogTitle>
{joinedTeam ? (
<div className="flex items-center">
<BadgeCheck className="mr-2 h-5 w-5" />
Woho!
</div>
) : (
'Join a team'
)}
</DialogTitle>
<DialogDescription>
{joinedTeam ? (
<span className="max-w-md">
You have successfully joined the team{' '}
<span className="font-bold text-black underline">{joinedTeam.name}</span>.
</span>
) : (
'Fill the four-digit code, this code should be provided to you by a admin.'
)}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-8">
<FormField
control={form.control}
name="code"
render={({ field }) => (
<FormItem>
<FormLabel>Team code</FormLabel>
<FormControl>
<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS_AND_CHARS} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
{!isLoading ? (
<Button type="submit">Join Team</Button>
) : (
<Button disabled>
<Loader2 className="animate-sping mr-2 h-4 w-4" />
Please wait
</Button>
)}
</DialogFooter>
</form>
</Form>
</>
);
};

export default JoinTeamDialog;
Loading

0 comments on commit b9d4621

Please sign in to comment.