Skip to content

Commit

Permalink
Merge pull request #485 from jisurk/part3-우지석-week15
Browse files Browse the repository at this point in the history
[Week15] 우지석
  • Loading branch information
choinashil authored May 29, 2024
2 parents 5b4db1f + 13f9547 commit f9bfea6
Show file tree
Hide file tree
Showing 59 changed files with 1,629 additions and 199 deletions.
1 change: 1 addition & 0 deletions api/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BASE_URL = "https://bootcamp-api.codeit.kr/api";
24 changes: 10 additions & 14 deletions components/EmailInput.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
// components/EmailInput.tsx

import React, { useState, useEffect } from "react";
import classNames from "classnames/bind";
import styles from "@/styles/signin.module.scss";
import styles from "@/styles/sign-in.module.scss";

import { isEmailValid } from "@/utils/util";

interface EmailInputProps {
value: string;
onChange: (value: string) => void;
error?: string;
}

export const emailCheck = (email: string) => {
const emailForm =
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
return emailForm.test(email);
};

export function EmailInput({ value, onChange, error }: EmailInputProps) {
export default function EmailInput({
value,
onChange,
error,
}: EmailInputProps) {
const cx = classNames.bind(styles);
const [isFocused, setIsFocused] = useState(false);
const [emailErrorText, setEmailErrorText] = useState("");
Expand All @@ -33,7 +31,7 @@ export function EmailInput({ value, onChange, error }: EmailInputProps) {
const handleBlur = () => {
setIsFocused(false);
if (value) {
if (!emailCheck(value)) {
if (!isEmailValid(value)) {
setEmailErrorText("올바른 이메일 주소가 아닙니다.");
} else {
setEmailErrorText("");
Expand All @@ -51,9 +49,7 @@ export function EmailInput({ value, onChange, error }: EmailInputProps) {

return (
<div className={cx("input__section")}>
<label className={cx("text")}>
이메일 <br />
</label>
<label className={cx("text")}>이메일</label>
<input
id="email"
placeholder="이메일을 입력해 주세요."
Expand Down
73 changes: 73 additions & 0 deletions components/Folder/AddLinkBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Image from "next/image";
import styled from "styled-components";

const AddLinkBarContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 220px;
width: 100%;
`;
const AddLinkInputBox = styled.div`
background-color: #fff;
position: relative;
height: 70px;
width: 800px;
border: 1px solid #6d6afe;
border-radius: 15px;
padding: 16px 20px;
`;
const InputField = styled.input`
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0;
&::placeholder {
text-indent: 10px;
font-size: 16px;
font-weight: 400;
color: #9fa6b2;
}
`;
const LinkIcon = styled(Image)`
position: absolute;
left: 5px;
top: 50%;
transform: translateY(-50%);
`;
const AddButton = styled.button`
height: 38px;
background: linear-gradient(90.99deg, #6d6afe 0.12%, #6ae3fe 101.84%);
border: none;
border-radius: 8px;
color: #f5f5f5;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
text-align: center;
right: 20px;
white-space: nowrap;
position: absolute;
`;

function AddLinkBar() {
return (
<AddLinkBarContainer>
<AddLinkInputBox>
<InputField type="search" placeholder="링크를 추가해 보세요." />
<LinkIcon
width={20}
height={20}
src="/icon/link.svg"
alt="링크모양 아이콘"
/>
<AddButton>추가하기</AddButton>
</AddLinkInputBox>
</AddLinkBarContainer>
);
}

export default AddLinkBar;
71 changes: 71 additions & 0 deletions components/Folder/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState } from "react";
import styled from "styled-components";

interface FolderData {
id: number;
name: string;
}

interface StyledFolderButtonProps {
active: boolean;
}

const StyledFolderButton = styled.button<StyledFolderButtonProps>`
background-color: #ffffff;
width: auto;
height: 36px;
border-radius: 5px;
border: 1px solid #6d6afe;
font-size: 16px;
font-weight: 400;
margin-top: 6px;
margin-right: 10px;
text-align: center;
&:hover {
cursor: pointer;
}
${({ active }) =>
active &&
`
background-color: #6D6AFE;
color: #ffffff;
`}
`;

interface ButtonProps {
folderData: FolderData[];
selectedFolderId: number | null;
onFolderClick: (buttonId: number | null) => void;
}

function Button({ folderData, selectedFolderId, onFolderClick }: ButtonProps) {
const [activeButton, setActiveButton] = useState<number | null>(null);

const handleButtonClick = (buttonId: number | null) => {
setActiveButton(buttonId);
onFolderClick(buttonId);
};

return (
<>
<StyledFolderButton
onClick={() => handleButtonClick(null)}
active={activeButton === null}
>
전체
</StyledFolderButton>
{folderData.map((data) => (
<StyledFolderButton
key={data.id}
onClick={() => handleButtonClick(data.id)}
active={activeButton === data.id}
>
{data.name}
</StyledFolderButton>
))}
</>
);
}
export default Button;
96 changes: 96 additions & 0 deletions components/Folder/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import styled from "styled-components";
import { formatDate, generateTimeText } from "@/utils/util";
import Link from "next/link";

const StyledCard = styled.div`
max-width: 340px;
display: flex;
flex-direction: column;
align-items: flex-start;
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
border-radius: 10px;
@media (max-width: 1124px) {
max-width: 340px;
}
`;

const StyledLink = styled(Link)`
display: block;
width: 100%;
`;

const CardImg = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
`;

const CardTextSection = styled.section`
width: 100%;
height: 136px;
padding: 10px 20px;
display: grid;
grid-template-rows: 1fr 2fr 1fr;
grid-template-columns: 1fr;
grid-template-areas:
"time"
"description"
"date";
`;

const CardDescription = styled.p`
margin: 0;
width: 100%;
height: 50px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
grid-area: description;
`;

const TimeText = styled.p`
margin: 0;
grid-area: time;
`;

const DateText = styled.p`
margin: 0;
grid-area: date;
`;
export interface LinkData {
id: number;
created_at: string;
updated_at: string;
url: string;
title: string;
description: string;
image_source: string;
folder_id: number;
}

function Card({ linkData }: { linkData: LinkData }) {
return (
<StyledCard>
<StyledLink href={linkData.url} target="_blank">
<CardImg
src={linkData.image_source || "/img/thumbnail.svg"}
alt={linkData.title}
/>
</StyledLink>
<CardTextSection>
<TimeText>{generateTimeText(linkData.created_at)}</TimeText>
<CardDescription>{linkData.description}</CardDescription>
<DateText>{formatDate(linkData.created_at)}</DateText>
</CardTextSection>
</StyledCard>
);
}

export default Card;
72 changes: 72 additions & 0 deletions components/Folder/CardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import axios from "axios";
import Card, { LinkData } from "./Card";
import { BASE_URL } from "@/api/config";
import styled from "styled-components";

const StyledCardContainer = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 20px;
row-gap: 25px;
margin: 0 auto;
@media (max-width: 1124px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 767px) {
grid-template-columns: repeat(1, 1fr);
}
`;

interface CardListProps {
selectedFolderId: number | null;
}

function CardList({ selectedFolderId }: CardListProps) {
const [linkData, setLinkData] = useState<LinkData[]>([]);

useEffect(() => {
const fetchLinkData = async () => {
const accessToken = localStorage.getItem("accessToken");

if (accessToken) {
try {
let url = `${BASE_URL}/links`;
if (selectedFolderId !== null) {
url += `?folderId=${selectedFolderId}`;
console.log("Id :", selectedFolderId);
}
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (selectedFolderId === null) {
setLinkData(response.data.data.folder);
} else {
setLinkData(response.data.data);
}
console.log("response : ", response.data.data);
} catch (error) {
console.error("에러 메세지 : ", error);
}
}
};

fetchLinkData();
}, [selectedFolderId]);

return (
<StyledCardContainer>
{linkData.length > 0 ? (
linkData.map((data) => <Card key={data.id} linkData={data} />)
) : (
<div>저장된 링크가 없습니다.</div>
)}
</StyledCardContainer>
);
}

export default CardList;
Loading

0 comments on commit f9bfea6

Please sign in to comment.