Skip to content

Commit

Permalink
feat(web): feedback dialog default to user email
Browse files Browse the repository at this point in the history
  • Loading branch information
ixahmedxi committed Jul 31, 2023
1 parent a416d91 commit e2a2ee3
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 78 deletions.
2 changes: 1 addition & 1 deletion apps/web/src/components/ActiveLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type ActiveLinkProps = LinkProps &
const ActiveLink = forwardRef<HTMLAnchorElement, ActiveLinkProps>(
({ className, activeClassName, href, ...props }, ref) => {
const router = useRouter();
const active = router.pathname.startsWith(href);
const active = href === router.pathname;

return (
<Link
Expand Down
139 changes: 73 additions & 66 deletions apps/web/src/layouts/dashboard/feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { type FC } from 'react';
import AnimateHeight from 'react-animate-height';
import { useForm } from 'react-hook-form';
import { useUser } from '@clerk/nextjs';
import { zodResolver } from '@hookform/resolvers/zod';
import { type TRPCErrorResponse } from '@trpc/server/rpc';
import { z } from 'zod';
Expand Down Expand Up @@ -32,13 +34,13 @@ const formSchema = z.object({
message: z.string().min(3),
});

export const FeedbackDialog = () => {
const FeedbackForm: FC<{ email?: string | undefined }> = ({ email }) => {
const { mutateAsync, isLoading, isSuccess } = api.feedback.add.useMutation();

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
email: email ?? '',
message: '',
},
});
Expand All @@ -56,10 +58,75 @@ export const FeedbackDialog = () => {
}
};

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="pt-6" noValidate>
<div className="space-y-8">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
placeholder="[email protected]"
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea
className="min-h-[120px] resize-none"
placeholder="Your message"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<FormMessage className="pt-2">
{form.formState.errors.root?.message}
</FormMessage>

<AnimateHeight height={isSuccess ? 'auto' : 0}>
<Typography.P className="text-teal-10 dark:text-tealdark-10 flex items-center gap-2 pt-2">
<Icon name="check" />
Thanks for your feedback!
</Typography.P>
</AnimateHeight>

<DialogFooter className="pt-6">
<Button type="submit" variant="inverse" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Message'}
</Button>
</DialogFooter>
</form>
</Form>
);
};

export const FeedbackDialog = () => {
const { user } = useUser();

return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary" className="w-full">
<Button variant="muted" className="w-full">
<Icon name="badge-help" />
Provide feedback
</Button>
Expand All @@ -73,69 +140,9 @@ export const FeedbackDialog = () => {
</DialogDescription>
</DialogHeader>

<Form {...form}>
<form
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSubmit={form.handleSubmit(onSubmit)}
className="pt-6"
noValidate
>
<div className="space-y-8">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
placeholder="[email protected]"
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea
className="min-h-[120px] resize-none"
placeholder="Your message"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<FormMessage className="pt-2">
{form.formState.errors.root?.message}
</FormMessage>

<AnimateHeight height={isSuccess ? 'auto' : 0}>
<Typography.P className="text-teal-10 dark:text-tealdark-10 flex items-center gap-2 pt-2">
<Icon name="check" />
Thanks for your feedback!
</Typography.P>
</AnimateHeight>

<DialogFooter className="pt-6">
<Button type="submit" variant="inverse" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Message'}
</Button>
</DialogFooter>
</form>
</Form>
{user && (
<FeedbackForm email={user.primaryEmailAddress?.emailAddress} />
)}
</DialogContent>
</Dialog>
);
Expand Down
66 changes: 62 additions & 4 deletions apps/web/src/layouts/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { FC, PropsWithChildren } from 'react';
import { UserButton } from '@clerk/nextjs';
import { dark } from '@clerk/themes';
import { useRouter } from 'next/navigation';

import { Brand, Button } from '@noodle/ui';

Expand All @@ -8,8 +11,10 @@ import { FeedbackDialog } from './feedback';
import { pageLinks } from './static-data';

const DashboardLayout: FC<PropsWithChildren> = ({ children }) => {
const router = useRouter();

return (
<div className="flex min-h-screen gap-8 p-8">
<div className="flex min-h-screen gap-6 p-6">
<aside className="flex min-w-[220px] flex-col justify-between">
<div>
<div className="pl-4">
Expand All @@ -19,7 +24,7 @@ const DashboardLayout: FC<PropsWithChildren> = ({ children }) => {
<ul className="mt-8 flex flex-col gap-2">
{pageLinks.map((link) => (
<li key={link.href}>
<Button asChild variant="secondary" className="w-full">
<Button asChild variant="muted" className="w-full">
<ActiveLink
href={link.href}
activeClassName="text-gray-12 dark:text-graydark-12"
Expand All @@ -38,8 +43,61 @@ const DashboardLayout: FC<PropsWithChildren> = ({ children }) => {
</div>
</aside>

<div className="border-gray-3 dark:border-graydark-3 flex flex-1 rounded-xl border p-6">
{children}
<div className="border-gray-3 dark:border-graydark-3 flex flex-1 flex-col rounded-2xl border px-6 py-4">
<nav className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Button type="button" variant="muted" size="icon">
<Icon name="panel-left-close" />
</Button>
<div className="flex items-center">
<Button
type="button"
variant="muted"
size="icon"
onClick={() => {
router.back();
}}
>
<Icon name="chevron-left" />
</Button>
<Button
type="button"
variant="muted"
size="icon"
onClick={() => {
router.forward();
}}
>
<Icon name="chevron-right" />
</Button>
</div>
</div>

<div className="flex flex-row-reverse items-center gap-4">
<UserButton
appearance={{
baseTheme: dark,
}}
/>
<Button type="button" variant="muted" size="icon">
<Icon name="bell-ring" />
</Button>
<Button
type="button"
variant="secondary"
size="icon"
className="gap-2 px-2.5 py-1.5"
>
<Icon name="plus" size={16} />
<Icon
name="chevron-down"
size={11}
className="text-gray-10 dark:text-graydark-10"
/>
</Button>
</div>
</nav>
<div className="flex flex-1">{children}</div>
</div>
</div>
);
Expand Down
40 changes: 35 additions & 5 deletions apps/web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import '@/styles/globals.css';
import 'react';

import { Provider as WrapBalancerProvider } from 'react-wrap-balancer';
import type { FC, PropsWithChildren } from 'react';
import { ClerkProvider } from '@clerk/nextjs';
import { dark } from '@clerk/themes';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Analytics } from '@vercel/analytics/react';
import { DefaultSeo } from 'next-seo';
import { ThemeProvider } from 'next-themes';
import { ThemeProvider, useTheme } from 'next-themes';
import { Inter, JetBrains_Mono } from 'next/font/google';

import { api } from '@/utils/api';
Expand All @@ -26,21 +29,48 @@ const jetBrainsMono = JetBrains_Mono({
display: 'swap',
});

const ThemedClerkProvider: FC<PropsWithChildren> = ({ children }) => {
const { resolvedTheme } = useTheme();

if (!resolvedTheme) return null;

return (
<ClerkProvider
appearance={{
baseTheme:
resolvedTheme === 'dark' ? dark : { __type: 'prebuilt_appearance' },
variables: {
colorPrimary: '#e64d67',
},
elements: {
userButtonPopoverCard:
'border border-gray-6 dark:border-graydark-6 bg-gray-2 dark:bg-graydark-2 w-[18rem] rounded-lg',
navbar: 'border-r border-gray-6 dark:border-graydark-6',
card: 'border border-gray-6 dark:border-graydark-6 bg-gray-2 dark:bg-graydark-2 rounded-lg',
pageScrollBox: '',
},
}}
>
{children}
</ClerkProvider>
);
};

const App = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page);

return (
<div className={`${inter.variable} ${jetBrainsMono.variable} font-sans`}>
<DefaultSeo {...seo} />
<ClerkProvider {...pageProps}>
<ThemeProvider attribute="class">
<ThemeProvider attribute="class">
<ThemedClerkProvider>
<WrapBalancerProvider>
{getLayout(<Component {...pageProps} />)}
</WrapBalancerProvider>
<Analytics />
<ReactQueryDevtools position="bottom-right" initialIsOpen={false} />
</ThemeProvider>
</ClerkProvider>
</ThemedClerkProvider>
</ThemeProvider>
<style jsx global>{`
:root {
--font-inter: ${inter.variable}, sans-serif;
Expand Down
23 changes: 23 additions & 0 deletions apps/web/src/pages/app/modules.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Typography } from '@noodle/ui';

import { DashboardLayout } from '@/layouts/dashboard';
import { type NextPageWithLayout } from '@/utils/NextPageWithLayout';

const ModulesPage: NextPageWithLayout = () => {
return (
<main className="flex flex-1 items-center justify-center">
<div className="text-center">
<Typography.H1>😪</Typography.H1>
<Typography.H1>It&apos;s a bit lonely here</Typography.H1>
<Typography.P className="max-w-[50ch]">
We are hard at work getting the dashboard up and running, we are still
in very much early days of the development of Noodle.
</Typography.P>
</div>
</main>
);
};

ModulesPage.getLayout = (page) => <DashboardLayout>{page}</DashboardLayout>;

export default ModulesPage;
4 changes: 4 additions & 0 deletions apps/web/src/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import SuperJSON from 'superjson';
Expand All @@ -18,3 +19,6 @@ export const api = createTRPCNext<AppRouter>({
},
ssr: false,
});

export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
8 changes: 8 additions & 0 deletions packages/config/eslint/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ const config = {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
'eslint-comments/no-unused-disable': 'error',
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: {
attributes: false,
},
},
],
},
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
Expand Down
Loading

0 comments on commit e2a2ee3

Please sign in to comment.