1+ ---
2+ import Layout from ' ./Layout.astro' ;
3+ import { apps , type AppInfo } from " @/lib/apps" ;
4+
5+ export async function getStaticPaths() {
6+ // the keys of the Record<string, AppInfo> are the app tokens
7+ return Object .keys (apps ).map (tok => ({ params: { token: tok } }));
8+ }
9+
10+ const { token } = Astro .params ;
11+ if (! token ) {
12+ throw new Error (` No token parameter ` );
13+ }
14+
15+ const tok: string = token ;
16+ const app: AppInfo = apps [tok ];
17+ // console.log(`app: ${app}`);
18+
19+ if (! app ) {
20+ throw new Error (` App with token "${token }" not found ` );
21+ }
22+
23+ const tokenLower = token .toLowerCase ();
24+
25+ // console.log(`app.ios: ${app.ios}`);
26+
27+ const iosInfo = app .ios ?.appInfo ;
28+ console .log (` iosInfo: ${iosInfo } ` );
29+
30+ // const fdroidInfo = app.fdroid?.appInfo; // TODO
31+
32+ const appStoreLink = app .ios ?.appleItemId ? ` https://apps.apple.com/app/${tokenLower }/id${app .ios ?.appleItemId } ` : null ;
33+ const playStoreLink = app .android ?.appid ? ` https://play.google.com/store/apps/details?id=${app .android ?.appid } ` : null ;
34+
35+ const appName = iosInfo ?.name ?? app .token ;
36+ const appSubtitle = iosInfo ?.subtitle ?? " " ;
37+ const appDescription = iosInfo ?.localizedDescription ?? " " ;
38+ const appIcon = iosInfo ?.iconURL ?? " " ;
39+
40+ const sourceCodeURL = ` https://github.com/${token }/${token } ` ;
41+ const privacyPolicyURL = " https://appfair.org/privacy" ;
42+ ---
43+ <Layout title ={ appName } >
44+ <main class =" app-detail" >
45+ <div class =" app-container" >
46+ <!-- Header -->
47+ <div class =" app-header" >
48+ <div class =" app-header-content" >
49+ <div class =" app-icon-large" >
50+ <img src ={ appIcon } alt ={ ` ${appName } icon ` } />
51+ </div >
52+ <div class =" app-header-info force-wrap" >
53+ <h1 >{ appName } </h1 >
54+ <p class =" subtitle" >{ appSubtitle } </p >
55+ <!-- <div class="developer">{app.developer}</div> -->
56+ </div >
57+ </div >
58+
59+ <!-- <div class="app-stats-bar">
60+ <div class="stat">
61+ <div class="stat-value">{app.rating}</div>
62+ <div class="stat-label">{app.reviews} Ratings</div>
63+ </div>
64+ <div class="stat-divider"></div>
65+ <div class="stat">
66+ <div class="stat-value">{app.age}</div>
67+ <div class="stat-label">Age</div>
68+ </div>
69+ <div class="stat-divider"></div>
70+ <div class="stat">
71+ <div class="stat-value">{app.size}</div>
72+ <div class="stat-label">Size</div>
73+ </div>
74+ </div> -->
75+
76+ <div class =" download-section" >
77+ { appStoreLink && (
78+ <a href = { appStoreLink } class = " btn-download btn-ios" ><img src = " https://appfair.org/assets/badges/apple-app-store.svg" alt = " Download on the Apple App Store" /></a >
79+ )}
80+ { playStoreLink && (
81+ <a href = { playStoreLink } class = " btn-download btn-android" ><img src = " https://appfair.org/assets/badges/google-play-store.svg" alt = " Download on the Google Play Store" /></a >
82+ )}
83+ </div >
84+ </div >
85+
86+ <!-- Screenshots -->
87+ <section class =" screenshots-section" >
88+ <h2 >Screenshots</h2 >
89+ <div class =" screenshots-scroll" >
90+ { iosInfo ?.screenshots ?.iphone ?.map ((screenshot , index ) => (
91+ <div class = " screenshot-item" >
92+ <img src = { screenshot } alt = { ` Screenshot ${index + 1 } ` } />
93+ </div >
94+ ))}
95+ </div >
96+ </section >
97+
98+ <!-- Description -->
99+ <section class =" description-section" >
100+ <h2 >About This App</h2 >
101+ <pre >{ appDescription } </pre >
102+ </section >
103+
104+ <!-- Permissions -->
105+ <section class =" permissions-section force-wrap" >
106+ <h2 >Permissions</h2 >
107+ <div class =" permissions-grid" >
108+ { iosInfo ?.appPermissions ?.privacy ?.map ((permission ) => (
109+ <div class = " permission-card" >
110+ <div class = " permission-icon" >
111+ <svg width = " 24" height = " 24" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" >
112+ <circle cx = " 12" cy = " 12" r = " 10" ></circle >
113+ <path d = " M12 6v6l4 2" ></path >
114+ </svg >
115+ </div >
116+ <div class = " permission-content" >
117+ <h3 >{ permission .name } </h3 >
118+ <p >{ permission .usageDescription } </p >
119+ </div >
120+ </div >
121+ )) || <p >None</p >}
122+ </div >
123+ <h2 >Entitlements</h2 >
124+ <div class =" permissions-grid" >
125+ { iosInfo ?.appPermissions ?.entitlements ?.map ((entitlement ) => (
126+ <div class = " permission-card" >
127+ <div class = " permission-icon" >
128+ <svg width = " 24" height = " 24" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" >
129+ <circle cx = " 12" cy = " 12" r = " 10" ></circle >
130+ <path d = " M12 6v6l4 2" ></path >
131+ </svg >
132+ </div >
133+ <div class = " permission-content" >
134+ <h3 >{ entitlement .name } </h3 >
135+ </div >
136+ </div >
137+ )) || <p >None</p >}
138+ </div >
139+ </section >
140+
141+ <!-- Links -->
142+ <section class =" links-section force-wrap" >
143+ <h2 >Developer Resources</h2 >
144+ <div class =" links-grid" >
145+ <a href ={ sourceCodeURL } class =" link-card" target =" _blank" rel =" noopener" >
146+ <svg width =" 24" height =" 24" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
147+ <polyline points =" 16 18 22 12 16 6" ></polyline >
148+ <polyline points =" 8 6 2 12 8 18" ></polyline >
149+ </svg >
150+ <div >
151+ <h3 >Source Code</h3 >
152+ <p >View on GitHub</p >
153+ </div >
154+ </a >
155+ <a href ={ privacyPolicyURL } class =" link-card" target =" _blank" rel =" noopener" >
156+ <svg width =" 24" height =" 24" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
157+ <rect x =" 3" y =" 11" width =" 18" height =" 11" rx =" 2" ry =" 2" ></rect >
158+ <path d =" M7 11V7a5 5 0 0 1 10 0v4" ></path >
159+ </svg >
160+ <div >
161+ <h3 >Privacy Policy</h3 >
162+ <p >Learn about data protection</p >
163+ </div >
164+ </a >
165+ </div >
166+ </section >
167+ </div >
168+ </main >
169+ </Layout >
0 commit comments