Skip to content
Snippets Groups Projects
Commit 15c44682 authored by 화균 김's avatar 화균 김
Browse files

refactor: ui structure refactoration

parent dc80f8d2
Branches
No related tags found
No related merge requests found
Showing with 276 additions and 98 deletions
......@@ -53,9 +53,10 @@ export const getSentence = cache(async (): Promise<{
throw new Error('데이터가 없습니다.');
}
const randomIndex = Math.floor(Math.random() * rows.length) % rows.length;
const [sentence, title, author, location, code] = rows[randomIndex];
let randomIndex = Math.floor(Math.random() * rows.length) % rows.length;
if (randomIndex === 0) randomIndex = 1;
const [sentence, title, author, location, code] = rows[randomIndex];
return {
result: true,
data: {sentence,title,author,location,code}
......
'use client';
import { useMemo, useCallback, useState } from "react";
import { ContentWrap } from '@/components/ContentWrap';
import { AppSection, GlobalSection } from '@/style/App.style';
// styles
import * as S from '@/style/App.style';
// hooks
import { useBookInfo } from "@/hooks/useBookInfo";
// components
import ScrollArea from "@/components/ScrollArea";
import PageTitleArea from "@/components/PageTitleArea";
import StyleElements from "@/components/StyleElements";
import LinkButton from "@/components/LinkButton";
export default function Home() {
return <GlobalSection>
<AppSection>
<ContentWrap/>
</AppSection>
</GlobalSection>;
}
const [loading, error, book_info] = useBookInfo();
const [scroll, setScroll] = useState(0);
const button_link = useMemo(() => {
return `https://library.ajou.ac.kr/#/total-search?keyword=${book_info?.code}`;
}, [book_info]);
return <S.GlobalSection>
<S.AppSection>
<S.StyledSection>
<S.TopLogo src="/ajoulib_logo_4x.png" alt="AjouLib Logo" />
<PageTitleArea scroll={scroll}/>
<ScrollArea book_info={book_info} onScroll={setScroll}/>
<LinkButton scroll={scroll} href={button_link}/>
<StyleElements scroll={scroll}/>
</S.StyledSection>
</S.AppSection>
</S.GlobalSection>;
}
\ No newline at end of file
import * as S from "./style";
import TextArea from "../TextArea";
import ScrollArea from "../ScrollArea";
import { useCallback, useEffect, useMemo, useState } from "react";
import axios from "axios";
import { BookInfo } from "@/types";
import IntroArea from "../IntroArea";
export const ContentWrap = () => {
const [book_info, setBookInfo] = useState<BookInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchSentence = async () => {
try {
const response = await axios.get('/api/sentence');
const {result, data, error} = response?.data;
if (!result) throw new Error(error);
setBookInfo(data); // 첫 번째 문장만 표시
setLoading(false);
} catch (err) {
setError('문장을 불러오는데 실패했습니다.');
setLoading(false);
console.error('Error fetching sentence:', err);
}
};
fetchSentence();
}, []);
const [scroll, setScroll] = useState(0);
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const percent = (e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight)) * 100;
setScroll(percent);
}
const date_text = useMemo(() => {
const now = new Date();
return `${now.getFullYear()}.${now.getMonth()}.${now.getDate()}.`;
}, []);
const onLinkButtonClick = useCallback(() => {
if (scroll < 50) return;
window.open(`https://library.ajou.ac.kr/#/total-search?keyword=${book_info?.code}`);
}, [book_info, scroll]);
return (
<S.StyledSection>
<S.TopLogo src="/ajoulib_logo_4x.png" alt="AjouLib Logo" />
<S.PageTitle style={{
opacity: (100-scroll)/100
}}>오늘의 한 문장</S.PageTitle>
<S.DateText style={{
opacity: (100-scroll)/100
}}>{date_text}</S.DateText>
<S.ScrollSection onScroll={handleScroll} style={{
marginTop: `-${Math.min(scroll/2, 30)}%`
}}>
<IntroArea topRate={scroll} info={book_info}/>
<TextArea topRate={scroll} sentence={book_info?.sentence || ""}/>
<ScrollArea/>
</S.ScrollSection>
<S.LinkButton style={{
opacity: scroll/100
}} onClick={onLinkButtonClick}>도서관에서 이어보기</S.LinkButton>
<S.DesignBackground style={{
transform: `translate(-50%, calc(-1*${scroll*1/10}%))`
}}/>
<S.BottomLogo src="/ajoulib_bottom_logo.png" alt="AjouLib Logo" />
<S.CharacterImg src="/ajoulib_reading_chito.png" alt="AjouLib Chito" />
</S.StyledSection>
)
}
\ No newline at end of file
import { useState, useEffect, useRef } from "react";
import { BookInfo } from "@/types";
// style
import * as S from "./style";
// types
import { BookInfo } from "@/types";
// interfaces
type IntroAreaProps = {
topRate: number,
info: BookInfo | null
}
// components
const IntroArea: React.FC<IntroAreaProps> = ({ topRate, info }) => {
if (!info) return <></>;
......
import { useCallback } from "react";
// styles
import * as S from "./style";
const LinkButton = ({scroll, href}: {scroll: number, href: string}) => {
const onLinkButtonClick = useCallback(() => {
if (scroll < 50) return;
window.open(href);
}, [href, scroll]);
return <S.LinkButton
style={{opacity: scroll/100}}
onClick={onLinkButtonClick}
>도서관에서 이어보기</S.LinkButton>
};
export default LinkButton
\ No newline at end of file
import styled from "styled-components";
export const LinkButton = styled.div`
width: 100%;
padding: 1.5em;
position: sticky;
margin-bottom: 2rem;
left: 0;
font-family: var(--font-ajou), sans-serif;
font-size: 1rem;
font-weight: 100;
color: white;
text-align: center;
background-color: #1361A7;
border-radius: 1em;
cursor: pointer;
`;
\ No newline at end of file
import { useMemo } from "react";
// styles
import * as S from "./style";
const PageTitleArea = ({ scroll }: { scroll: number }) => {
const date_text = useMemo(() => {
const now = new Date();
return `${now.getFullYear()}.${now.getMonth()}.${now.getDate()}.`;
}, []);
const text_opacity = useMemo(() => {
return (100-scroll)/100;
}, [scroll]);
return <S.PageTitleArea>
<S.PageTitle style={{opacity: text_opacity}}>오늘의 한 문장</S.PageTitle>
<S.DateText style={{opacity: text_opacity}}>{date_text}</S.DateText>
</S.PageTitleArea>
}
export default PageTitleArea;
\ No newline at end of file
import styled from "styled-components";
export const PageTitleArea = styled.section`
display: flex;
flex-direction: column;
align-items: center;
`;
export const PageTitle = styled.h1`
font-family: var(--font-ajou), sans-serif;
font-size: 2rem;
font-weight: 100;
color: #1361A7;
margin: 0;
margin-top: 1.5rem;
`;
export const DateText = styled.span`
font-family: var(--font-ajou), sans-serif;
font-size: 1rem;
font-weight: 100;
color: #1361A7;
margin-top: .5rem;
`;
\ No newline at end of file
......@@ -3,17 +3,35 @@ import { useState, useEffect, useRef } from "react";
// style
import * as S from "./style";
// types
import { BookInfo } from "@/types";
import { useScrollState } from "@/hooks/useScrollState";
// components
import IntroArea from "@/components/IntroArea";
import TextArea from "@/components/TextArea";
// interfaces
type ScrollAreaProps = {
book_info: BookInfo | null;
onScroll: (v: number) => void;
}
// components
const ScrollArea: React.FC<ScrollAreaProps> = ({ book_info, onScroll }) => {
const [scroll, handleScroll] = useScrollState();
useEffect(() => {
onScroll(scroll);
}, [scroll]);
const ScrollArea: React.FC<ScrollAreaProps> = ({ }) => {
return <S.ScrollAreaWrap>
return <S.ScrollAreaWrap
onScroll={handleScroll}
style={{marginTop: `-${Math.min(scroll/2, 30)}%`}}
>
<IntroArea topRate={scroll} info={book_info}/>
<TextArea topRate={scroll} sentence={book_info?.sentence || ""}/>
<S.FakeScrollArea/>
</S.ScrollAreaWrap>
};
......
......@@ -2,5 +2,18 @@ import styled from "styled-components";
export const ScrollAreaWrap = styled.section`
width: 100%;
flex: 1;
height: auto;
overflow-y: auto;
@media (min-height: 760px) {
margin-top: 0 !important;
}
`;
export const FakeScrollArea = styled.div`
width: 100%;
height: calc(150%);
`;
\ No newline at end of file
// styles
import * as S from "./style";
const StyleElements = ({scroll}: {scroll: number}) => {
return <>
<S.DesignBackground style={{transform: `translate(-50%, calc(-1*${scroll*1/10}%))`}}/>
<S.BottomLogo src="/ajoulib_bottom_logo.png" alt="AjouLib Logo" />
<S.CharacterImg src="/ajoulib_reading_chito.png" alt="AjouLib Chito" />
</>;
};
export default StyleElements;
\ No newline at end of file
import styled from "styled-components";
export const StyledSection = styled.section`
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
box-sizing: border-box;
position: relative;
overflow: hidden;
`;
export const DateText = styled.span`
font-family: var(--font-ajou), sans-serif;
font-size: 1rem;
font-weight: 100;
color: #1361A7;
margin-top: .5rem;
`;
export const LinkButton = styled.div`
width: 100%;
padding: 1.5em;
position: sticky;
margin-bottom: 2rem;
left: 0;
font-family: var(--font-ajou), sans-serif;
font-size: 1rem;
font-weight: 100;
color: white;
text-align: center;
background-color: #1361A7;
border-radius: 1em;
cursor: pointer;
`;
export const TopLogo = styled.img`
height: 2.5rem;
`;
export const BottomLogo = styled.img`
max-width: 100%;
height: auto;
`;
export const ScrollSection = styled.section`
width: 100%;
flex: 1;
height: auto;
overflow-y: auto;
@media (min-height: 760px) {
margin-top: 0 !important;
}
`;
export const PageTitle = styled.h1`
font-family: var(--font-ajou), sans-serif;
font-size: 2rem;
font-weight: 100;
color: #1361A7;
margin: 0;
margin-top: 1.5rem;
`;
export const DesignBackground = styled.div`
width: 1200px;
height: 1200px;
......
import { useState, useEffect, useCallback } from "react";
import axios from "axios";
import { BookInfo } from "@/types";
export const useBookInfo = (): [boolean, string | null, BookInfo | null] => {
const [book_info, setBookInfo] = useState<BookInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchSentence = useCallback(async () => {
try {
const response = await axios.get('/api/sentence');
const {result, data, error} = response?.data;
if (!result) throw new Error(error);
setBookInfo(data); // 첫 번째 문장만 표시
setLoading(false);
} catch (err) {
setError('문장을 불러오는데 실패했습니다.');
setLoading(false);
console.error('Error fetching sentence:', err);
}
}, []);
useEffect(() => {
fetchSentence();
}, []);
return [loading, error, book_info];
}
\ No newline at end of file
import { useState } from "react";
export const useScrollState = (): [number, (e: React.UIEvent<HTMLElement>) => void] => {
const [scroll, setScroll] = useState(0);
const handleScroll = (e: React.UIEvent<HTMLElement>) => {
const percent = (e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight)) * 100;
setScroll(percent);
}
return [scroll, handleScroll];
}
\ No newline at end of file
......@@ -24,4 +24,22 @@ export const AppSection = styled.section`
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
export const StyledSection = styled.section`
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
box-sizing: border-box;
position: relative;
overflow: hidden;
`;
export const TopLogo = styled.img`
height: 2.5rem;
`;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment