Skip to content

Commit 928cfec

Browse files
authored
Merge pull request #97 from Balackburn/copilot/fix-blank-page-and-add-themes
Fix blank page, add all source variations, implement light/dark mode
2 parents 245107e + 1d4ab0d commit 928cfec

10 files changed

Lines changed: 304 additions & 100 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
cache: npm
26+
- run: npm ci
27+
- run: npm run build
28+
- uses: actions/upload-pages-artifact@v3
29+
with:
30+
path: dist
31+
32+
deploy:
33+
environment:
34+
name: github-pages
35+
url: ${{ steps.deployment.outputs.page_url }}
36+
runs-on: ubuntu-latest
37+
needs: build
38+
steps:
39+
- id: deployment
40+
uses: actions/deploy-pages@v4

src/App.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
import { ThemeProvider } from './hooks/useTheme';
2+
import ThemeToggle from './components/ThemeToggle';
13
import Header from './components/Header';
24
import Screenshots from './components/Screenshots';
35
import Description from './components/Description';
46
import Footer from './components/Footer';
57

68
function App() {
79
return (
8-
<>
10+
<ThemeProvider>
11+
<ThemeToggle />
912
<main>
1013
<Header />
1114
<Screenshots />
1215
<Description />
1316
</main>
1417
<Footer />
15-
</>
18+
</ThemeProvider>
1619
);
1720
}
1821

src/components/Header.jsx

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,82 @@
1+
import { useTheme } from '../hooks/useTheme';
12
import useRandomIcon from '../hooks/useRandomIcon';
23
import useDownloadLink from '../hooks/useDownloadLink';
34

4-
function ActionButtons({ downloadUrl, isMobile }) {
5+
const BASE_URL = 'https://raw.githubusercontent.com/Balackburn/Apollo/refs/heads/main';
6+
const REDIRECT = 'https://intradeus.github.io/http-protocol-redirector?r=';
7+
const BASE = import.meta.env.BASE_URL;
8+
9+
const SOURCES = [
10+
{
11+
title: 'Standard',
12+
description: 'Apollo + ImprovedCustomApi',
13+
json: 'apps.json',
14+
},
15+
{
16+
title: 'No Extensions',
17+
description: 'Fewer App IDs (1 vs 7) — ideal for free developer accounts',
18+
json: 'apps_noext.json',
19+
},
20+
{
21+
title: 'GLASS',
22+
description: 'Liquid Glass UI Patch (iOS 26+)',
23+
json: 'apps_glass.json',
24+
},
25+
{
26+
title: 'No Extensions + GLASS',
27+
description: 'Both options combined (iOS 26+)',
28+
json: 'apps_noext_glass.json',
29+
},
30+
];
31+
32+
function SourceButtons({ jsonFile, isDark }) {
33+
const folder = isDark ? 'DARK' : 'LIGHT';
34+
const jsonUrl = `${BASE_URL}/${jsonFile}`;
35+
36+
const buttons = [
37+
{
38+
href: `${REDIRECT}altstore://source?url=${jsonUrl}`,
39+
alt: 'Add to AltStore',
40+
img: `${BASE}images/buttons/${folder}/Altstore.png`,
41+
},
42+
{
43+
href: `${REDIRECT}feather://source/${jsonUrl}`,
44+
alt: 'Add to Feather',
45+
img: `${BASE}images/buttons/${folder}/Feather.png`,
46+
},
47+
{
48+
href: `${REDIRECT}sidestore://source?url=${jsonUrl}`,
49+
alt: 'Add to SideStore',
50+
img: `${BASE}images/buttons/${folder}/Sidestore.png`,
51+
},
52+
{
53+
href: jsonUrl,
54+
alt: 'Direct URL',
55+
img: `${BASE}images/buttons/${folder}/DirectURL.png`,
56+
},
57+
];
58+
559
return (
6-
<div className={`app__buttons ${isMobile ? 'app__buttons--mobile container' : 'app__buttons--desktop'}`}>
7-
<a
8-
className="app__button-ios"
9-
href="altstore://source/?url=https://balackburn.github.io/Apollo/apps.json"
10-
target="_blank"
11-
rel="noreferrer"
12-
title="Add to Altstore"
13-
>
14-
<img alt="Add to Altstore" src={isMobile ? '/images/UI/image_4.webp' : '/images/UI/image_1.webp'} />
15-
</a>
16-
<a
17-
className="app__button-web"
18-
href="https://github.com/Balackburn/Apollo"
19-
target="_blank"
20-
rel="noreferrer"
21-
title="See on Github"
22-
>
23-
<img alt="See on Github" src={isMobile ? '/images/UI/image_5.webp' : '/images/UI/image_2.webp'} />
60+
<div className="source-buttons">
61+
{buttons.map((btn) => (
62+
<a key={btn.alt} href={btn.href} target="_blank" rel="noreferrer" title={btn.alt}>
63+
<img alt={btn.alt} src={btn.img} />
64+
</a>
65+
))}
66+
</div>
67+
);
68+
}
69+
70+
function BottomLinks({ downloadUrl, isDark }) {
71+
const folder = isDark ? 'DARK' : 'LIGHT';
72+
73+
return (
74+
<div className="bottom-links">
75+
<a href={downloadUrl} target="_blank" rel="noreferrer" title=".ipa Download">
76+
<img alt=".ipa Download" src={`${BASE}images/buttons/${folder}/Download.png`} />
2477
</a>
25-
<a
26-
className="app__button-download"
27-
href={downloadUrl}
28-
target="_blank"
29-
rel="noreferrer"
30-
title=".ipa download"
31-
>
32-
<img alt=".ipa download" src="/images/UI/image_3.webp" />
78+
<a href="https://github.com/Balackburn/Apollo" target="_blank" rel="noreferrer" title="GitHub">
79+
<img alt="GitHub" src={`${BASE}images/buttons/${folder}/Github.png`} />
3380
</a>
3481
</div>
3582
);
@@ -38,6 +85,7 @@ function ActionButtons({ downloadUrl, isMobile }) {
3885
export default function Header() {
3986
const iconSrc = useRandomIcon();
4087
const downloadUrl = useDownloadLink();
88+
const { isDark } = useTheme();
4189

4290
return (
4391
<>
@@ -47,11 +95,20 @@ export default function Header() {
4795
</div>
4896
<div className="app__infos">
4997
<h1 className="app__name">Apollo for Reddit</h1>
50-
<p className="app__description"> The award-winning Reddit app !</p>
51-
<ActionButtons downloadUrl={downloadUrl} isMobile={false} />
98+
<p className="app__description">The award-winning Reddit app !</p>
5299
</div>
53100
</header>
54-
<ActionButtons downloadUrl={downloadUrl} isMobile={true} />
101+
102+
<section className="app__sources container">
103+
{SOURCES.map((source) => (
104+
<div key={source.title} className="source-section">
105+
<h2 className="source-title">{source.title}</h2>
106+
<p className="source-desc">{source.description}</p>
107+
<SourceButtons jsonFile={source.json} isDark={isDark} />
108+
</div>
109+
))}
110+
<BottomLinks downloadUrl={downloadUrl} isDark={isDark} />
111+
</section>
55112
</>
56113
);
57114
}

src/components/Screenshots.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { useEffect, useRef } from 'react';
22

3+
const BASE = import.meta.env.BASE_URL;
4+
35
const SCREENSHOT_IMAGES = [
4-
'/images/image_1.webp',
5-
'/images/image_2.webp',
6-
'/images/image_3.webp',
7-
'/images/image_4.webp',
8-
'/images/image_5.webp',
9-
'/images/image_6.webp',
10-
'/images/image_7.webp',
11-
'/images/image_8.webp',
6+
`${BASE}images/image_1.webp`,
7+
`${BASE}images/image_2.webp`,
8+
`${BASE}images/image_3.webp`,
9+
`${BASE}images/image_4.webp`,
10+
`${BASE}images/image_5.webp`,
11+
`${BASE}images/image_6.webp`,
12+
`${BASE}images/image_7.webp`,
13+
`${BASE}images/image_8.webp`,
1214
];
1315

1416
export default function Screenshots() {

src/components/ThemeToggle.jsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useTheme } from '../hooks/useTheme';
2+
3+
export default function ThemeToggle() {
4+
const { isDark, toggleTheme } = useTheme();
5+
6+
return (
7+
<button
8+
onClick={toggleTheme}
9+
className="theme-toggle"
10+
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
11+
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
12+
>
13+
{isDark ? (
14+
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
15+
<circle cx="12" cy="12" r="5"/>
16+
<line x1="12" y1="1" x2="12" y2="3"/>
17+
<line x1="12" y1="21" x2="12" y2="23"/>
18+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
19+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
20+
<line x1="1" y1="12" x2="3" y2="12"/>
21+
<line x1="21" y1="12" x2="23" y2="12"/>
22+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
23+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
24+
</svg>
25+
) : (
26+
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
27+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
28+
</svg>
29+
)}
30+
</button>
31+
);
32+
}

src/hooks/useRandomIcon.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useState, useEffect } from 'react';
22

33
const TOTAL_ICONS = 156;
4+
const BASE = import.meta.env.BASE_URL;
45

56
function getRandomIcon() {
67
const randomIndex = Math.floor(Math.random() * TOTAL_ICONS);
7-
return `/apollo_icons/icon_${randomIndex}.webp`;
8+
return `${BASE}apollo_icons/icon_${randomIndex}.webp`;
89
}
910

1011
export default function useRandomIcon() {

src/hooks/useTheme.jsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createContext, useContext, useState, useEffect } from 'react';
2+
3+
const ThemeContext = createContext();
4+
5+
export function ThemeProvider({ children }) {
6+
const [isDark, setIsDark] = useState(() => {
7+
if (typeof window !== 'undefined') {
8+
const stored = localStorage.getItem('theme');
9+
if (stored) return stored === 'dark';
10+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
11+
}
12+
return true;
13+
});
14+
15+
useEffect(() => {
16+
const root = document.documentElement;
17+
if (isDark) {
18+
root.classList.add('dark');
19+
} else {
20+
root.classList.remove('dark');
21+
}
22+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
23+
}, [isDark]);
24+
25+
const toggleTheme = () => setIsDark((prev) => !prev);
26+
27+
return (
28+
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
29+
{children}
30+
</ThemeContext.Provider>
31+
);
32+
}
33+
34+
// eslint-disable-next-line react-refresh/only-export-components
35+
export function useTheme() {
36+
const context = useContext(ThemeContext);
37+
if (!context) {
38+
throw new Error('useTheme must be used within a ThemeProvider');
39+
}
40+
return context;
41+
}

0 commit comments

Comments
 (0)