Skip to content

feat: 모바일 Footer 개발 #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 221 additions & 0 deletions apps/pyconkr/src/components/layout/Footer/Mobile/MobileFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import styled from "@emotion/styled";
import * as Common from "@frontend/common";
import { Article, Email, Facebook, GitHub, Instagram, LinkedIn, X, YouTube } from "@mui/icons-material";
import * as React from "react";

import FlickrIcon from "@apps/pyconkr/assets/thirdparty/flickr.svg?react";

import { useAppContext } from "../../../../contexts/app_context";

interface IconItem {
icon: React.FC<{ width?: number; height?: number }>;
alt: string;
href: string;
}

const defaultIcons: IconItem[] = [
{
icon: Facebook,
alt: "facebook",
href: "https://www.facebook.com/pyconkorea/",
},
{
icon: YouTube,
alt: "YouTube",
href: "https://www.youtube.com/c/PyConKRtube",
},
{ icon: X, alt: "X", href: "https://x.com/PyConKR" },
{ icon: GitHub, alt: "github", href: "https://github.com/pythonkr" },
{
icon: Instagram,
alt: "Instagram",
href: "https://www.instagram.com/pycon_korea/",
},
{
icon: LinkedIn,
alt: "LinkedIn",
href: "https://www.linkedin.com/company/pyconkorea/",
},
{ icon: Article, alt: "blog", href: "https://blog.pycon.kr/" },
{
icon: FlickrIcon,
alt: "Flickr",
href: "https://www.flickr.com/photos/126829363@N08/",
},
];

const Bar: React.FC = () => <div style={{ display: "inline-block", padding: "0 0.25rem" }}>|</div>;

export default function MobileFooter() {
const { sendEmail } = Common.Hooks.Common.useEmail();
const { language } = useAppContext();

const title = language === "ko" ? "Weave with Python, 파이콘 한국 2025" : "Weave with Python, Pycon KR 2025";
const committeeTitle =
language === "ko"
? "파이콘 한국 2025는 파이콘 한국 준비위원회가 만들고 있습니다"
: "PyCon Korea 2025 is organized by the PyCon Korea Organizing Committee";
const djangoTitle = language === "ko" ? "파이썬 웹 프레임워크 Django로 만들었습니다" : "Built with the Django web framework for Python";

const links = [
{
text: language === "ko" ? "파이콘 한국 행동 강령(CoC)" : "PyCon Korea Code of Conduct",
href: "https://pythonkr.github.io/pycon-code-of-conduct/ko/coc/a_intent_and_purpose.html",
},
{
text: language === "ko" ? "서비스 이용 약관" : "Terms of Service",
href: "/about/terms-of-service",
},
{
text: language === "ko" ? "개인 정보 처리 방침" : "Privacy Policy",
href: "/about/privacy-policy",
},
];

return (
<FooterContainer>
<FooterContent>
<FooterSlogan>
<br />
<FooterBoldText children={title} />
<br />
<FooterNormalText children={committeeTitle} />
<br />
<FooterNormalText children={djangoTitle} />
<br />
</FooterSlogan>
<FooterLinks>
{links.map((link, index) => (
<FooterLinkSlogan key={index}>
<Link key={link.text} href={link.href}>
{link.text}
</Link>
{index < links.length - 1 && <Separator>|</Separator>}
</FooterLinkSlogan>
))}
</FooterLinks>
<FooterIcons>
<IconLink onClick={sendEmail} aria-label="이메일 보내기">
<Email width={20} height={20} aria-hidden="true" />
</IconLink>
{defaultIcons.map((icon) => (
<IconLink key={icon.alt} href={icon.href} target="_blank" rel="noopener noreferrer" aria-label={`${icon.alt}로 이동`}>
<icon.icon width={20} height={20} aria-hidden="true" />
</IconLink>
))}
</FooterIcons>
</FooterContent>
</FooterContainer>
);
}

const FooterContainer = styled.footer`
background: linear-gradient(to bottom, #ffffff 0%, #e4fdff 25%, #92c9cc 50%, #5cadb3 75%, #095a5f 100%);
color: ${({ theme }) => theme.palette.common.white};
font-size: 0.75rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
max-height: 16rem;
padding: 5rem 0 1rem 0;
`;

const FooterContent = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
`;

const FooterText = styled.div`
padding: 0 2rem;
margin: 0.1rem;
font-size: 9pt;
a > button {
margin-left: 0.25rem;
padding: 0.05rem 0.25rem;
font-size: 8pt;
color: ${({ theme }) => theme.palette.common.white};
border-color: ${({ theme }) => theme.palette.common.white};
gap: 0.25rem;
& span {
margin-left: -2px;
margin-right: 0;
& svg {
font-size: 12pt !important;
}
}
}
strong {
font-size: 12pt;
}
`;

const FooterBoldText = styled.text`
font-weight: 600;
`;

const FooterNormalText = styled.text`
font-weight: 400;
`;

const FooterSlogan = styled.div`
text-align: center;
`;

const FooterLinkSlogan = styled.div`
display: flex;
gap: 0.3rem;
`;

const FooterLinks = styled.div`
display: flex;
align-items: center;
gap: 0.3rem;
`;

const FooterIcons = styled.div`
display: flex;
align-items: center;
gap: 9px;
`;

const Link = styled.a`
color: ${({ theme }) => theme.palette.common.white};
text-decoration: none;
&:hover {
text-decoration: underline;
}
`;

const Separator = styled.span`
color: ${({ theme }) => theme.palette.common.white};
opacity: 0.5;
margin: 0.05rem 0;
`;

const IconLink = styled.a`
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:hover {
opacity: 0.8;
}
img {
width: 20px;
height: 20px;
}
`;
111 changes: 61 additions & 50 deletions apps/pyconkr/src/components/layout/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import styled from "@emotion/styled";
import * as Common from "@frontend/common";
import { Article, Email, Facebook, GitHub, Instagram, LinkedIn, OpenInNew, X, YouTube } from "@mui/icons-material";
import { Button } from "@mui/material";
import { Button, useMediaQuery, useTheme } from "@mui/material";
import * as React from "react";

import FlickrIcon from "@apps/pyconkr/assets/thirdparty/flickr.svg?react";

import { useAppContext } from "../../../contexts/app_context";
import MobileFooter from "./Mobile/MobileFooter";

interface IconItem {
icon: React.FC<{ width?: number; height?: number }>;
Expand Down Expand Up @@ -49,6 +50,9 @@ const Bar: React.FC = () => <div style={{ display: "inline-block", padding: "0 0

export default function Footer() {
const { sendEmail } = Common.Hooks.Common.useEmail();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));

const { language } = useAppContext();

const corpPasamoStr = language === "ko" ? "사단법인 파이썬사용자모임" : "Python Korea";
Expand Down Expand Up @@ -83,56 +87,62 @@ export default function Footer() {
},
];

return (
<FooterContainer>
<FooterContent>
<FooterText>
<strong>{corpPasamoStr}</strong>
<br />
{corpAddressStr}
<Bar />
{corpRepresentatorStr}
<Bar />
{corpPhoneStr}
<Bar />
{corpCompanyNumberStr}
<a href="http://www.ftc.go.kr/bizCommPop.do?wrkr_no=3388200046" target="_blank" rel="noreferrer">
<Button variant="outlined" startIcon={<OpenInNew sx={{ fontSize: "7pt" }} />}>
{corpCheckBtnStr}
</Button>
</a>
<br />
{corpMailOrderSalesRegistrationNumberStr}
<Bar />
{hostingProviderStr}
<Bar />
{contractEmailStr}
<a href="mailto:[email protected]">[email protected]</a>
</FooterText>
<FooterLinks>
{links.map((link, index) => (
<React.Fragment key={index}>
<Link key={link.text} href={link.href}>
{link.text}
</Link>
{index < links.length - 1 && <Separator>|</Separator>}
</React.Fragment>
))}
</FooterLinks>
<FooterIcons>
<IconLink onClick={sendEmail} aria-label="이메일 보내기">
<Email width={20} height={20} aria-hidden="true" />
</IconLink>
{defaultIcons.map((icon) => (
<IconLink key={icon.alt} href={icon.href} target="_blank" rel="noopener noreferrer" aria-label={`${icon.alt}로 이동`}>
<icon.icon width={20} height={20} aria-hidden="true" />
console.log("isMobile " + isMobile);

if (isMobile) {
return <MobileFooter />;
} else {
return (
<FooterContainer>
<FooterContent>
<FooterText>
<strong>{corpPasamoStr}</strong>
<br />
{corpAddressStr}
<Bar />
{corpRepresentatorStr}
<Bar />
{corpPhoneStr}
<Bar />
{corpCompanyNumberStr}
<a href="http://www.ftc.go.kr/bizCommPop.do?wrkr_no=3388200046" target="_blank" rel="noreferrer">
<Button variant="outlined" startIcon={<OpenInNew sx={{ fontSize: "7pt" }} />}>
{corpCheckBtnStr}
</Button>
</a>
<br />
{corpMailOrderSalesRegistrationNumberStr}
<Bar />
{hostingProviderStr}
<Bar />
{contractEmailStr}
<a href="mailto:[email protected]">[email protected]</a>
</FooterText>
<FooterLinks>
{links.map((link, index) => (
<React.Fragment key={index}>
<Link key={link.text} href={link.href}>
{link.text}
</Link>
{index < links.length - 1 && <Separator>|</Separator>}
</React.Fragment>
))}
</FooterLinks>
<FooterIcons>
<IconLink onClick={sendEmail} aria-label="이메일 보내기">
<Email width={20} height={20} aria-hidden="true" />
</IconLink>
))}
</FooterIcons>
<FooterSlogan>{copyrightStr}</FooterSlogan>
</FooterContent>
</FooterContainer>
);
{defaultIcons.map((icon) => (
<IconLink key={icon.alt} href={icon.href} target="_blank" rel="noopener noreferrer" aria-label={`${icon.alt}로 이동`}>
<icon.icon width={20} height={20} aria-hidden="true" />
</IconLink>
))}
</FooterIcons>
<FooterSlogan>{copyrightStr}</FooterSlogan>
</FooterContent>
</FooterContainer>
);
}
}

const FooterContainer = styled.footer`
Expand Down Expand Up @@ -195,6 +205,7 @@ const FooterLinks = styled.div`
align-items: center;
gap: 0.625rem;
`;

const FooterIcons = styled.div`
display: flex;
align-items: center;
Expand Down