Skip to content
Open
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
2,850 changes: 2,850 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-router-dom": "^7.7.1"
},
"devDependencies": {
"@eslint/js": "^9.30.1",
Expand Down
31 changes: 28 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
import { useState } from "react";
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Header from "./components/Header";
import Home from "./pages/Home";
import ProfileLayout from './pages/ProfileLayout';
import ProfileList from './components/ProfileList';
import ProfileForm from './components/ProfileForm';
import ProfileModify from './components/ProfileModify';
import cardData from './data/cardData';

function App() {
return <>당신은 할 수 있습니다.</>;
}
const [profiles, setProfiles] = useState(cardData)

export default App;
return (
<BrowserRouter>
<Header />
<div className='container'>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/profiles' element={<ProfileLayout profiles={profiles} setProfiles={setProfiles}/>}>
<Route path='list' element={<ProfileList />} />
<Route path='new' element={<ProfileForm />} />
<Route path='modify/:id' element={<ProfileModify />} />
</Route>
</Routes>
</div>
</BrowserRouter>
);
};
export default App;
Binary file added src/assets/PARADOX_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/PARADOX_reverse.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from 'react-router-dom';

export default function Header() {

return (
<div className='header'>
<header>
<h1>Profile Card List</h1>
</header>
<nav>
<Link to='/' className='navtxt'>Home</Link>
<Link to='/profiles/list' className='navtxt'>Card List</Link>
<Link to='/profiles/new' className='navtxt'>Make Card</Link>
</nav>
</div>
);
};
33 changes: 33 additions & 0 deletions src/components/ProfileCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Link } from 'react-router-dom';

export default function ProfileCard({ profile, setProfiles }) {


const deleteBtn = () => {
setProfiles(prev => prev.filter(p => p.id !== profile.id));
}

return (
<div className='card'>
<div className='imageName'>
<img src={profile.imgUrl} alt='Profile' />
<h3>{profile.name}</h3>
</div>
<div className='introBtnBox'>
<div className='introContainer'>
<p><strong>Team. </strong><strong>{profile.team}</strong></p>
<strong>{profile.job}</strong>
<p><strong>tel. </strong><span>{profile.tel}</span></p>
<p><strong>email. </strong><span>{profile.email}</span></p>
</div>
<div className='btns'>
<Link to={`/profiles/modify/${profile.id}`}>
<button className='modifyBtn'>수정</button>
</Link>
<button className='deleteBtn' onClick={deleteBtn}>삭제</button>
</div>
</div>
</div>
)
}

96 changes: 96 additions & 0 deletions src/components/ProfileForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useOutletContext } from 'react-router-dom';

export default function ProfileForm() {
const { profiles, setProfiles } = useOutletContext();
const navigate = useNavigate();
const [data, setData] = useState({
Name: '',
Team: '',
Job: '',
Phone: '',
Email: '',
Image: ''
});

const nameRef = useRef(null);
const teamRef = useRef(null);
const jobRef = useRef(null);
const phoneRef = useRef(null);
const emailRef = useRef(null);

const change = (e) => {
const { name, value } = e.target;
setData(prev => ({ ...prev, [name]: value }))
}

const Submit = (e) => {
e.preventDefault();

if (!data.Name) {
alert('이름이 입력되지 않았습니다!');
nameRef.current.focus();
return;
}
if(!data.Team) {
alert('팀이 입력되지 않았습니다!');
teamRef.current.focus();
return;
}
if(!data.Job) {
alert('직책이 입력되지 않았습니다!');
jobRef.current.focus();
return;
}
if(!data.Phone) {
alert('전화번호가 입력되지 않았습니다!');
phoneRef.current.focus();
return;
}
if(!data.Email) {
alert('이메일이 입력되지 않았습니다!');
emailRef.current.focus();
return;
}
if(!data.Image) {
alert('사진이 선택되지 않았습니다!');
return;
}

const maxId = Math.max(...profiles.map(p => p.id))
const newProfile = {
id: maxId+1,
name: data.Name,
team: data.Team,
job: data.Job,
tel: data.Phone,
email: data.Email,
imgUrl: data.Image === 'default' ? '/src/assets/PARADOX_default.png' : '/src/assets/PARADOX_reverse.png'
};

setProfiles([...profiles, newProfile]);

navigate('../list')
};

return (
<div className='container'>
<h1>프로필 카드 만들기</h1>
<form onSubmit={Submit} className='inputBox'>
<h2>정보를 입력해주세요.</h2>
<p><b><span>Name </span></b><input type="text" ref={nameRef} name='Name' value={data.Name} onChange={change} placeholder='ex) 이주환' /></p>
<p><b><span>Team </span></b><input type="text" ref={teamRef} name='Team' value={data.Team} onChange={change} placeholder='ex) PARADOX' /></p>
<p><b><span>Job </span></b><input type="text" ref={jobRef} name='Job' value={data.Job} onChange={change} placeholder='ex) Frontend Developer' /></p>
<p><b><span>Phone </span></b><input type="text" ref={phoneRef} name='Phone' value={data.Phone} onChange={change} placeholder='ex) 010-8888-4444' /></p>
<p><b><span>Email </span></b><input type="text" ref={emailRef} name='Email' value={data.Email} onChange={change} placeholder='ex) [email protected]' /></p>
<p><b><span>Image </span></b>
<input type="radio" name='Image' value="default" onChange={change}/>Default
<b></b>
<input type="radio" name='Image' value="reverse" onChange={change}/>Reverse
</p>
<button type='submit'>등록하기</button>
</form>
</div>
)
}
19 changes: 19 additions & 0 deletions src/components/ProfileList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ProfileCard from "./ProfileCard";
import { useOutletContext } from 'react-router-dom';

export default function ProfileList() {
const { profiles, setProfiles } = useOutletContext();

return (
<div>
<h1>프로필 카드 목록</h1>
<ul className='cardContainer'>
{profiles.map(profile => (
<li key={profile.id}>
<ProfileCard profile={profile} setProfiles={setProfiles} />
</li>
))}
</ul>
</div>
)
}
96 changes: 96 additions & 0 deletions src/components/ProfileModify.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useState, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useOutletContext } from 'react-router-dom';

export default function ProfileModify() {
const { profiles, setProfiles } = useOutletContext();

const { id } = useParams();
const navigate = useNavigate();
const profile = profiles.find(p => p.id === parseInt(id));

const [data, setData] = useState({
Name: profile.name,
Team: profile.team,
Job: profile.job,
Phone: profile.tel,
Email: profile.email,
Image: profile.imgUrl.includes('default') ? 'default' : 'reverse'
});

const nameRef = useRef(null);
const teamRef = useRef(null);
const jobRef = useRef(null);
const phoneRef = useRef(null);
const emailRef = useRef(null);

const change = (e) => {
const { name, value } = e.target;
setData(prev => ({ ...prev, [name]: value }))
}

const Submit = (e) => {
e.preventDefault();

if (!data.Name) {
alert('이름이 입력되지 않았습니다!');
nameRef.current.focus();
return;
}
if(!data.Team) {
alert('팀이 입력되지 않았습니다!');
teamRef.current.focus();
return;
}
if(!data.Job) {
alert('직책이 입력되지 않았습니다!');
jobRef.current.focus();
return;
}
if(!data.Phone) {
alert('전화번호가 입력되지 않았습니다!');
phoneRef.current.focus();
return;
}
if(!data.Email) {
alert('이메일이 입력되지 않았습니다!');
emailRef.current.focus();
return;
}

const updateProfile = {
id: parseInt(id),
name: data.Name,
team: data.Team,
job: data.Job,
tel: data.Phone,
email: data.Email,
imgUrl: data.Image === 'default' ? '/src/assets/PARADOX_default.png' : '/src/assets/PARADOX_reverse.png'
};

setProfiles(prev => prev.map(p => p.id === parseInt(id) ? updateProfile : p));

navigate('/profiles/list');
};


return (
<div className='container'>
<h1>프로필 카드 만들기</h1>
<form onSubmit={Submit} className='inputBox'>
<h2>정보를 입력해주세요.</h2>
<p><b><span>Name </span></b><input type="text" ref={nameRef} name='Name' value={data.Name} onChange={change} placeholder='ex) 이주환' /></p>
<p><b><span>Team </span></b><input type="text" ref={teamRef} name='Team' value={data.Team} onChange={change} placeholder='ex) PARADOX' /></p>
<p><b><span>Job </span></b><input type="text" ref={jobRef} name='Job' value={data.Job} onChange={change} placeholder='ex) Frontend Developer' /></p>
<p><b><span>Phone </span></b><input type="text" ref={phoneRef} name='Phone' value={data.Phone} onChange={change} placeholder='ex) 010-8888-4444' /></p>
<p><b><span>Email </span></b><input type="text" ref={emailRef} name='Email' value={data.Email} onChange={change} placeholder='ex) [email protected]' /></p>
<p><b><span>Image </span></b>
<input type="radio" name='Image' value="default" checked={data.Image === 'default'} onChange={change} />Default
<b></b>
<input type="radio" name='Image' value="reverse" checked={data.Image === 'reverse'} onChange={change} />Reverse
</p>
<button type='submit'>수정완료</button>
</form>
</div>
)
}
31 changes: 31 additions & 0 deletions src/data/cardData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const cardData = [
{
id: 1,
name: "이주환",
team: "PARADOX",
imgUrl: "/src/assets/PARADOX_reverse.png",
job: "student",
tel: "010-4026-6461",
email: "[email protected]",
},
{
id: 2,
name: "삼주환",
team: "PARADOX",
imgUrl: "/src/assets/PARADOX_default.png",
job: "student",
tel: "010-4026-6461",
email: "[email protected]",
},
{
id: 3,
name: "사주환",
team: "PARADOX",
imgUrl: "/src/assets/PARADOX_reverse.png",
job: "student",
tel: "010-4026-6461",
email: "[email protected]",
}
];

export default cardData;
23 changes: 23 additions & 0 deletions src/pages/Home.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Outlet } from 'react-router-dom';
import '../styles/Home.css'


const textList = [
"Home : 홈화면을 볼 수 있다.",
"Card List : 카드 리스트를 볼 수 있다.",
"Make Card : 카드를 만들 수 있다.",
];

export default function Home() {

const list = textList.map(li => <li key={li}>{li}</li>)
return (
<div className='container'>
<Outlet />
<h1 className='title'>프로필 카드 리스트 만들기</h1>
<ul>
{list}
</ul>
</div>
);
}
12 changes: 12 additions & 0 deletions src/pages/ProfileLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Outlet } from 'react-router-dom';
import '../styles/ProfileLayout.css';

function ProfileLayout({ profiles, setProfiles }) {
return (
<div className='layout'>
<Outlet context={{ profiles, setProfiles }} />
</div>
)
}

export default ProfileLayout
Loading