Skip to content

Commit

Permalink
Merge pull request #196 from hackdays-io/feature/header
Browse files Browse the repository at this point in the history
Feature/header
  • Loading branch information
yu23ki14 authored Dec 3, 2024
2 parents 2b03c34 + 3922fd6 commit a4f3324
Show file tree
Hide file tree
Showing 16 changed files with 2,309 additions and 2,112 deletions.
452 changes: 226 additions & 226 deletions pkgs/frontend/abi/bigbang.ts

Large diffs are not rendered by default.

1,264 changes: 632 additions & 632 deletions pkgs/frontend/abi/fractiontoken.ts

Large diffs are not rendered by default.

1,844 changes: 922 additions & 922 deletions pkgs/frontend/abi/hats.ts

Large diffs are not rendered by default.

430 changes: 215 additions & 215 deletions pkgs/frontend/abi/hatsTimeFrameModule.ts

Large diffs are not rendered by default.

22 changes: 0 additions & 22 deletions pkgs/frontend/app/components/CommonDialog.tsx

This file was deleted.

96 changes: 96 additions & 0 deletions pkgs/frontend/app/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useState, useEffect } from "react";
import { Box, Text } from "@chakra-ui/react";
import { WorkspaceIcon } from "./WorkspaceIcon";
import { UserIcon } from "./UserIcon";
import { useLocation } from "@remix-run/react";

const NO_HEADER_PATHS: string[] = ["/login"]; // 適宜ヘッダーが不要なページのパスを追加
const HEADER_SIZE: number = 12; // 偶数のnumberだとアイコンが対応しているため望ましい

const headerTextStyle = {
my: "auto",
wordBreak: "break-word",
flex: "1",
};

enum HeaderType {
NonHeader = "NonHeader",
UserIconOnly = "UserIconOnly",
WorkspaceAndUserIcons = "WorkspaceAndUserIcons",
}

export const Header = () => {
const [headerType, setHeaderType] = useState<HeaderType>(
HeaderType.NonHeader
);

const { pathname } = useLocation();

// ToDo: ページのパスや hooks で柔軟にロジックを実装する(切り替えてテストできます)
const isWalletConnected = true;
const isUserTobanEnsFound = true;
const isWorkspaceSelected = true;

// ToDo: ユーザーやワークスペースごとの各種データを取得するロジックを実装する
const userImageUrl: string | undefined = undefined;
const workspaceName: string | undefined = "Workspace Name";
const workspaceImageUrl: string | undefined = undefined;

useEffect(() => {
const determineHeaderType = () => {
if (
!NO_HEADER_PATHS.includes(pathname) &&
isWalletConnected &&
isUserTobanEnsFound
) {
return isWorkspaceSelected && workspaceName
? HeaderType.WorkspaceAndUserIcons
: HeaderType.UserIconOnly;
}
return HeaderType.NonHeader;
};

setHeaderType(determineHeaderType());
}, [
pathname,
isWalletConnected,
isUserTobanEnsFound,
isWorkspaceSelected,
workspaceName,
]);

return (
<Box
height={HEADER_SIZE}
display="flex"
justifyContent="space-between"
width="100%"
alignItems="center"
mb={6}
>
{headerType !== HeaderType.NonHeader && (
<>
<Box display="flex" height={HEADER_SIZE} flex="1">
{headerType === HeaderType.UserIconOnly && (
<Text {...headerTextStyle} fontSize="xl">
Workspaces
</Text>
)}
{headerType === HeaderType.WorkspaceAndUserIcons && (
<>
<WorkspaceIcon
workspaceImageUrl={workspaceImageUrl}
size={HEADER_SIZE}
/>
<Text {...headerTextStyle} ml={4}>
{workspaceName}
</Text>
</>
)}
</Box>
<UserIcon userImageUrl={userImageUrl} size={HEADER_SIZE - 2} />
</>
)}
</Box>
);
};
21 changes: 21 additions & 0 deletions pkgs/frontend/app/components/UserIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FaCircleUser } from "react-icons/fa6";
import { CommonIcon } from "./common/CommonIcon";

interface UserIconProps {
userImageUrl: string | undefined;
size: number;
}

export const UserIcon = ({ userImageUrl, size }: UserIconProps) => {
return (
<CommonIcon
imageUrl={userImageUrl}
size={size}
fallbackIconComponent={
<FaCircleUser
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
}
/>
);
};
28 changes: 28 additions & 0 deletions pkgs/frontend/app/components/WorkspaceIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { FaPeopleGroup } from "react-icons/fa6";
import { CommonIcon } from "./common/CommonIcon";

interface WorkspaceIconProps {
workspaceImageUrl?: string;
size: number;
}

export const WorkspaceIcon = ({
workspaceImageUrl,
size,
}: WorkspaceIconProps) => {
return (
<CommonIcon
imageUrl={workspaceImageUrl}
size={size}
fallbackIconComponent={
<FaPeopleGroup
style={{
width: "90%",
height: "90%",
objectFit: "cover",
}}
/>
}
/>
);
};
22 changes: 22 additions & 0 deletions pkgs/frontend/app/components/common/CommonDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
DialogContent,
DialogRoot,
DialogTrigger,
} from "~/components/ui/dialog";

interface CommonDialogProps {
dialogTriggerReactNode?: React.ReactNode;
children?: React.ReactNode;
}

export const CommonDialog = ({
dialogTriggerReactNode,
children,
}: CommonDialogProps) => {
return (
<DialogRoot>
<DialogTrigger asChild>{dialogTriggerReactNode}</DialogTrigger>
<DialogContent>{children}</DialogContent>
</DialogRoot>
);
};
45 changes: 45 additions & 0 deletions pkgs/frontend/app/components/common/CommonIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState, useEffect, ReactNode } from "react";
import { Box, Image } from "@chakra-ui/react";

interface CommonIconProps {
imageUrl: string | undefined;
size: number;
fallbackIconComponent?: ReactNode;
}

export const CommonIcon = ({
size,
imageUrl,
fallbackIconComponent,
}: CommonIconProps) => {
const [showFallbackIcon, setShowFallbackIcon] = useState(!imageUrl);

useEffect(() => {
setShowFallbackIcon(!imageUrl);
}, [imageUrl]);

return (
<Box
height={size}
width={size}
display="flex"
alignItems="center"
justifyContent="center"
my="auto"
borderRadius="full"
flexShrink={0}
>
{!showFallbackIcon ? (
<Image
src={imageUrl}
width="100%"
height="100%"
objectFit="cover"
onError={() => setShowFallbackIcon(true)}
/>
) : (
fallbackIconComponent || null
)}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Input, InputProps } from "@chakra-ui/react";

interface CommonInputProps extends Omit<InputProps, "value"> {
value: string | number;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
value: string | number;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

export const CommonInput = ({ value, onChange }: CommonInputProps) => {
return <Input value={value} onChange={onChange} />;
return <Input value={value} onChange={onChange} />;
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Textarea, TextareaProps } from "@chakra-ui/react";

interface CommonTextAreaProps extends Omit<TextareaProps, "value"> {
value: string;
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
value: string;
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
}

export const CommonTextArea = ({ value, onChange }: CommonTextAreaProps) => {
return <Textarea value={value} onChange={onChange} />;
return <Textarea value={value} onChange={onChange} />;
};
95 changes: 51 additions & 44 deletions pkgs/frontend/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,66 @@
import { withEmotionCache } from "@emotion/react";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { ThemeProvider } from "next-themes";
// import { ThemeProvider } from "next-themes"; // DarkMode 切り替えの実装の可能性に備え、ThemeProvider を残しておいてあります
import { ChakraProvider } from "./components/chakra-provider";
import { useInjectStyles } from "./emotion/emotion-client";
import { PrivyProvider } from "@privy-io/react-auth";

import { Box, Container } from "@chakra-ui/react";
import { Header } from "./components/Header";
interface LayoutProps extends React.PropsWithChildren {}

export const Layout = withEmotionCache((props: LayoutProps, cache) => {
const { children } = props;
const { children } = props;

useInjectStyles(cache);
useInjectStyles(cache);

return (
<html lang="en">
<head suppressHydrationWarning>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<meta
name="emotion-insertion-point"
content="emotion-insertion-point"
/>
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
return (
<html lang="en">
<head suppressHydrationWarning>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<meta
name="emotion-insertion-point"
content="emotion-insertion-point"
/>
</head>
<body>
<Box bg="gray.50" minHeight="100vh" overflow="auto">
<Container bg="white" maxW="430px" minH="100vh" px={5} py={4}>
<Header />
{children}
</Container>
</Box>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
});

export default function App() {
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{
embeddedWallets: {
createOnLogin: "users-without-wallets",
},
}}
>
<ChakraProvider>
<ThemeProvider disableTransitionOnChange attribute="class">
<Outlet />
</ThemeProvider>
</ChakraProvider>
</PrivyProvider>
);
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{
embeddedWallets: {
createOnLogin: "users-without-wallets",
},
}}
>
<ChakraProvider>
{/* DarkMode 切り替えの実装の可能性に備え、ThemeProvider を残しておいてあります */}
{/* <ThemeProvider disableTransitionOnChange attribute="class"> */}
<Outlet />
{/* </ThemeProvider> */}
</ChakraProvider>
</PrivyProvider>
);
}
Loading

0 comments on commit a4f3324

Please sign in to comment.