Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
# No special permissions needed for public repositories
GITHUB_TOKEN=your_github_token_here

# Shopify Configuration (for Merch Store)
# Get these from: Shopify Admin > Settings > Apps and sales channels > Develop apps
# Required scopes: unauthenticated_read_product_listings, unauthenticated_write_checkouts
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_access_token_here

# Firebase Configuration (if needed)
# FIREBASE_API_KEY=your_firebase_api_key
# FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain
Expand Down
7 changes: 7 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ const config: Config = {
label: "🎙️ Podcast",
to: "/podcasts/",
},
{
label: "🛍️ Merch Store",
to: "/merch",
},
],
},
// Search disabled until Algolia is properly configured
Expand Down Expand Up @@ -265,6 +269,9 @@ const config: Config = {
// ✅ Add this customFields object to expose the token to the client-side
customFields: {
gitToken: process.env.DOCUSAURUS_GIT_TOKEN,
// Shopify credentials for merch store
SHOPIFY_STORE_DOMAIN: process.env.SHOPIFY_STORE_DOMAIN || 'junh9v-gw.myshopify.com',
SHOPIFY_STOREFRONT_ACCESS_TOKEN: process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN || '2503dfbf93132b42e627e7d53b3ba3e9',
hooks: {
onBrokenMarkdownLinks: "warn",
},
Expand Down
177 changes: 177 additions & 0 deletions src/components/merch/FilterBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/* Filter Bar Styles */

.filter-bar {
background: var(--ifm-card-background-color);
border-bottom: 1px solid var(--ifm-color-emphasis-200);
position: sticky;
top: 60px;
z-index: 100;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.filter-bar-container {
max-width: 1400px;
margin: 0 auto;
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
flex-wrap: wrap;
}

/* Filter Section */
.filter-section {
flex: 1;
min-width: 300px;
}

.filter-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
color: var(--ifm-font-color-base);
}

.filter-title {
font-weight: 600;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}

/* Category Filters */
.category-filters {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}

.category-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--ifm-color-emphasis-100);
border: 2px solid transparent;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
color: #2d3748;
cursor: pointer;
transition: all 0.2s ease;
}

.category-button:hover {
background: var(--ifm-color-emphasis-200);
transform: translateY(-2px);
}

.category-button.active {
background: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
color: #ffffff !important;
font-weight: 600;
}

.category-icon {
font-size: 1.25rem;
}

.category-label {
white-space: nowrap;
}

/* Sort Section */
.sort-section {
min-width: 200px;
}

.sort-select {
width: 100%;
padding: 0.625rem 1rem;
background: var(--ifm-color-emphasis-100);
border: 2px solid var(--ifm-color-emphasis-200);
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
color: var(--ifm-font-color-base);
cursor: pointer;
transition: all 0.2s ease;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
padding-right: 2.5rem;
}

.sort-select:hover {
border-color: var(--ifm-color-primary);
}

.sort-select:focus {
outline: none;
border-color: var(--ifm-color-primary);
box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest);
}

/* Responsive Design */
@media (max-width: 768px) {
.filter-bar-container {
padding: 1rem;
flex-direction: column;
align-items: stretch;
}

.filter-section {
min-width: 100%;
}

.category-filters {
gap: 0.5rem;
}

.category-button {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
}

.category-icon {
font-size: 1rem;
}

.sort-section {
min-width: 100%;
}

.filter-bar {
top: 0;
}
}

/* Dark Mode */
[data-theme="dark"] .filter-bar {
background: var(--ifm-background-surface-color);
border-bottom-color: var(--ifm-color-emphasis-300);
}

[data-theme="dark"] .category-button {
background: var(--ifm-color-emphasis-200);
color: var(--ifm-font-color-base);
}

[data-theme="dark"] .category-button.active {
background: rgba(16, 185, 129, 0.15);
border-color: var(--ifm-color-primary);
color: #10b981 !important;
}

[data-theme="dark"] .category-button:hover {
background: var(--ifm-color-emphasis-300);
}

[data-theme="dark"] .sort-select {
background: var(--ifm-color-emphasis-200);
border-color: var(--ifm-color-emphasis-300);
}
80 changes: 80 additions & 0 deletions src/components/merch/FilterBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import { Filter, SlidersHorizontal } from "lucide-react";
import "./FilterBar.css";

interface FilterBarProps {
selectedCategory: string;
onCategoryChange: (category: string) => void;
sortBy: string;
onSortChange: (sortBy: string) => void;
}

const categories = [
{ id: "all", label: "All Products", icon: "🛍️" },
{ id: "t-shirts", label: "T-Shirts", icon: "👕" },
{ id: "hoodies", label: "Hoodies", icon: "🧥" },
{ id: "accessories", label: "Accessories", icon: "🎒" },
];

const sortOptions = [
{ value: "featured", label: "Featured" },
{ value: "price-low", label: "Price: Low to High" },
{ value: "price-high", label: "Price: High to Low" },
{ value: "name", label: "Name: A to Z" },
];

const FilterBar: React.FC<FilterBarProps> = ({
selectedCategory,
onCategoryChange,
sortBy,
onSortChange,
}) => {
return (
<div className="filter-bar">
<div className="filter-bar-container">
{/* Category Filters */}
<div className="filter-section">
<div className="filter-header">
<Filter size={18} />
<span className="filter-title">Categories</span>
</div>
<div className="category-filters">
{categories.map((cat) => (
<button
key={cat.id}
className={`category-button ${
selectedCategory === cat.id ? "active" : ""
}`}
onClick={() => onCategoryChange(cat.id)}
>
<span className="category-icon">{cat.icon}</span>
<span className="category-label">{cat.label}</span>
</button>
))}
</div>
</div>

{/* Sort Options */}
<div className="sort-section">
<div className="filter-header">
<SlidersHorizontal size={18} />
<span className="filter-title">Sort By</span>
</div>
<select
className="sort-select"
value={sortBy}
onChange={(e) => onSortChange(e.target.value)}
>
{sortOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
</div>
</div>
);
};

export default FilterBar;
Loading
Loading