Skip to content

Commit

Permalink
feat: navbar imporvments fixes dockerfile
Browse files Browse the repository at this point in the history
  • Loading branch information
OnlyNico43 committed Jul 28, 2024
1 parent fa3bcca commit 660e014
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 29 deletions.
9 changes: 4 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ RUN npm ci --omit=dev --omit=optional
FROM node:22-alpine AS production

# Secrets
RUN --mount=type=secret,id=CLOUDFLARE_ORIGIN_CERTIFICATE
RUN --mount=type=secret,id=CLOUDFLARE_ORIGIN_CA_KEY
ARG CLOUDFLARE_ORIGIN_CERTIFICATE
ARG CLOUDFLARE_ORIGIN_CA_KEY

# Uninstall yarn and npm not needed in prod
RUN npm uninstall -g yarn
Expand All @@ -48,9 +48,8 @@ COPY --chown=appuser:appgroup --from=builder /app/nginx.conf /etc/nginx/nginx.co
RUN echo '{"type": "module"}' > /app/package.json

# Cloudflare origin certificate
RUN mkdir -p /etc/ssl/
RUN cat /run/secrets/CLOUDFLARE_ORIGIN_CERTIFICATE
RUN cat /run/secrets/CLOUDFLARE_ORIGIN_CA_KEY
RUN echo "$CLOUDFLARE_ORIGIN_CERTIFICATE" > /etc/ssl/easyflow.pem
RUN echo "$CLOUDFLARE_ORIGIN_CA_KEY" > /etc/ssl/easyflow.key
RUN chown -R appuser:appgroup /etc/ssl/

# add nginx
Expand Down
7 changes: 6 additions & 1 deletion public/locales/de/navbar.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
"de": "Deutsch"
},
"menuLabels": {
"about": "Über uns",
"chat": "Chat",
"signup": "Registrieren",
"profile": "Profil",
"pricing": "Preise",
"login": "Anmelden"
},
"userMenuLabels": {
"dangerZone": "Gefahrenzone",
"profile": "Profil",
"logout": "Abmelden"
}
}
10 changes: 8 additions & 2 deletions public/locales/en/navbar.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
"de": "Deutsch"
},
"menuLabels": {
"about": "About",
"chat": "Chat",
"profile": "Profile",
"pricing": "Pricing",
"login": "Login",
"pricing": "Pricing",
"profile": "Profile",
"signup": "Signup"
},
"userMenuLabels": {
"dangerZone": "Danger Zone",
"profile": "Profile",
"logout": "Logout"
}
}
17 changes: 17 additions & 0 deletions src/app/[locale]/about/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'About - EasyFlow',
description: 'Our mission and vision',
};

const RootLayout = ({
children,
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
}>): JSX.Element => {
return <main className="h-full w-screen">{children}</main>;
};

export default RootLayout;
7 changes: 7 additions & 0 deletions src/app/[locale]/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FunctionComponent, ReactElement } from 'react';

const Chat: FunctionComponent = (): ReactElement => {
return <div>About</div>;
};

export default Chat;
7 changes: 7 additions & 0 deletions src/app/[locale]/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use server';
import { APIOperation } from '@/src/services/api-services/common';
import { makeRequest } from '@/src/services/api-services/request';
import { RequestResponse } from '@/src/types/request-response.type';
import { UserResponse } from '@/src/types/response.types';
import { cookies } from 'next/headers';

export const getUser = async (): Promise<RequestResponse<UserResponse>> => {
const res = await makeRequest<APIOperation.GET_USER, UserResponse>({
Expand All @@ -16,3 +18,8 @@ export const getProfilePicture = async (): Promise<RequestResponse<string>> => {
});
return res;
};

export const serverLogout = async (): Promise<void> => {
cookies().delete('access_token');
cookies().delete('refresh_token');
};
5 changes: 3 additions & 2 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Nav from '@/src/components/nav/Nav';
import NavWrapper from '@/src/components/nav/NavBarWrapper';
import { dir } from 'i18next';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { FunctionComponent, PropsWithChildren, ReactElement } from 'react';
import '../globals.css';
import '../i18n';
import Providers from '../providers';
import NavWrapper from '@/src/components/nav/NavBarWrapper';
import { serverLogout } from './actions';

const inter = Inter({ subsets: ['latin'] });

Expand All @@ -30,7 +31,7 @@ const RootLayout: FunctionComponent<PropsWithChildren<RootLayoutProps>> = ({
<body className={'h-screen w-screen bg-background' + inter.className}>
<Providers>
<NavWrapper params={{ locale }}>
<Nav params={{ locale }} />
<Nav params={{ locale }} serverLogout={serverLogout} />
</NavWrapper>
<div className="h-[calc(100vh-64px)] overflow-y-auto">{children}</div>
</Providers>
Expand Down
1 change: 0 additions & 1 deletion src/app/[locale]/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const Home: FunctionComponent<HomeProps> = async ({ params: { locale } }) => {

// Check if user is already logged in
const res = await getUser();
console.log(res);
if (res.success) {
redirect('/chat');
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/login-form/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const LoginForm: FunctionComponent = (): ReactElement => {
setUser(res.data);
const profilePictureRes = await getProfilePicture();
if (!profilePictureRes.success) {
console.error('Failed to get profile picture');
console.log('Failed to get profile picture');
} else {
setProfilePicture(profilePictureRes.data);
}
Expand Down
82 changes: 66 additions & 16 deletions src/components/nav/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { ParamsType } from '@/src/types/params.type';
import {
Avatar,
Button,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownSection,
DropdownTrigger,
Link,
Navbar,
NavbarBrand,
Expand All @@ -13,54 +18,77 @@ import {
NavbarMenuItem,
NavbarMenuToggle,
} from '@nextui-org/react';
import { SignOut, User } from '@phosphor-icons/react';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { usePathname, useRouter } from 'next/navigation';
import { FunctionComponent, ReactElement, useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import LangSwitcher from './LangSwitcher';
import ThemeSwitcher from './ThemeSwitcher';

const Nav: FunctionComponent<ParamsType> = ({ params }): ReactElement => {
interface NavProps extends ParamsType {
serverLogout: () => Promise<void>;
}

const Nav: FunctionComponent<NavProps> = ({ params, serverLogout }): ReactElement => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { t } = useTranslation('navbar');
const { t } = useTranslation();
const pathname = usePathname();
const { user, profilePicture } = useContext(UserContext);
const { user, profilePicture, setUser, setProfilePicture } = useContext(UserContext);
const router = useRouter();

const [menuItems, setMenuItems] = useState<{ label: string; href: string; active: boolean }[]>([]);
const [menuItems, setMenuItems] = useState<{ label: string; href: string; active: boolean; hidden: boolean }[]>([]);

useEffect(() => {
setMenuItems([
{
label: t('navbar:menuLabels.chat'),
href: '/chat',
active: pathname.endsWith('chat'),
hidden: !user,
},
{
label: t('navbar:menuLabels.pricing'),
href: '/pricing',
active: pathname.endsWith('pricing'),
hidden: false,
},
{
label: t('navbar:menuLabels.about'),
href: '/about',
active: pathname.endsWith('about'),
hidden: false,
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);

const logout = async (): Promise<void> => {
await serverLogout();
setUser(undefined);
setProfilePicture(undefined);
router.push('/');
};

return (
<Navbar onMenuOpenChange={setIsMenuOpen}>
<Navbar onMenuOpenChange={setIsMenuOpen} isMenuOpen={isMenuOpen} isBordered>
<NavbarContent>
<NavbarMenuToggle aria-label={isMenuOpen ? 'Close menu' : 'Open menu'} className="sm:hidden" />
<NavbarBrand>
<Image src="/logo.svg" alt="Easyflow" width={40} height={40} />
</NavbarBrand>
</NavbarContent>

<NavbarContent className="hidden gap-4 sm:flex" justify="center">
{menuItems.map((item, index) => (
<NavbarItem key={`${item.label}-${index}`}>
<Link color={item.active ? 'primary' : 'foreground'} href={item.href}>
{item.label}
</Link>
</NavbarItem>
))}
<NavbarContent className="hidden gap-4 sm:flex" justify="start">
{menuItems
.filter(item => !item.hidden)
.map((item, index) => (
<NavbarItem key={`${item.label}-${index}`} className="px-1">
<Link color={item.active ? 'primary' : 'foreground'} href={item.href}>
{item.label}
</Link>
</NavbarItem>
))}
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem>
Expand All @@ -72,12 +100,34 @@ const Nav: FunctionComponent<ParamsType> = ({ params }): ReactElement => {
{user || profilePicture ? (
<NavbarItem>
{/* TODO: add s3 integration for profilepictures in backend and past url */}
<Avatar src="" name={user?.name} />
<Dropdown>
<DropdownTrigger>
<Avatar src={profilePicture} name={user?.name} isBordered />
</DropdownTrigger>
<DropdownMenu>
<DropdownSection showDivider>
<DropdownItem key={'profile'} startContent={<User />} href="/profile">
{t('navbar:userMenuLabels.profile')}
</DropdownItem>
</DropdownSection>
<DropdownSection title={t('navbar:userMenuLabels.dangerZone')}>
<DropdownItem
key={'logout'}
className="text-danger"
color="danger"
startContent={<SignOut />}
onClick={() => logout()}
>
{t('navbar:userMenuLabels.logout')}
</DropdownItem>
</DropdownSection>
</DropdownMenu>
</Dropdown>
</NavbarItem>
) : (
<>
<NavbarItem className="max-sm:hidden">
<Button as={Link} href="/login" variant="flat">
<Button as={Link} href="/login" color="secondary" variant="flat">
{t('navbar:menuLabels.login')}
</Button>
</NavbarItem>
Expand Down
8 changes: 7 additions & 1 deletion src/components/nav/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { FunctionComponent, ReactElement } from 'react';
const ThemeSwitcher: FunctionComponent = (): ReactElement => {
const { theme, setTheme } = useTheme();
return (
<>{theme === 'light' ? <Moon onClick={() => setTheme('dark')} /> : <Sun onClick={() => setTheme('light')} />}</>
<>
{theme === 'light' ? (
<Moon size={20} onClick={() => setTheme('dark')} />
) : (
<Sun size={20} onClick={() => setTheme('light')} />
)}
</>
);
};

Expand Down
8 changes: 8 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ const config: Config = {
plugins: [
nextui({
prefix: 'nextui', // prefix for themes variables
themes: {
light: {
colors: {},
},
dark: {
colors: {},
},
},
}),
],
};
Expand Down

0 comments on commit 660e014

Please sign in to comment.