Skip to content

Commit

Permalink
Merge pull request #17 from xyzith/feature/profilePage
Browse files Browse the repository at this point in the history
feat profile page
  • Loading branch information
mcg25035 authored Feb 22, 2024
2 parents bb528be + a73e238 commit 715c000
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 85 deletions.
4 changes: 3 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import Nav from "./Nav";
import Footer from "./containers/Footer";
import IndexPage from "./containers/IndexPage";
import ArticlePage from "./containers/ArticlePage";
import "./App.scss";
import ProfilePage from "./containers/ProfilePage";
import Login from "./acount/login";
import "./App.scss";

function App(){
var divStyle={
Expand All @@ -22,6 +23,7 @@ function App(){
<Route path="/working_project" element={<IndexPage />} />
<Route path="/join_us" element={<IndexPage />} />
<Route path="/login" element={<Login />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</main>
<Footer />
Expand Down
8 changes: 4 additions & 4 deletions src/containers/IndexPage/IndexContentBlock.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ function IndexContentBlock({ articleData }){
over : () => {
for (var i in refs){
/**@type {HTMLElement} */
var element = refs[i].current
var element = refs[i].current;
element.classList.add("hover");
}
},
},
out : () => {
for (var i in refs){
/**@type {HTMLElement} */
var element = refs[i].current
var element = refs[i].current;
element.classList.remove("hover");
}
}
}
};
return <div className="index-content-container" ref={refs[0]}>
<div className="index-content-header">
<p className="date">{timestampFormat(date)}</p>
Expand Down
1 change: 1 addition & 0 deletions src/containers/IndexPage/IndexContentBlock.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ div.index-content-container{

@media (orientation: portrait) {
.index-content-wrapper {
padding: 0;
grid-template-columns: 1fr;
}
}
41 changes: 41 additions & 0 deletions src/containers/ProfilePage/ImageInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCamera } from '@fortawesome/free-solid-svg-icons';
import './ImageInput.scss';

const ImageInput = ({ value }) => {

const [tmpImage, setTmpImage ]= useState('');

const changeImage = async (ev) => {
const { files } = ev?.target || {};
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
setTmpImage(dataUrl);
};

const displayImg = tmpImage ? tmpImage : value;

return (
<div className="image-input">
<div className="image-wrapper">
<img src={displayImg} />
<div className="camera-tip">
<FontAwesomeIcon icon={faCamera} />
</div>
</div>
<input type="file" onChange={changeImage} />
</div>
);
};

ImageInput.propTypes = {
value: PropTypes.string.isRequired,
};

export default ImageInput;
55 changes: 55 additions & 0 deletions src/containers/ProfilePage/ImageInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.image-input {
display: flex;
width: 100%;
align-items: center;
justify-content: center;

.image-wrapper {
position: relative;
width: 240rem;
height: 240rem;
border-radius: 50%;
overflow: hidden;
}

.camera-tip {
position: absolute;
bottom: 0;
background: #00000066;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 40%;
transition: transform .3s;
transform: translateY(100%);
svg {
width: 40rem;
height: 40rem;
path{
fill: white;
}
}
}

&:hover .camera-tip {
transform: translateY(0);
}

input[type="file"] {
display: none;
}

img {
width: 100%;
height: 100%;
object-fit: cover;
}
}

@media (orientation: portrait) {
.image-input .image-wrapper{
width: 80vw;
height: 80vw;
}
}
45 changes: 45 additions & 0 deletions src/containers/ProfilePage/Profile.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.profile-page {
padding-top: 120rem;
margin: auto;
width: 90vw;
max-width: 800rem;
font-size: 28rem;

label {
width: 100%;
margin: 20rem auto;
display: flex;
align-items: center;
span {
width: 300rem;
}

input {
flex: 1;
padding: 0 8rem;
box-sizing: border-box;
border-radius: 8rem;
background-color: transparent;
border: 1px solid transparent;

&:focus {
outline-width: 0;
}

&:not(:read-only) {
border-color: #052745;
}
}
}
}

@media (orientation: portrait) {
.profile-page {
label {
flex-wrap: wrap;
span {
basis-width: 100%;
}
}
}
}
18 changes: 18 additions & 0 deletions src/containers/ProfilePage/SwitchInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import './SwitchInput.scss';

const SwitchInput = ({ value, onClick }) => {
const className = classNames('switch-input', { enabled: value });

return (
<div className={className} onClick={onClick} />
);
};

SwitchInput.propTypes = {
value: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
};

export default SwitchInput;
30 changes: 30 additions & 0 deletions src/containers/ProfilePage/SwitchInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.switch-input {
position: relative;
display: inline-block;
box-sizing: border-box;
width: 60rem;
height: 30rem;
background-color: #ff5a67;
vertical-align: middle;
border-radius: 15rem;
transition: background-color .3s;
&::after {
content: '';
position: absolute;
display: block;
width: 30rem;
height: 30rem;
background-color: white;
box-shadow: 2rem 2rem 4rem #dbdbdb;
border-radius: 15px;
transition: left .3s;
left: calc(100% - 30rem);
}

&.enabled {
background-color: #aae88f;
&::after {
left: 0;
}
}
}
98 changes: 98 additions & 0 deletions src/containers/ProfilePage/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import SwitchInput from './SwitchInput';
import ImageInput from './ImageInput';
import './Profile.scss';

// XXX: mock
const mockData = [
{ name:"使用者名稱", type:"string", editable: true, value: "codingbear" },
{ name:"密碼", type:"password", editable: true, value: null },
{ name:"累積發文數", type:"number", editable: false, value: 276 },
{ name:"上線顯示", type:"boolean", editable: true, value: true },
{ name:"頭貼", type:"image", editable: true, value: "https://cdn.discordapp.com/attachments/763787703958372402/1209053537628323900/image.png?ex=65e585da&is=65d310da&hm=51f9cfce771d3b41cfb53286a3ad4ec9fbea74bdec59764830c82d14bd5bd3bf&" },
];

const ProfilePage = () => {
// const updateProfile = () => {
// // TODO: update profile
// };

const mapInputType = (type) => {
switch (type) {
case 'string':
return 'text';
default:
return type;
}
};

const renderInput = (data) => {
const { type } = data;
switch (type) {
case 'string':
case 'password':
case 'number':
return renderCommonInput(data);
case 'boolean':
return renderSwitchInput(data);
case 'image':
return renderImageInput(data);
default:
return null;

}

};

const rendetInputRow = (data) => {
const { name } = data;
return (
<label key={name}>
<span>{name}</span>
{renderInput(data)}
</label>
);
};

const renderCommonInput = ({ name, type, editable, value }) => {
const displayValue = type === 'password' ? '********' : value;
const fixedType = mapInputType(type);
const onChange = () => {
// TODO: update value;
};
return (
<input
type={fixedType}
name={name}
readOnly={!editable}
value={displayValue}
onChange={onChange}
/>
);
};

const renderSwitchInput = ({ editable, value }) => {
const onClick = () => {
if (editable) {
// TODO: update value;
}
};

return (
<SwitchInput value={value} onClick={onClick} />
);
};

const renderImageInput = ({ value, name, editable }) => {
return (
<ImageInput value={value} name={name} editable={editable} />
);
};

return (
<div className="profile-page">
{mockData.map(rendetInputRow)}
</div>
);
};

export default ProfilePage;
Loading

0 comments on commit 715c000

Please sign in to comment.