From f74069e6c5e4bae25d5d5c2eda868e03f8798507 Mon Sep 17 00:00:00 2001
From: Diego Alzate
Date: Thu, 20 Jun 2024 09:09:46 +0100
Subject: [PATCH 01/15] initial account hub (#598)
* remove redundant type in voting utils
* make input validation cleaner
* extract account form
* make account form title dynamic
* fix build issues
* create tab manager
* add fallback to tab manager
* fix typo and add lucid react
* add new query apis and show list in account hub
---
package.json | 6 +-
packages/api/src/fetchUserOptions.ts | 24 ++
packages/api/src/index.ts | 1 +
packages/api/src/types/CycleType.ts | 1 +
packages/api/src/types/RegistrationType.ts | 12 +-
packages/api/src/types/UserOptionType.ts | 21 ++
.../{form => form-input}/FormInput.tsx | 16 +-
.../FormNumberInput.tsx} | 28 +-
.../FormSelectInput.tsx} | 2 +-
.../FormTextAreaInput.tsx} | 30 +--
.../FormTextInput.tsx} | 33 +--
.../berlin/src/components/form-input/index.ts | 5 +
.../src/components/form/AccountForm.tsx | 130 +++++++++
.../ResearchGroupForm.tsx | 8 +-
packages/berlin/src/components/form/index.ts | 7 +-
.../src/components/header/Header.styled.tsx | 2 +-
.../berlin/src/components/header/Header.tsx | 94 +------
.../components/research-group-form/index.ts | 1 -
.../src/components/tab-manager/TabManager.tsx | 13 +
.../src/components/tab-manager/index.ts | 1 +
packages/berlin/src/pages/Account.tsx | 248 ++++++++----------
packages/berlin/src/pages/Register.tsx | 2 +-
.../src/pages/SecretGroupRegistration.tsx | 2 +-
packages/berlin/src/utils/validation.ts | 12 +
packages/berlin/src/utils/voting.ts | 15 +-
pnpm-lock.yaml | 12 +
26 files changed, 408 insertions(+), 318 deletions(-)
create mode 100644 packages/api/src/fetchUserOptions.ts
create mode 100644 packages/api/src/types/UserOptionType.ts
rename packages/berlin/src/components/{form => form-input}/FormInput.tsx (77%)
rename packages/berlin/src/components/{form/NumberInput.tsx => form-input/FormNumberInput.tsx} (57%)
rename packages/berlin/src/components/{form/SelectInput.tsx => form-input/FormSelectInput.tsx} (94%)
rename packages/berlin/src/components/{form/TextAreaInput.tsx => form-input/FormTextAreaInput.tsx} (52%)
rename packages/berlin/src/components/{form/TextInput.tsx => form-input/FormTextInput.tsx} (50%)
create mode 100644 packages/berlin/src/components/form-input/index.ts
create mode 100644 packages/berlin/src/components/form/AccountForm.tsx
rename packages/berlin/src/components/{research-group-form => form}/ResearchGroupForm.tsx (92%)
delete mode 100644 packages/berlin/src/components/research-group-form/index.ts
create mode 100644 packages/berlin/src/components/tab-manager/TabManager.tsx
create mode 100644 packages/berlin/src/components/tab-manager/index.ts
create mode 100644 packages/berlin/src/utils/validation.ts
diff --git a/package.json b/package.json
index bb190c8b..460ea736 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,6 @@
"version": "1.0.0",
"type": "module",
"scripts": {
- "core:dev": "pnpm -r --filter core dev",
- "core:build": "pnpm -r --filter core build",
- "core:preview": "pnpm -r --filter core preview",
"berlin:dev": "pnpm -r --filter berlin dev",
"berlin:build": "pnpm -r --filter berlin build",
"berlin:preview": "pnpm -r --filter berlin preview",
@@ -29,6 +26,7 @@
"@radix-ui/react-dialog": "^1.0.5",
"@tanstack/react-query": "^5.13.4",
"@tanstack/react-query-devtools": "^5.13.5",
+ "lucide-react": "^0.395.0",
"react": "^18.2.0",
"react-content-loader": "^7.0.0",
"react-dom": "^18.2.0",
@@ -57,4 +55,4 @@
"vite": "^5.0.0",
"vite-plugin-node-polyfills": "^0.17.0"
}
-}
+}
\ No newline at end of file
diff --git a/packages/api/src/fetchUserOptions.ts b/packages/api/src/fetchUserOptions.ts
new file mode 100644
index 00000000..eff058d9
--- /dev/null
+++ b/packages/api/src/fetchUserOptions.ts
@@ -0,0 +1,24 @@
+import { GetUserOptionsResponse } from './types/UserOptionType';
+
+async function fetchUserOptions(userId: string): Promise {
+ try {
+ const response = await fetch(`${import.meta.env.VITE_SERVER_URL}/api/users/${userId}/options`, {
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP Error! Status: ${response.status}`);
+ }
+
+ const userOptions = (await response.json()) as { data: GetUserOptionsResponse };
+ return userOptions.data;
+ } catch (error) {
+ console.error('Error fetching user options:', error);
+ return null;
+ }
+}
+
+export default fetchUserOptions;
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index 5fd17c38..5f3de8b5 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -37,4 +37,5 @@ export { default as putRegistration } from './putRegistration';
export { default as putUser } from './putUser';
export { default as putUsersToGroups } from './putUsersToGroups';
export { default as fetchEventGroupCategories } from './fetchEventGroupCategories';
+export { default as fetchUserOptions } from './fetchUserOptions';
export * from './types';
diff --git a/packages/api/src/types/CycleType.ts b/packages/api/src/types/CycleType.ts
index 66977300..e920862e 100644
--- a/packages/api/src/types/CycleType.ts
+++ b/packages/api/src/types/CycleType.ts
@@ -1,5 +1,6 @@
export type GetCycleResponse = {
id: string;
+ eventId: string;
createdAt: string;
updatedAt: string;
status: 'OPEN' | 'CLOSED' | 'UPCOMING' | null;
diff --git a/packages/api/src/types/RegistrationType.ts b/packages/api/src/types/RegistrationType.ts
index da9e0855..768e7645 100644
--- a/packages/api/src/types/RegistrationType.ts
+++ b/packages/api/src/types/RegistrationType.ts
@@ -1,4 +1,4 @@
-export type RegistrationStatus = 'DRAFT' | 'APPROVED' | 'PUBLISHED' | null;
+export type RegistrationStatus = 'DRAFT' | 'APPROVED' | 'REJECTED' | null;
export type GetRegistrationResponseType = {
id?: string | undefined;
@@ -8,6 +8,16 @@ export type GetRegistrationResponseType = {
eventId?: string | undefined;
createdAt: string;
updatedAt: string;
+ event?: {
+ id: string;
+ name: string;
+ imageUrl: string;
+ link: string | null;
+ registrationDescription: string | null;
+ createdAt: string;
+ updatedAt: string;
+ description: string | null;
+ };
};
export type GetRegistrationsResponseType = GetRegistrationResponseType[];
diff --git a/packages/api/src/types/UserOptionType.ts b/packages/api/src/types/UserOptionType.ts
new file mode 100644
index 00000000..fb0697b4
--- /dev/null
+++ b/packages/api/src/types/UserOptionType.ts
@@ -0,0 +1,21 @@
+export type GetUserOptionsResponse = {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ userId: string | null;
+ registrationId: string | null;
+ questionId: string;
+ optionTitle: string;
+ optionSubTitle: string | null;
+ accepted: boolean | null;
+ voteScore: string;
+ fundingRequest: string | null;
+ forumQuestion: {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ description: string | null;
+ cycleId: string;
+ title: string;
+ };
+}[];
diff --git a/packages/berlin/src/components/form/FormInput.tsx b/packages/berlin/src/components/form-input/FormInput.tsx
similarity index 77%
rename from packages/berlin/src/components/form/FormInput.tsx
rename to packages/berlin/src/components/form-input/FormInput.tsx
index 2f5bc69f..706e87f3 100644
--- a/packages/berlin/src/components/form/FormInput.tsx
+++ b/packages/berlin/src/components/form-input/FormInput.tsx
@@ -1,8 +1,8 @@
import { FieldValues, Path, UseFormReturn } from 'react-hook-form';
-import { SelectInput } from './SelectInput';
-import { TextAreaInput } from './TextAreaInput';
-import { TextInput } from './TextInput';
-import { NumberInput } from './NumberInput';
+import { FormSelectInput } from './FormSelectInput';
+import { FormTextAreaInput } from './FormTextAreaInput';
+import { FormTextInput } from './FormTextInput';
+import { FormNumberInput } from './FormNumberInput';
export function FormInput(props: {
form: UseFormReturn;
@@ -15,7 +15,7 @@ export function FormInput(props: {
switch (props.type) {
case 'TEXT':
return (
- (props: {
);
case 'TEXTAREA':
return (
- (props: {
);
case 'SELECT':
return (
- (props: {
);
case 'NUMBER':
return (
- (props: {
+export function FormNumberInput(props: {
form: UseFormReturn;
name: Path;
label: string;
required: boolean | null;
customValidation?: (value: number) => string | undefined;
}) {
- const handleChange = (
- val: string,
- onSuccess: (val: string) => void,
- onFailure: (errorMsg: string) => void,
- ) => {
- if (props.customValidation) {
- const customError = props.customValidation(Number(val));
- if (customError) {
- onFailure(customError);
- return;
- }
- }
- onSuccess(val);
- };
-
return (
(
(props: {
: []
}
value={field.value ?? undefined}
- onChange={(e) =>
- handleChange(e.target.value, field.onChange, (err) => {
- props.form.setError(props.name, { message: err });
- })
- }
+ onChange={field.onChange}
+ onBlur={field.onBlur}
/>
)}
/>
diff --git a/packages/berlin/src/components/form/SelectInput.tsx b/packages/berlin/src/components/form-input/FormSelectInput.tsx
similarity index 94%
rename from packages/berlin/src/components/form/SelectInput.tsx
rename to packages/berlin/src/components/form-input/FormSelectInput.tsx
index 102beaed..a3263ac2 100644
--- a/packages/berlin/src/components/form/SelectInput.tsx
+++ b/packages/berlin/src/components/form-input/FormSelectInput.tsx
@@ -1,7 +1,7 @@
import { Controller, FieldValues, Path, UseFormReturn } from 'react-hook-form';
import Select from '../select';
-export function SelectInput(props: {
+export function FormSelectInput(props: {
form: UseFormReturn;
name: Path;
label: string;
diff --git a/packages/berlin/src/components/form/TextAreaInput.tsx b/packages/berlin/src/components/form-input/FormTextAreaInput.tsx
similarity index 52%
rename from packages/berlin/src/components/form/TextAreaInput.tsx
rename to packages/berlin/src/components/form-input/FormTextAreaInput.tsx
index 13d550d3..6d02feb7 100644
--- a/packages/berlin/src/components/form/TextAreaInput.tsx
+++ b/packages/berlin/src/components/form-input/FormTextAreaInput.tsx
@@ -1,32 +1,21 @@
import { Controller, FieldValues, Path, UseFormReturn } from 'react-hook-form';
import Textarea from '../textarea';
-export function TextAreaInput(props: {
+export function FormTextAreaInput(props: {
form: UseFormReturn;
name: Path;
label: string;
required: boolean | null;
- customValidation?: (value: number) => string | undefined;
+ customValidation?: (value: string) => string | undefined;
}) {
- const handleChange = (
- val: string,
- onSuccess: (val: string) => void,
- onFailure: (errorMsg: string) => void,
- ) => {
- if (props.customValidation) {
- const customError = props.customValidation(Number(val));
- if (customError) {
- onFailure(customError);
- return;
- }
- }
- onSuccess(val);
- };
-
return (
(
- Our first convening is May 28 in Berlin, where the community is tasked to
+ Our first convening was May 28 in Berlin, where the community was tasked to
allocate 100,000 ARB in research grants.
diff --git a/packages/berlin/src/pages/Onboarding.tsx b/packages/berlin/src/pages/Onboarding.tsx
index 48e83d52..beb4f67e 100644
--- a/packages/berlin/src/pages/Onboarding.tsx
+++ b/packages/berlin/src/pages/Onboarding.tsx
@@ -56,7 +56,7 @@ function Onboarding() {
{data[step].title}
-
+ setStep(i)} />
);
};
@@ -479,7 +524,6 @@ const filterRegistrationFields = (
function RegisterForm(props: {
user: GetUserResponse | null | undefined;
- userIsApproved: boolean;
usersToGroups: GetUsersToGroupsResponse | null | undefined;
registrationFields: GetRegistrationFieldsResponse | null | undefined;
registrationId: string | null | undefined;
@@ -493,12 +537,12 @@ function RegisterForm(props: {
}) {
const navigate = useNavigate();
const queryClient = useQueryClient();
- const [selectedGroupId, setSelectedGroupId] = useState('');
- const prevSelectGroupId = props.groupId ?? 'none';
// i want to differentiate between when a group is selected and it is not
// so i can show the correct registration fields
// i will use the selectedGroupId to do this
+ const [selectedGroupId, setSelectedGroupId] = useState('');
+ const prevSelectGroupId = props.groupId ?? 'none';
const form = useForm({
defaultValues: useMemo(
@@ -530,10 +574,12 @@ function RegisterForm(props: {
return sortedFields;
}, [props.registrationFields, selectedGroupId, prevSelectGroupId]);
- const redirectToHoldingPage = (isApproved: boolean) => {
+ const redirectToNextPage = (isApproved: boolean) => {
if (!isApproved) {
navigate(`/events/${props.event?.id}/holding`);
}
+
+ navigate(`/events/${props.event?.id}/cycles`);
};
const { mutate: mutateRegistrationData, isPending } = useMutation({
@@ -548,9 +594,15 @@ function RegisterForm(props: {
queryKey: ['registrations', body.id, 'registration-data'],
});
+ // invalidate user registrations, this is for the 1 event use case
+ // where the authentication is because you are approved to the event
+ await queryClient.invalidateQueries({
+ queryKey: [props.user?.id, 'registrations'],
+ });
+
props.onRegistrationFormCreate?.(body.id);
- redirectToHoldingPage(props.userIsApproved);
+ redirectToNextPage(body.status === 'APPROVED');
} else {
toast.error('Failed to save registration, please try again');
}
@@ -574,7 +626,7 @@ function RegisterForm(props: {
queryKey: ['registrations', props.registrationId, 'registration-data'],
});
- redirectToHoldingPage(props.userIsApproved);
+ redirectToNextPage(body.status === 'APPROVED');
} else {
toast.error('Failed to update registration, please try again');
}
@@ -682,7 +734,7 @@ function RegisterForm(props: {
type={regField.type.toLocaleUpperCase()}
options={regField.registrationFieldOptions?.map((option) => ({
name: option.value,
- value: option.id,
+ value: option.value,
}))}
/>
))}
diff --git a/packages/berlin/src/pages/Results.tsx b/packages/berlin/src/pages/Results.tsx
index 7b15b4f5..3faa5245 100644
--- a/packages/berlin/src/pages/Results.tsx
+++ b/packages/berlin/src/pages/Results.tsx
@@ -80,6 +80,8 @@ function Results() {
key={option.id}
$expanded={expandedIndex === index}
option={option}
+ cycleId={cycleId}
+ eventId={eventId}
onClick={() => setExpandedIndex(expandedIndex === index ? null : index)}
/>
))}
From 48f1f24711c04f0824799c0edfb0bbce9b9bee14 Mon Sep 17 00:00:00 2001
From: Camilo Vega <59750365+camilovegag@users.noreply.github.com>
Date: Fri, 19 Jul 2024 04:57:34 -0500
Subject: [PATCH 04/15] 669 new header (#670)
* Add tailwind plugin
* Install prettier tailwind plugin
* Create new header (transitional component)
* Update some css variable styles
* Add shadcn component
* Work in progress
* Current changes
* Modify component
* Fix formatting
* Add font family & fix shadcn component
* Finish new header (pending mobile)
* Comment old header at layout
* Fix formatting
* Improvements to submenu
* Match styles
* Add mobile menu
* Remove old header
* Update import
---
.prettierrc.cjs | 3 +-
package.json | 9 +-
.../src/_components/ui/navigation-menu.tsx | 117 ++++++++
.../src/components/header/Header.styled.tsx | 170 -----------
.../berlin/src/components/header/Header.tsx | 276 +++++++++++-------
packages/berlin/src/components/icon/Icon.tsx | 7 +-
.../components/theme-toggler/ThemeToggler.tsx | 17 ++
.../src/components/theme-toggler/index.ts | 1 +
.../zupass-button/ZupassLoginButton.tsx | 7 +-
packages/berlin/src/global.styled.tsx | 2 +
packages/berlin/src/index.css | 8 +-
packages/berlin/src/layout/Layout.tsx | 2 +-
packages/berlin/tailwind.config.js | 3 +
packages/ui/src/components/ui/command.tsx | 14 +-
packages/ui/src/components/ui/dialog.tsx | 8 +-
packages/ui/src/components/ui/popover.tsx | 2 +-
pnpm-lock.yaml | 59 ++++
17 files changed, 403 insertions(+), 302 deletions(-)
create mode 100644 packages/berlin/src/_components/ui/navigation-menu.tsx
delete mode 100644 packages/berlin/src/components/header/Header.styled.tsx
create mode 100644 packages/berlin/src/components/theme-toggler/ThemeToggler.tsx
create mode 100644 packages/berlin/src/components/theme-toggler/index.ts
diff --git a/.prettierrc.cjs b/.prettierrc.cjs
index c920e99b..7b8b2d7f 100644
--- a/.prettierrc.cjs
+++ b/.prettierrc.cjs
@@ -3,4 +3,5 @@ module.exports = {
singleQuote: true,
quoteProps: 'consistent',
printWidth: 100,
-};
\ No newline at end of file
+ plugins: ['prettier-plugin-tailwindcss'],
+};
diff --git a/package.json b/package.json
index fede6c6a..98cb06f6 100644
--- a/package.json
+++ b/package.json
@@ -45,18 +45,19 @@
"@hookform/devtools": "^4.3.1",
"@tanstack/eslint-plugin-query": "^5.18.0",
"@types/node": "^20.12.4",
- "@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
+ "@types/react": "^18.2.37",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"@vitejs/plugin-react": "^4.2.0",
- "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
+ "eslint": "^8.56.0",
+ "prettier-plugin-tailwindcss": "^0.6.5",
"prettier": "^3.1.1",
"typescript": "^5.5.2",
- "vite": "^5.0.0",
- "vite-plugin-node-polyfills": "^0.17.0"
+ "vite-plugin-node-polyfills": "^0.17.0",
+ "vite": "^5.0.0"
}
}
diff --git a/packages/berlin/src/_components/ui/navigation-menu.tsx b/packages/berlin/src/_components/ui/navigation-menu.tsx
new file mode 100644
index 00000000..18024242
--- /dev/null
+++ b/packages/berlin/src/_components/ui/navigation-menu.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
+import { cva } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+const NavigationMenu = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+));
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
+
+const NavigationMenuList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item;
+
+const navigationMenuTriggerStyle = cva();
+// "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+
+const NavigationMenuTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+));
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
+
+const NavigationMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link;
+
+const NavigationMenuViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
+
+const NavigationMenuIndicator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
+
+export {
+ navigationMenuTriggerStyle,
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+};
diff --git a/packages/berlin/src/components/header/Header.styled.tsx b/packages/berlin/src/components/header/Header.styled.tsx
deleted file mode 100644
index 3f61b9dd..00000000
--- a/packages/berlin/src/components/header/Header.styled.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import styled, { keyframes } from 'styled-components';
-import Button from '../button/Button';
-
-export const StyledHeader = styled.header`
- align-items: center;
- display: flex;
- justify-content: center;
- min-height: 10rem;
- padding-block: 2rem;
-`;
-
-export const HeaderContainer = styled.div`
- align-items: center;
- background-color: var(--color-white);
- display: flex;
- gap: 1rem;
- justify-content: space-between;
- margin-inline: auto;
- width: min(90%, 1080px);
-`;
-
-export const LogoContainer = styled.div`
- align-items: center;
- cursor: pointer;
- display: flex;
- gap: 0.75rem;
-`;
-
-export const LogoImage = styled.img`
- height: 3.5rem;
- max-width: 3.5rem;
-
- @media (min-width: 430px) {
- height: 5rem;
- max-width: 5rem;
- }
-`;
-
-export const LogoTextContainer = styled.div`
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-`;
-
-export const LogoTitle = styled.h1`
- font-size: 1.5rem;
- line-height: 1.65rem;
-
- @media (min-width: 430px) {
- display: block;
- font-family: var(--font-family-title);
- font-size: 1.75rem;
- font-weight: 600;
- line-height: 1.75rem;
- }
-
- @media (min-width: 600px) {
- max-width: none;
- }
-`;
-
-export const LogoSubtitle = styled.h2`
- display: block;
- font-family: var(--font-family-body);
- font-size: 0.65rem;
- font-style: italic;
- font-weight: 600;
- line-height: 0.75rem;
- max-width: 360px;
- @media (min-width: 600px) {
- font-size: 1rem;
- }
-`;
-
-export const NavContainer = styled.nav`
- align-items: center;
- display: flex;
-`;
-
-export const NavButtons = styled.ul`
- display: flex;
- gap: 0.75rem;
- list-style: none;
-`;
-
-export const DesktopButtons = styled.div`
- display: none;
- @media (min-width: 1080px) {
- display: flex;
- gap: 0.75rem;
- }
-`;
-
-export const MobileButtons = styled.div`
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
- height: 100%;
- text-align: center;
- @media (min-width: 1080px) {
- display: none;
- }
-`;
-
-export const ThemeButton = styled(Button)`
- img {
- min-width: 20px;
- }
-`;
-
-export const MenuButton = styled.li`
- align-items: center;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- height: 2.25rem;
- justify-content: center;
- width: 2.25rem;
-
- @media (min-width: 1080px) {
- display: none;
- }
-`;
-
-export const Bar = styled.div<{ $isOpen: boolean }>`
- background-color: var(--color-black);
- border-radius: 8px;
- height: 3px;
- margin: 2px 0;
- transition: 0.4s;
- width: 27px;
-
- &:first-child {
- transform: ${({ $isOpen }) => ($isOpen ? 'rotate(-45deg) translateY(10px)' : '')};
- }
-
- &:nth-child(2) {
- opacity: ${({ $isOpen }) => ($isOpen ? '0' : '1')};
- transition: 0.2s;
- }
-
- &:nth-child(3) {
- transform: ${({ $isOpen }) => ($isOpen ? 'rotate(45deg) translateY(-10px)' : '')};
- }
-`;
-
-const fadeIn = keyframes`
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-`;
-
-export const BurgerMenuContainer = styled.nav<{ $$isOpen: boolean }>`
- align-items: center;
- background-color: var(--color-white);
- bottom: 0;
- display: flex;
- height: calc(100% - 160px);
- justify-content: center;
- left: 0;
- position: fixed;
- width: 100%;
- z-index: 999;
-
- display: ${(props) => (props.$$isOpen ? 'flex' : 'none')};
- animation: ${fadeIn} 0.3s ease-out;
-`;
diff --git a/packages/berlin/src/components/header/Header.tsx b/packages/berlin/src/components/header/Header.tsx
index de8a5313..3fee0c8a 100644
--- a/packages/berlin/src/components/header/Header.tsx
+++ b/packages/berlin/src/components/header/Header.tsx
@@ -1,53 +1,156 @@
-// React and third-party libraries
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useState } from 'react';
import {
- // useLocation,
- useNavigate,
-} from 'react-router-dom';
-import { SunMoon, User } from 'lucide-react';
+ NavigationMenu,
+ NavigationMenuContent,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuList,
+ NavigationMenuTrigger,
+} from '@/_components/ui/navigation-menu';
+import useUser from '@/hooks/useUser';
+import { useAppStore } from '@/store';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { fetchAlerts, fetchEvents, fetchUserRegistrations, GetUserResponse, logout } from 'api';
+import { Menu, User } from 'lucide-react';
+import { useMemo, useState } from 'react';
+import { NavLink } from 'react-router-dom';
+import Icon from '../icon';
+import ThemeToggler from '../theme-toggler';
+import { useNavigate } from 'react-router-dom';
+import ZupassLoginButton from '../zupass-button';
-// Store
-import { useAppStore } from '../../store';
+export default function NewHeader() {
+ const theme = useAppStore((state) => state.theme);
+ const { user } = useUser();
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
-// Data
-import header from '../../data/header';
+ return (
+
+ {isMenuOpen && (
+ setIsMenuOpen(!isMenuOpen)}
+ >
+
+ {user && }
+
+
+
+ )}
+
+
+
+
Lexicon
+
+
+
+ {user ? (
+ <>
+
+
+
+
+
+ setIsMenuOpen(!isMenuOpen)}>
+
+
+
+ >
+ ) : (
+ Login
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
-// API
-import { logout } from 'api';
+const HeaderLinks = ({ user }: { user: GetUserResponse }) => {
+ const { data: events } = useQuery({
+ queryKey: ['events'],
+ queryFn: fetchEvents,
+ enabled: !!user,
+ });
-// Hooks
-import useUser from '../../hooks/useUser';
+ const { data: registrationsData } = useQuery({
+ queryKey: [user?.id, 'registrations'],
+ queryFn: () => fetchUserRegistrations(user?.id ?? ''),
+ enabled: !!user,
+ });
-// Components
-import Button from '../button';
-import NavButton from '../nav-button';
-import ZupassLoginButton from '../zupass-button/ZupassLoginButton';
+ const { data: alerts } = useQuery({
+ queryKey: ['alerts'],
+ queryFn: () => fetchAlerts(),
+ enabled: !!user,
+ refetchInterval: 10000,
+ });
-// Styled components
-import {
- Bar,
- BurgerMenuContainer,
- DesktopButtons,
- HeaderContainer,
- LogoContainer,
- LogoSubtitle,
- LogoTextContainer,
- LogoTitle,
- MenuButton,
- MobileButtons,
- NavButtons,
- NavContainer,
- StyledHeader,
-} from './Header.styled';
+ const links = useMemo(() => {
+ const baseLinks = [
+ {
+ title: 'My Proposals',
+ link: events ? `/events/${events?.[0].id}/register` : '',
+ },
+ {
+ title: 'Agenda',
+ link: events ? `/events/${events?.[0].id}/cycles` : '',
+ },
+ ];
+
+ if (
+ registrationsData?.some((registration) => registration.status === 'APPROVED') &&
+ alerts &&
+ alerts.length > 0
+ ) {
+ const alertsLinks = alerts.map((alert) => ({
+ title: alert.title,
+ link: alert.link || '',
+ }));
+ return [...baseLinks, ...alertsLinks];
+ }
+
+ return baseLinks;
+ }, [events, registrationsData, alerts]);
+
+ return links.map(({ title, link }) => (
+
+
+
+ {title}
+
+
+
+ ));
+};
+
+const UserMenu = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
-function Header() {
+const UserMenuLinks = () => {
const queryClient = useQueryClient();
- const { user } = useUser();
- const theme = useAppStore((state) => state.theme);
- const toggleTheme = useAppStore((state) => state.toggleTheme);
- const navigate = useNavigate();
const resetState = useAppStore((state) => state.reset);
+ const navigate = useNavigate();
+
const { mutate: mutateLogout } = useMutation({
mutationFn: logout,
onSuccess: async () => {
@@ -57,71 +160,30 @@ function Header() {
navigate('/');
},
});
+ const links = useMemo(() => {
+ return [
+ {
+ title: 'Account',
+ link: '/account',
+ },
+ {
+ title: 'Log out',
+ onClick: () => mutateLogout(),
+ },
+ ];
+ }, [mutateLogout]);
- const [isBurgerMenuOpen, setIsBurgerMenuOpen] = useState(false);
-
- return (
-
-
- navigate('/')}>
-
-
- {header.title}
- {header.subtitle}
-
-
-
-
-
- {user ? (
- <>
-
- Events
-
- mutateLogout()}>Log out
- navigate('/account')}>
-
-
- >
- ) : (
- Login with Zupass
- )}
-
- setIsBurgerMenuOpen(!isBurgerMenuOpen)}>
-
-
-
-
-
-
-
-
-
-
-
- setIsBurgerMenuOpen(false)}>
-
-
- {user ? (
- <>
-
- Events
-
-
-
- Account
-
- mutateLogout()}>Log out
- >
- ) : (
- Login with Zupass
- )}
-
-
-
-
-
- );
-}
-
-export default Header;
+ return links.map(({ title, link, onClick }) => (
+
+
+
+ {title}
+
+
+
+ ));
+};
diff --git a/packages/berlin/src/components/icon/Icon.tsx b/packages/berlin/src/components/icon/Icon.tsx
index ba3d553c..5c21c6d1 100644
--- a/packages/berlin/src/components/icon/Icon.tsx
+++ b/packages/berlin/src/components/icon/Icon.tsx
@@ -3,10 +3,13 @@ import { StyledIcon } from './Icon.styled';
type IconProps = {
children: React.ReactNode;
+ onClick?: () => void;
};
-const Icon = forwardRef(({ children }, ref) => (
- {children}
+const Icon = forwardRef(({ children, onClick }, ref) => (
+
+ {children}
+
));
export default Icon;
diff --git a/packages/berlin/src/components/theme-toggler/ThemeToggler.tsx b/packages/berlin/src/components/theme-toggler/ThemeToggler.tsx
new file mode 100644
index 00000000..55036590
--- /dev/null
+++ b/packages/berlin/src/components/theme-toggler/ThemeToggler.tsx
@@ -0,0 +1,17 @@
+import { useEffect } from 'react';
+import { useAppStore } from '@/store';
+import Icon from '../icon';
+import { Moon, Sun } from 'lucide-react';
+
+export default function ThemeToggler() {
+ const theme = useAppStore((state) => state.theme);
+ const toggleTheme = useAppStore((state) => state.toggleTheme);
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+ root.classList.remove('light', 'dark');
+ root.classList.add(theme);
+ }, [theme]);
+
+ return {theme === 'dark' ? : };
+}
diff --git a/packages/berlin/src/components/theme-toggler/index.ts b/packages/berlin/src/components/theme-toggler/index.ts
new file mode 100644
index 00000000..a9f59a23
--- /dev/null
+++ b/packages/berlin/src/components/theme-toggler/index.ts
@@ -0,0 +1 @@
+export { default } from './ThemeToggler';
diff --git a/packages/berlin/src/components/zupass-button/ZupassLoginButton.tsx b/packages/berlin/src/components/zupass-button/ZupassLoginButton.tsx
index 642240a5..488e422d 100644
--- a/packages/berlin/src/components/zupass-button/ZupassLoginButton.tsx
+++ b/packages/berlin/src/components/zupass-button/ZupassLoginButton.tsx
@@ -80,7 +80,12 @@ function ZupassLoginButton({ children, $variant, ...props }: ZupassLoginButtonPr
return (
<>
-
+
{children}
>
diff --git a/packages/berlin/src/global.styled.tsx b/packages/berlin/src/global.styled.tsx
index dc7a8263..c9b40c50 100644
--- a/packages/berlin/src/global.styled.tsx
+++ b/packages/berlin/src/global.styled.tsx
@@ -11,6 +11,8 @@ export const GlobalStyle = createGlobalStyle`
}
:root {
+ --color-primary: '#222';
+ --color-secondary: '#fff';
--color-white: ${(props) => props.theme.backgroundColor};
--color-black: ${(props) => props.theme.textColor};
--color-gray: ${(props) => props.theme.gray};
diff --git a/packages/berlin/src/index.css b/packages/berlin/src/index.css
index 8abdb15c..c6b2a343 100644
--- a/packages/berlin/src/index.css
+++ b/packages/berlin/src/index.css
@@ -13,10 +13,10 @@
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
- --primary: 222.2 47.4% 11.2%;
+ --primary: 0 0% 100%;
--primary-foreground: 210 40% 98%;
- --secondary: 210 40% 96.1%;
+ --secondary: 0 0% 13%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
@@ -45,10 +45,10 @@
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
- --primary: 210 40% 98%;
+ --primary: 0 0% 13%;
--primary-foreground: 222.2 47.4% 11.2%;
- --secondary: 217.2 32.6% 17.5%;
+ --secondary: 0 0% 100%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
diff --git a/packages/berlin/src/layout/Layout.tsx b/packages/berlin/src/layout/Layout.tsx
index 9824e5e4..c4f86618 100644
--- a/packages/berlin/src/layout/Layout.tsx
+++ b/packages/berlin/src/layout/Layout.tsx
@@ -6,8 +6,8 @@ import { Outlet } from 'react-router-dom';
import { Main } from './Layout.styled';
// Components
-import Header from '../components/header';
import Footer from '../components/footer';
+import Header from '@/components/header';
function Layout() {
return (
diff --git a/packages/berlin/tailwind.config.js b/packages/berlin/tailwind.config.js
index fe12db56..eb76aef6 100644
--- a/packages/berlin/tailwind.config.js
+++ b/packages/berlin/tailwind.config.js
@@ -17,6 +17,9 @@ module.exports = {
},
},
extend: {
+ fontFamily: {
+ raleway: ["'Raleway'", 'sans-serif'],
+ },
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
diff --git a/packages/ui/src/components/ui/command.tsx b/packages/ui/src/components/ui/command.tsx
index 907de29d..9223ca4e 100644
--- a/packages/ui/src/components/ui/command.tsx
+++ b/packages/ui/src/components/ui/command.tsx
@@ -15,7 +15,7 @@ const Command = React.forwardRef<
{
return (
- Click to revisit the{' '}
-
- event rules
-
- ,{' '}
-
- trust assumptions
-
- , and the community’s{' '}
-
- data policy
-
- .
- No groups