Skip to content

Commit d1e37a3

Browse files
authored
Merge pull request #58 from wlee261/feat/themes-page-implementation
feat: themes page implementation
2 parents 91e6c4a + 7371bd0 commit d1e37a3

File tree

9 files changed

+342
-244
lines changed

9 files changed

+342
-244
lines changed

src/components/LandingPage/HeroSection.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@ export default function HeroSection() {
99
Browse, rate and share themes for your chatbot today and more!
1010
</h3>
1111
<div className="flex mt-6 flex-col items-center sm:flex-row gap-2">
12-
13-
14-
<Link to={"/themes"}>
15-
<button className='bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600 transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg'>
12+
<Link to={"/themes"}>
13+
<button
14+
className="
15+
bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600
16+
transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg
17+
"
18+
>
1619
Themes
1720
</button>
1821
</Link>
19-
<Link to={"/plugins"}>
20-
<button className='bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600 transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg'>
22+
<Link to={"/plugins"}>
23+
<button
24+
className="
25+
bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600
26+
transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg
27+
"
28+
>
2129
Plugins
2230
</button>
2331
</Link>
24-
<Link to={"https://react-chatbotify.com"}>
25-
<button className='bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600 transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg'>
32+
<Link to={"https://react-chatbotify.com"}>
33+
<button
34+
className="
35+
bg-accent-900 hover:bg-gradient-to-r hover:from-secondary-900 px-2 py-1 text-[14px] hover:to-primary-600
36+
transition-colors duration-300 hover:text-accent-900 sm:px-4 sm:py-2 sm:text-lg rounded-lg
37+
"
38+
>
2639
Documentation
2740
</button>
2841
</Link>

src/components/NavigationBar/NavigationBar.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Link, useNavigate } from 'react-router-dom';
33

44
import { handleLogin } from '../../services/authService';
55
import { useAuth } from '../../context/AuthContext';
6-
import { SiteConfig } from '../../constants/SiteConfig';
76
import logo from '../../assets/images/logo.png';
87
import AppThemeToggle from './AppThemeToggle';
98
import { useTranslation } from 'react-i18next';
@@ -41,7 +40,7 @@ const NavigationBar = () => {
4140
const navbarRef = useRef<HTMLDivElement>(null)
4241

4342
useEffect(function(){
44-
window.addEventListener('scroll',function(e){
43+
window.addEventListener('scroll',function(){
4544
if(navbarRef.current) {
4645
if(window.scrollY >= 50) {
4746
navbarRef.current.className = navBarClass + ' opacity-80 bg-black'

src/components/SearchBar/SearchBar.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ const SearchBar: React.FC<Props> = ({ onSearch }) => {
3232

3333
return (
3434
<div className="mb-8">
35-
<h2 className="text-lg font-semibold mb-4">Search</h2>
3635
<input
3736
type="text"
3837
placeholder="Search themes..."
3938
value={query}
4039
onChange={handleChange}
4140
onKeyDown={handleKeyDown}
42-
className="w-full border rounded-md px-3 py-2"
41+
className="border rounded-md px-2 py-1 w-3/4 bg-accent-800 text-xs"
4342

4443
/>
4544
</div>

src/components/Themes/ThemeCard.tsx

Lines changed: 59 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,87 @@
1-
import React, { useState } from 'react';
1+
import React, { useState } from "react";
22

3-
import { Link } from 'react-router-dom';
3+
import Skeleton from "react-loading-skeleton";
44

5-
import ThemeModal from './ThemeModal';
6-
import { Theme } from '../../interfaces/Theme';
7-
import FavIcon from '../../assets/images/icon_favorite.svg';
8-
import GitHubIcon from '../../assets/images/icon_github_white.svg';
5+
import { Theme } from "../../interfaces/Theme";
96
import { useTranslation } from 'react-i18next';
10-
import '../../styles/theme_card.css'
7+
import "../../styles/theme_card.css";
8+
import ThemeModal from "./ThemeModal";
9+
import { InfoIcon } from "lucide-react";
1110

1211
type Props = {
13-
theme: Theme
14-
isPreviewed: boolean
15-
onPreview: (name: string) => void
16-
}
12+
theme: Theme;
13+
isPreviewed: boolean;
14+
onPreview: (name: string) => void;
15+
isLoading: boolean;
16+
};
1717

1818
/**
1919
* Theme card component to hold the details of each theme in the themes page.
2020
*/
21-
const ThemeCard: React.FC<Props> = ({ theme, isPreviewed, onPreview }) => {
22-
const [isFav, setIsFav] = useState(false)
23-
const [viewDetails, setViewDetails] = useState(false)
21+
const ThemeCard: React.FC<Props> = ({ theme, isPreviewed, onPreview, isLoading }) => {
22+
const [viewDetails, setViewDetails] = useState(false);
2423

2524
const onViewDetails = () => {
26-
setViewDetails(true)
27-
}
25+
setViewDetails(true);
26+
};
2827

2928
const onClickPreview = () => {
3029
onPreview(theme.name)
3130
}
3231

32+
const handleCheckboxChange = () => {
33+
onClickPreview();
34+
};
35+
3336
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3437
const {t} = useTranslation();
3538

39+
if (isLoading) {
40+
return (
41+
<div>
42+
<div className="my-4 mx-2 hidden md:block">
43+
<Skeleton height={452} />
44+
</div>
45+
<div className="my-4 mx-2 block md:hidden">
46+
<Skeleton height={148} />
47+
</div>
48+
</div>
49+
);
50+
}
51+
3652
return (
3753
<>
38-
<div className="text-black w-[300px] h-[545px]">
39-
<button
40-
onClick={onViewDetails}
41-
type="button"
42-
className="relative group"
54+
<div
55+
className={`flex flex-row items-center md:items-start md:flex-col
56+
md:w-64 p-4 my-4 mx-2 border-2 border-solid rounded-xl
57+
${isPreviewed ? "border-blue-700" : "border-accent-600"}`}
58+
>
59+
<div
60+
className="flex-1 basis-1/3 md:basis-1/2 mr-3 flex flex-row
61+
overflow-hidden w-32 h-20 md:w-56 md:h-56 rounded-xl"
4362
>
44-
<img
45-
src={theme.themeImg}
46-
className="cursor-pointer w-full h-[400px] scale-80
47-
group-hover:-translate-y-6 transition ease-in-out duration"
48-
alt={theme.name}
49-
/>
50-
<div className="theme-card-details">View Details</div>
51-
</button>
52-
<div className="theme-card-info">
53-
<div>
54-
<div className="flex justify-between">
55-
<p className="theme-card-title">{theme.name}</p>
56-
<button
57-
type="button"
58-
aria-label="Favorite Button"
59-
className={`theme-card-fav ${isFav && 'active'}`}
60-
onClick={() => setIsFav((fav) => !fav)}
61-
>
62-
<img src={FavIcon} alt="fav-icon" />
63-
</button>
64-
</div>
65-
<p className="text-[15px] opacity-80">{theme.description}</p>
66-
</div>
67-
<Link to={`/profile/${theme.github}`} className="theme-card-github">
68-
<img src={GitHubIcon} alt="github-icon" />
69-
</Link>
70-
<div className="flex justify-between">
71-
<h4 className="theme-card-author">{theme.authorName}</h4>
72-
<button
73-
className={`theme-card-preview ${isPreviewed ? 'active' : ''}`}
74-
type="button"
75-
onClick={onClickPreview}
76-
>
77-
Preview
63+
<img src={theme.themeImg} alt={theme.name} className="w-60 h-60 object-cover object-left-top" />
64+
</div>
65+
<div className="flex-1 mr-4 basis-1/2 md:basis-1/3 flex flex-col md:pt-4">
66+
<h2 className="text-accent-50 text-lg font-medium mt-[-4px]">{theme.name}</h2>
67+
<span className="text-accent-300 text-sm">{theme.description}</span>
68+
</div>
69+
<div className="flex-1 basis-1/6 md:basis-1/6 flex flex-col">
70+
<div className="flex items-center text-blue-500">
71+
<button onClick={onViewDetails} className="text-sm my-4 w-fit mr-[3px]">
72+
More Info
7873
</button>
74+
<InfoIcon size={15}/>
7975
</div>
76+
<label className=" ml-[2px] text-accent-50 text-sm md:text-base">
77+
Select
78+
<input type="checkbox" checked={isPreviewed} onChange={handleCheckboxChange} className="ml-2" />
79+
</label>
8080
</div>
8181
</div>
82-
<ThemeModal
83-
isOpen={viewDetails}
84-
theme={theme}
85-
onClose={() => setViewDetails(false)}
86-
/>
82+
<ThemeModal isOpen={viewDetails} theme={theme} onClose={() => setViewDetails(false)} />
8783
</>
88-
)
89-
}
84+
);
85+
};
9086

91-
export default ThemeCard
87+
export default ThemeCard;

src/components/Themes/ThemeModal.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import { downloadThemeContent } from '../../utils';
66
import { Theme } from '../../interfaces/Theme';
77

88
type ThemeModalProps = {
9-
isOpen: boolean
10-
onClose: () => void
11-
theme: Theme
12-
}
9+
isOpen: boolean;
10+
onClose: () => void;
11+
theme: Theme;
12+
};
1313

1414
/**
1515
* Modal to popup for showing theme details.
1616
*/
1717
const ThemeModal: React.FC<ThemeModalProps> = ({ isOpen, onClose, theme }) => {
18-
const modalRef = useRef<HTMLDivElement>(null)
18+
const modalRef = useRef<HTMLDivElement>(null);
1919

2020
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2121
const {t} = useTranslation();
@@ -26,36 +26,37 @@ const ThemeModal: React.FC<ThemeModalProps> = ({ isOpen, onClose, theme }) => {
2626
modalRef.current &&
2727
!modalRef.current.contains(event.target as Node)
2828
) {
29-
onClose()
29+
onClose();
3030
}
31-
}
31+
};
3232

3333
if (isOpen) {
34-
document.addEventListener('mousedown', handleClickOutside)
35-
document.body.style.overflow = 'hidden'
34+
document.addEventListener('mousedown', handleClickOutside);
35+
document.body.style.overflow = 'hidden';
3636
}
3737

3838
return () => {
39-
document.removeEventListener('mousedown', handleClickOutside)
40-
document.body.style.overflow = 'unset'
41-
}
42-
}, [isOpen, onClose])
39+
document.removeEventListener('mousedown', handleClickOutside);
40+
document.body.style.overflow = 'unset';
41+
};
42+
}, [isOpen, onClose]);
4343

44-
if (!isOpen) return null
44+
if (!isOpen) return null;
4545

4646
const onDownload = () => {
4747
downloadThemeContent(
4848
theme.content.settings,
4949
theme.content.inlineStyles,
5050
theme.content.cssStyles,
5151
theme.name
52-
)
53-
}
52+
);
53+
};
5454

5555
const modalContent = (
5656
<div
5757
className="fixed inset-0 z-50 overflow-y-auto bg-black bg-opacity-50
58-
flex items-center justify-center pointer-events-auto">
58+
flex items-center justify-center pointer-events-auto"
59+
>
5960
<div
6061
ref={modalRef}
6162
className="relative bg-white p-6 rounded-lg shadow-lg max-w-screen-lg w-full m-4"
@@ -129,12 +130,12 @@ const ThemeModal: React.FC<ThemeModalProps> = ({ isOpen, onClose, theme }) => {
129130
</div>
130131
</div>
131132
</div>
132-
)
133+
);
133134

134135
return ReactDOM.createPortal(
135136
modalContent,
136137
document.getElementById('modal-container') || document.body
137-
)
138-
}
138+
);
139+
};
139140

140-
export default ThemeModal
141+
export default ThemeModal;

0 commit comments

Comments
 (0)