pramudya-yb commited on
Commit
fe5ae84
·
1 Parent(s): cbfc761

Minimalize landing page and remove rogasper components

Browse files
README.md CHANGED
@@ -1,4 +1,4 @@
1
- ---
2
  title: Pram API
3
  emoji: 🚀
4
  colorFrom: green
@@ -278,12 +278,6 @@ Contributions are welcome! Before opening a PR:
278
 
279
  For AI-related changes, see the **AI Generation Context** section in `AGENTS.md` — adding a new question format touches schemas, prompts, attempt normalization, and frontend constants.
280
 
281
- ## Support
282
-
283
- If you find Pram helpful and want to support its ongoing development, consider buying the creator a coffee! Your support helps cover API and hosting costs. ☕
284
-
285
- - **[Saweria](https://saweria.co/pram)** (Indonesian supporters)
286
- - **[Ko-fi](https://ko-fi.com/pram)** (International)
287
 
288
  ## License
289
 
 
1
+ ---
2
  title: Pram API
3
  emoji: 🚀
4
  colorFrom: green
 
278
 
279
  For AI-related changes, see the **AI Generation Context** section in `AGENTS.md` — adding a new question format touches schemas, prompts, attempt normalization, and frontend constants.
280
 
 
 
 
 
 
 
281
 
282
  ## License
283
 
apps/web/src/components/CommunityChannelList.tsx DELETED
@@ -1,90 +0,0 @@
1
- import { MaterialIcon } from "@/components/ui/MaterialIcon";
2
- import {
3
- COMMUNITY_PLATFORM_META,
4
- getCommunitySections,
5
- type CommunityChannel,
6
- type CommunityPlatform,
7
- } from "@/lib/community-links";
8
-
9
- function ChannelRow({
10
- channel,
11
- platform,
12
- variant,
13
- }: {
14
- channel: CommunityChannel;
15
- platform: CommunityPlatform;
16
- variant: "modal" | "card";
17
- }) {
18
- const meta = COMMUNITY_PLATFORM_META[platform];
19
- const baseClass =
20
- variant === "modal"
21
- ? `w-full flex items-start gap-3 py-3.5 px-4 rounded-[var(--radius-lg)] font-semibold transition-all text-[var(--clay-black)] border-2 clay-hover ${meta?.linkClass ?? ""}`
22
- : `flex items-start gap-3 rounded-[var(--radius-lg)] border px-4 py-3 transition-colors ${meta?.linkClass ?? "border-[var(--oat-border)] bg-[var(--warm-cream)]/60"}`;
23
-
24
- const iconClass = meta?.iconClass ?? "text-[var(--matcha-700)]";
25
-
26
- if (!channel.href) {
27
- return (
28
- <div
29
- className={`${baseClass} opacity-70 cursor-not-allowed border-dashed`}
30
- aria-disabled="true"
31
- >
32
- <MaterialIcon name={channel.icon} className={`text-2xl shrink-0 mt-0.5 ${iconClass}`} />
33
- <span className="text-left flex-1">
34
- <span className="flex items-center gap-2 flex-wrap">
35
- <span className="block font-bold">{channel.name}</span>
36
- <span className="text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded-full bg-[var(--oat-light)] text-[var(--warm-charcoal)]">
37
- Segera hadir
38
- </span>
39
- </span>
40
- <span className="block text-sm font-normal text-[var(--warm-charcoal)] mt-0.5">
41
- {channel.description}
42
- </span>
43
- </span>
44
- </div>
45
- );
46
- }
47
-
48
- return (
49
- <a
50
- href={channel.href}
51
- target="_blank"
52
- rel="noopener noreferrer"
53
- className={baseClass}
54
- >
55
- <MaterialIcon name={channel.icon} className={`text-2xl shrink-0 mt-0.5 ${iconClass}`} />
56
- <span className="text-left">
57
- <span className={`block font-bold ${variant === "card" ? "text-sm" : ""}`}>
58
- {channel.name}
59
- </span>
60
- <span
61
- className={`block font-normal text-[var(--warm-charcoal)] mt-0.5 ${variant === "card" ? "text-xs" : "text-sm"}`}
62
- >
63
- {channel.description}
64
- </span>
65
- </span>
66
- </a>
67
- );
68
- }
69
-
70
- export function CommunityChannelList({ variant }: { variant: "modal" | "card" }) {
71
- const sections = getCommunitySections();
72
-
73
- return (
74
- <div className={variant === "modal" ? "flex flex-col gap-5" : "space-y-5"}>
75
- {sections.map(({ platform, meta, channels }) => (
76
- <section key={platform} className="space-y-2">
77
- <h3 className="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-[var(--warm-charcoal)]">
78
- <MaterialIcon name={meta.icon} className={`text-base ${meta.iconClass}`} />
79
- {meta.label}
80
- </h3>
81
- <div className={variant === "modal" ? "flex flex-col gap-2" : "space-y-2"}>
82
- {channels.map((channel) => (
83
- <ChannelRow key={channel.id} channel={channel} platform={platform} variant={variant} />
84
- ))}
85
- </div>
86
- </section>
87
- ))}
88
- </div>
89
- );
90
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/web/src/components/CommunityModal.tsx DELETED
@@ -1,39 +0,0 @@
1
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@pram/ui/components/dialog";
2
- import { MaterialIcon } from "@/components/ui/MaterialIcon";
3
- import { CommunityChannelList } from "@/components/CommunityChannelList";
4
-
5
- export function CommunityModal({
6
- isOpen,
7
- onOpenChange,
8
- }: {
9
- isOpen: boolean;
10
- onOpenChange: (open: boolean) => void;
11
- }) {
12
- return (
13
- <Dialog open={isOpen} onOpenChange={onOpenChange}>
14
- <DialogContent className="sm:max-w-[440px] p-6 rounded-[var(--radius-xl)] bg-[var(--pure-white)] border-2 border-[var(--oat-border)]">
15
- <DialogHeader>
16
- <div className="mx-auto w-16 h-16 bg-[var(--matcha-300)]/30 rounded-full flex items-center justify-center mb-2">
17
- <MaterialIcon name="forum" className="text-4xl text-[var(--matcha-800)]" />
18
- </div>
19
- <DialogTitle className="text-center text-2xl font-headline font-extrabold text-[var(--clay-black)]">
20
- Gabung Komunitas Pram
21
- </DialogTitle>
22
- <DialogDescription className="text-center text-base text-[var(--warm-charcoal)] pt-2 leading-relaxed">
23
- Belajar bahasa, lapor bug, atau ikut ngembangin Pram — pilih channel yang paling pas.
24
- </DialogDescription>
25
- </DialogHeader>
26
- <div className="mt-6">
27
- <CommunityChannelList variant="modal" />
28
- <button
29
- type="button"
30
- onClick={() => onOpenChange(false)}
31
- className="mt-5 w-full text-sm text-[var(--warm-silver)] hover:text-[var(--clay-black)] font-semibold transition-colors"
32
- >
33
- Nanti Saja
34
- </button>
35
- </div>
36
- </DialogContent>
37
- </Dialog>
38
- );
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/web/src/components/DonationModal.tsx DELETED
@@ -1,59 +0,0 @@
1
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@pram/ui/components/dialog";
2
- import { MaterialIcon } from "@/components/ui/MaterialIcon";
3
-
4
- export function DonationModal({
5
- isOpen,
6
- onOpenChange,
7
- triggerAction
8
- }: {
9
- isOpen: boolean;
10
- onOpenChange: (open: boolean) => void;
11
- triggerAction: "exam" | "generate" | null;
12
- }) {
13
- return (
14
- <Dialog open={isOpen} onOpenChange={onOpenChange}>
15
- <DialogContent className="sm:max-w-[425px] p-6 rounded-[var(--radius-xl)] bg-[var(--pure-white)] border-2 border-[var(--oat-border)]">
16
- <DialogHeader>
17
- <div className="mx-auto w-16 h-16 bg-[var(--pomegranate-400)]/10 rounded-full flex items-center justify-center mb-2">
18
- <MaterialIcon name="favorite" className="text-4xl text-[var(--pomegranate-400)]" />
19
- </div>
20
- <DialogTitle className="text-center text-2xl font-headline font-extrabold text-[var(--clay-black)]">
21
- Dukung Pram!
22
- </DialogTitle>
23
- <DialogDescription className="text-center text-base text-[var(--warm-charcoal)] pt-2 leading-relaxed">
24
- Selamat, kamu baru saja menyelesaikan {triggerAction === "exam" ? "latihan soal" : "generate soal AI"}! 🎉
25
- <br/><br/>
26
- Jika Pram membantumu, dukung kami untuk menutupi biaya server & mengembangkan fitur baru dengan mentraktir kopi. ☕
27
- </DialogDescription>
28
- </DialogHeader>
29
- <div className="flex flex-col gap-3 mt-6">
30
- <a
31
- href="https://saweria.co/pram"
32
- target="_blank"
33
- rel="noopener noreferrer"
34
- className="w-full flex items-center justify-center gap-2 py-3.5 px-4 rounded-[var(--radius-lg)] font-bold transition-all bg-[#F3B63A] text-black hover:brightness-95 clay-shadow clay-hover"
35
- >
36
- <MaterialIcon name="favorite" className="text-xl" />
37
- Support via Saweria
38
- </a>
39
- <a
40
- href="https://ko-fi.com/pram"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- className="w-full flex items-center justify-center gap-2 py-3.5 px-4 rounded-[var(--radius-lg)] font-bold transition-all bg-[#13C3FF] text-white hover:brightness-95 clay-shadow clay-hover"
44
- >
45
- <MaterialIcon name="local_cafe" className="text-xl" />
46
- Support via Ko-fi
47
- </a>
48
- <button
49
- onClick={() => onOpenChange(false)}
50
- className="mt-3 text-sm text-[var(--warm-silver)] hover:text-[var(--clay-black)] font-semibold transition-colors"
51
- >
52
- Mungkin Nanti Saja
53
- </button>
54
- </div>
55
- </DialogContent>
56
- </Dialog>
57
- );
58
- }
59
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/web/src/components/routes/LandingPage.tsx CHANGED
@@ -2,47 +2,13 @@ import { Link } from "@tanstack/react-router";
2
  import { Button } from "@pram/ui/components/button";
3
  import { Card, CardContent } from "@pram/ui/components/card";
4
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
5
- import { useState } from "react";
6
- import { CommunityModal } from "@/components/CommunityModal";
7
 
8
- function FaqItem({ question, answer, isDark }: { question: string; answer: string; isDark?: boolean }) {
9
- const [isOpen, setIsOpen] = useState(false);
10
 
11
- return (
12
- <div className={`border-b-2 py-6 ${isDark ? 'border-[var(--ube-300)]/30' : 'border-[var(--oat-border)]'} last:border-b-0`}>
13
- <button
14
- type="button"
15
- className="flex w-full items-center justify-between text-left focus:outline-none group"
16
- onClick={() => setIsOpen(!isOpen)}
17
- aria-expanded={isOpen}
18
- >
19
- <span className={`font-headline font-semibold text-2xl tracking-[-0.64px] transition-colors ${isDark ? 'text-[var(--pure-white)] group-hover:text-[var(--ube-300)]' : 'text-[var(--clay-black)] group-hover:text-[var(--matcha-700)]'}`}>
20
- {question}
21
- </span>
22
- <div className={`w-10 h-10 rounded-full border-2 flex items-center justify-center transition-transform duration-300 ${isOpen ? "rotate-180" : ""} ${isDark ? 'border-[var(--ube-300)]/50 text-[var(--pure-white)] group-hover:bg-[var(--ube-300)]/20' : 'border-[var(--oat-border)] text-[var(--clay-black)] group-hover:bg-[var(--oat-light)]'}`}>
23
- <MaterialIcon
24
- name="expand_more"
25
- className="text-2xl"
26
- />
27
- </div>
28
- </button>
29
- <div
30
- className={`overflow-hidden transition-all duration-300 ease-in-out ${isOpen ? "max-h-96 mt-4 opacity-100" : "max-h-0 opacity-0"}`}
31
- >
32
- <p className={`text-lg leading-relaxed pr-12 ${isDark ? 'text-[var(--ube-300)]' : 'text-[var(--warm-charcoal)]'}`}>
33
- {answer}
34
- </p>
35
- </div>
36
- </div>
37
- );
38
- }
39
 
40
- export function LandingPage() {
41
- const [communityModalOpen, setCommunityModalOpen] = useState(false);
42
 
 
43
  return (
44
  <div className="min-h-screen bg-[var(--warm-cream)] flex flex-col font-sans selection:bg-[var(--matcha-300)] selection:text-[var(--clay-black)]">
45
- <CommunityModal isOpen={communityModalOpen} onOpenChange={setCommunityModalOpen} />
46
  {/* Navbar */}
47
  <nav className="w-full px-6 py-4 md:px-12 lg:px-16 flex items-center justify-between max-w-7xl mx-auto z-50 sticky top-0 bg-[var(--warm-cream)] border-b-2 border-[var(--oat-border)]">
48
  <div className="flex items-center gap-3">
@@ -50,16 +16,7 @@ export function LandingPage() {
50
  <span className="font-headline font-semibold text-2xl tracking-[-0.64px] text-[var(--clay-black)] hidden sm:block">Pram</span>
51
  </div>
52
  <div className="flex items-center gap-2 sm:gap-4">
53
- <button
54
- type="button"
55
- onClick={() => setCommunityModalOpen(true)}
56
- className="text-[var(--warm-charcoal)] hover:text-[var(--matcha-700)] transition-colors p-2 hidden md:flex items-center gap-2"
57
- aria-label="Komunitas WhatsApp"
58
- >
59
- <MaterialIcon name="forum" className="text-xl" />
60
- <span className="font-semibold text-base">Komunitas</span>
61
- </button>
62
- <div className="w-px h-6 bg-[var(--oat-border)] hidden sm:block mx-2"></div>
63
  <Link to="/login">
64
  <Button variant="ghost" className="text-[var(--clay-black)] font-semibold hover:bg-[var(--oat-light)] rounded-[12px] text-lg px-4 sm:px-6 h-12">
65
  Masuk
@@ -73,9 +30,9 @@ export function LandingPage() {
73
  </div>
74
  </nav>
75
 
76
- <main className="flex-1 flex flex-col items-center overflow-x-hidden">
77
  {/* HUGE HERO SECTION */}
78
- <section className="w-full px-6 md:px-12 lg:px-16 pt-16 pb-20 md:pt-24 md:pb-32 flex flex-col xl:flex-row items-center justify-between gap-12 lg:gap-16 relative max-w-[1440px] mx-auto">
79
  <div className="flex-1 text-center xl:text-left space-y-8 z-10 w-full max-w-[800px] xl:max-w-none">
80
  <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[var(--matcha-300)] border-2 border-[var(--matcha-800)] text-[var(--clay-black)] uppercase-label shadow-sm mx-auto xl:mx-0 transform -rotate-2">
81
  <MaterialIcon name="auto_awesome" className="text-sm" />
@@ -123,177 +80,7 @@ export function LandingPage() {
123
 
124
 
125
 
126
- {/* Feature Cards Section */}
127
- <section id="features" className="w-full bg-[var(--pure-white)] py-32 border-y-2 border-dashed border-[var(--oat-border)]">
128
- <div className="max-w-7xl mx-auto px-6 md:px-12 lg:px-16">
129
- <div className="text-center max-w-4xl mx-auto mb-24 space-y-6">
130
- <h2 className="text-[50px] md:text-[60px] font-headline font-semibold text-[var(--clay-black)] tracking-[-2.4px] leading-tight">
131
- Fitur Unggulan Pram
132
- </h2>
133
- <p className="text-2xl text-[var(--warm-charcoal)]">
134
- Desain yang elegan, namun kokoh untuk mempercepat kesiapan Anda menghadapi ujian.
135
- </p>
136
- </div>
137
-
138
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
139
- {/* Feature 1 */}
140
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
141
- <div className="h-56 w-full flex items-center justify-center p-2">
142
- <img src="/generateai.png" alt="AI Generator" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500 drop-shadow-lg" />
143
- </div>
144
- <CardContent className="p-6">
145
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
146
- AI Generator
147
- </h3>
148
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
149
- Hasilkan soal latihan baru menggunakan AI. Tentukan topik, level kesulitan, dan format dalam hitungan detik.
150
- </p>
151
- </CardContent>
152
- </Card>
153
-
154
- {/* Feature 2 */}
155
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
156
- <div className="h-56 w-full flex items-center justify-center p-2">
157
- <img src="/latihansoal.png" alt="Latihan Soal" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:-rotate-3 transition-transform duration-500 drop-shadow-lg" />
158
- </div>
159
- <CardContent className="p-6">
160
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
161
- Latihan Terfokus
162
- </h3>
163
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
164
- Akses ribuan soal latihan dari bank soal. Buat paket latihan Anda sendiri dan fokus pada area kelemahan.
165
- </p>
166
- </CardContent>
167
- </Card>
168
-
169
- {/* Feature 3 */}
170
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
171
- <div className="h-56 w-full flex items-center justify-center p-2">
172
- <img src="/mocktest.png" alt="Mock Test" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500 drop-shadow-lg" />
173
- </div>
174
- <CardContent className="p-6">
175
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
176
- Simulasi Ujian
177
- </h3>
178
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
179
- Simulasikan suasana ujian sesungguhnya dengan batas waktu, antarmuka imersif, dan penilaian instan.
180
- </p>
181
- </CardContent>
182
- </Card>
183
-
184
- {/* Feature 4 */}
185
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
186
- <div className="h-56 w-full flex items-center justify-center p-2">
187
- <img src="/progress.png" alt="Progress Tracking" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:-rotate-3 transition-transform duration-500 drop-shadow-lg" />
188
- </div>
189
- <CardContent className="p-6">
190
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
191
- Analitik Progres
192
- </h3>
193
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
194
- Lacak perkembangan nilai Anda dari waktu ke waktu. Analisis mendalam untuk setiap bagian tes bahasa.
195
- </p>
196
- </CardContent>
197
- </Card>
198
-
199
- {/* Feature 5 */}
200
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
201
- <div className="h-56 w-full flex items-center justify-center p-2">
202
- <img src="/vocabulary.png" alt="Vocabulary" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:rotate-3 transition-transform duration-500 drop-shadow-lg" />
203
- </div>
204
- <CardContent className="p-6">
205
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
206
- Kosakata
207
- </h3>
208
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
209
- Perkaya kosakata Anda dengan metode cerdas dan pengulangan berkala yang dioptimalkan.
210
- </p>
211
- </CardContent>
212
- </Card>
213
-
214
- {/* Feature 6 */}
215
- <Card className="clay-shadow clay-hover bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[24px] overflow-hidden group p-4">
216
- <div className="h-56 w-full flex items-center justify-center p-2">
217
- <img src="/diskusi.png" alt="Diskusi" className="h-full w-auto object-contain transform group-hover:scale-110 group-hover:-rotate-3 transition-transform duration-500 drop-shadow-lg" />
218
- </div>
219
- <CardContent className="p-6">
220
- <h3 className="font-headline text-[32px] font-semibold text-[var(--clay-black)] tracking-[-0.64px] mb-3 leading-tight">
221
- Forum Diskusi
222
- </h3>
223
- <p className="text-[var(--warm-charcoal)] text-lg leading-relaxed">
224
- Diskusikan soal-soal sulit dengan komunitas pembelajar lainnya dan dapatkan penjelasan ahli.
225
- </p>
226
- </CardContent>
227
- </Card>
228
- </div>
229
- </div>
230
- </section>
231
-
232
-
233
-
234
- {/* SWATCH ROOM 1: Ube FAQ Section */}
235
- <section className="w-full bg-[var(--ube-800)] py-32 rounded-t-[40px] mt-[-40px] z-10 relative shadow-[0_-10px_40px_rgba(0,0,0,0.1)] border-t-2 border-[var(--oat-border)]/20">
236
- <div className="max-w-4xl mx-auto px-6 md:px-12 lg:px-16">
237
- <div className="text-center mb-16">
238
- <h2 className="text-[50px] md:text-[60px] font-headline font-semibold text-[var(--pure-white)] tracking-[-2.4px] mb-6 leading-tight drop-shadow-sm">
239
- Pertanyaan Umum
240
- </h2>
241
- <p className="text-[var(--ube-300)] text-2xl max-w-2xl mx-auto">
242
- Temukan jawaban cepat untuk pertanyaan seputar Pram.
243
- </p>
244
- </div>
245
-
246
- <div className="bg-[var(--ube-900)]/40 backdrop-blur-sm border-2 border-[var(--ube-300)]/30 rounded-[32px] p-8 md:p-12 shadow-2xl">
247
- <FaqItem
248
- isDark
249
- question="Apa itu Pram?"
250
- answer="Pram adalah platform latihan ujian bahasa berbasis AI yang dirancang untuk membantu Anda berlatih dan menguasai bahasa asing melalui simulasi, bank soal interaktif, dan analitik performa."
251
- />
252
- <FaqItem
253
- isDark
254
- question="Bagaimana cara kerja AI Generator?"
255
- answer="Fitur AI Generator memungkinkan Anda membuat paket soal baru berdasarkan konteks atau topik tertentu. Anda cukup memasukkan teks acuan, dan AI Agent kami akan memproduksi soal secara otomatis."
256
- />
257
- <FaqItem
258
- isDark
259
- question="Apakah Pram sepenuhnya gratis?"
260
- answer="Platform Pram dapat digunakan secara gratis untuk fitur dasar. Untuk fitur generasi soal berbasis AI, kami menggunakan model Bring-Your-Own-Key (BYOK). Anda cukup memasukkan API Key OpenAI Anda."
261
- />
262
- <FaqItem
263
- isDark
264
- question="Bahasa apa saja yang didukung oleh Pram?"
265
- answer="Saat ini Pram mendukung latihan untuk berbagai ujian profisiensi bahasa populer seperti Bahasa Inggris (TOEFL, IELTS, TOEIC), Jepang (JLPT), Korea (TOPIK), dan banyak lagi."
266
- />
267
- </div>
268
- </div>
269
- </section>
270
-
271
- {/* SWATCH ROOM 2: Matcha CTA Section */}
272
- <section className="w-full bg-[var(--matcha-800)] py-40 px-6 rounded-t-[40px] mt-[-40px] z-20 relative shadow-[0_-10px_40px_rgba(0,0,0,0.2)] border-t-2 border-[var(--matcha-600)]">
273
- <div className="max-w-4xl mx-auto text-center space-y-12">
274
- <h2 className="text-[60px] md:text-[80px] font-headline font-semibold text-[var(--pure-white)] tracking-[-3.2px] leading-[0.95] drop-shadow-md">
275
- Siap Meningkatkan Skor Anda?
276
- </h2>
277
- <p className="text-2xl text-[var(--matcha-300)] max-w-2xl mx-auto">
278
- Bergabung sekarang dan rasakan perbedaan belajar dengan teknologi yang berpusat pada perkembangan Anda.
279
- </p>
280
- <div className="pt-8">
281
- <Link to="/login" className="inline-block">
282
- <Button className="bg-[var(--pure-white)] text-[var(--clay-black)] hover:bg-[var(--oat-light)] rounded-[24px] h-[80px] px-14 text-2xl font-bold clay-shadow clay-hover">
283
- Daftar Sekarang - Gratis
284
- </Button>
285
- </Link>
286
- </div>
287
- </div>
288
- </section>
289
- </main>
290
 
291
- {/* Footer */}
292
- <footer className="w-full bg-[var(--pure-white)] py-12 border-t-2 border-[var(--oat-border)] text-center relative z-30">
293
- <p className="text-[var(--warm-charcoal)] font-semibold text-lg">
294
- &copy; {new Date().getFullYear()} Pram. Didesain dengan penuh kehangatan.
295
- </p>
296
- </footer>
297
  </div>
298
  );
299
  }
 
2
  import { Button } from "@pram/ui/components/button";
3
  import { Card, CardContent } from "@pram/ui/components/card";
4
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
 
 
5
 
 
 
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
 
 
8
 
9
+ export function LandingPage() {
10
  return (
11
  <div className="min-h-screen bg-[var(--warm-cream)] flex flex-col font-sans selection:bg-[var(--matcha-300)] selection:text-[var(--clay-black)]">
 
12
  {/* Navbar */}
13
  <nav className="w-full px-6 py-4 md:px-12 lg:px-16 flex items-center justify-between max-w-7xl mx-auto z-50 sticky top-0 bg-[var(--warm-cream)] border-b-2 border-[var(--oat-border)]">
14
  <div className="flex items-center gap-3">
 
16
  <span className="font-headline font-semibold text-2xl tracking-[-0.64px] text-[var(--clay-black)] hidden sm:block">Pram</span>
17
  </div>
18
  <div className="flex items-center gap-2 sm:gap-4">
19
+
 
 
 
 
 
 
 
 
 
20
  <Link to="/login">
21
  <Button variant="ghost" className="text-[var(--clay-black)] font-semibold hover:bg-[var(--oat-light)] rounded-[12px] text-lg px-4 sm:px-6 h-12">
22
  Masuk
 
30
  </div>
31
  </nav>
32
 
33
+ <main className="flex-1 flex flex-col items-center justify-center w-full relative">
34
  {/* HUGE HERO SECTION */}
35
+ <section className="w-full px-6 md:px-12 lg:px-16 flex flex-col xl:flex-row items-center justify-between gap-12 lg:gap-16 relative max-w-[1440px] mx-auto">
36
  <div className="flex-1 text-center xl:text-left space-y-8 z-10 w-full max-w-[800px] xl:max-w-none">
37
  <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[var(--matcha-300)] border-2 border-[var(--matcha-800)] text-[var(--clay-black)] uppercase-label shadow-sm mx-auto xl:mx-0 transform -rotate-2">
38
  <MaterialIcon name="auto_awesome" className="text-sm" />
 
80
 
81
 
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
 
 
 
 
 
84
  </div>
85
  );
86
  }
apps/web/src/lib/community-links.ts DELETED
@@ -1,73 +0,0 @@
1
- export type CommunityPlatform = "whatsapp" | "discord";
2
-
3
- export type CommunityChannel = {
4
- id: string;
5
- platform: CommunityPlatform;
6
- name: string;
7
- description: string;
8
- /** null = belum live (tampil sebagai "Segera hadir") */
9
- href: string | null;
10
- icon: string;
11
- };
12
-
13
- export const COMMUNITY_PLATFORM_META: Record<
14
- CommunityPlatform,
15
- { label: string; icon: string; linkClass: string; iconClass: string }
16
- > = {
17
- whatsapp: {
18
- label: "WhatsApp",
19
- icon: "chat",
20
- linkClass:
21
- "bg-[#25D366]/10 border-[#25D366]/30 hover:bg-[#25D366]/20",
22
- iconClass: "text-[#128C7E]",
23
- },
24
- discord: {
25
- label: "Discord",
26
- icon: "forum",
27
- linkClass:
28
- "bg-[#5865F2]/10 border-[#5865F2]/30 hover:bg-[#5865F2]/20",
29
- iconClass: "text-[#5865F2]",
30
- },
31
- };
32
-
33
- /** Satu sumber data — tambah Discord cukup isi `href` di entri bawah. */
34
- export const COMMUNITY_CHANNELS: CommunityChannel[] = [
35
- {
36
- id: "pram-whatsapp",
37
- platform: "whatsapp",
38
- name: "Pram — Open Source",
39
- description: "Laporkan bug, usulkan fitur, atau diskusi pengembangan Pram.",
40
- href: "https://chat.whatsapp.com/JT6bDIXMQ5x6SUQCA1aDWB",
41
- icon: "groups",
42
- },
43
- {
44
- id: "learning-whatsapp",
45
- platform: "whatsapp",
46
- name: "pram Learning",
47
- description: "Berbagi tips, trik, dan berita terbaru dari pram Learning.",
48
- href: "https://chat.whatsapp.com/IrQOUGB3jhR7WnOoilCill",
49
- icon: "school",
50
- },
51
- // Aktifkan saat server Discord siap — cukup isi href:
52
- {
53
- id: "pram-discord",
54
- platform: "discord",
55
- name: "Pram Discord",
56
- description: "Diskusi async, bug report terstruktur, dan pengumuman fitur baru.",
57
- href: null,
58
- icon: "chat",
59
- },
60
- ];
61
-
62
- export const COMMUNITY_PROMPT_STORAGE_KEY = "pram_community_prompt_seen";
63
-
64
- const PLATFORM_ORDER: CommunityPlatform[] = ["whatsapp", "discord"];
65
-
66
- export function getCommunitySections() {
67
- return PLATFORM_ORDER.map((platform) => ({
68
- platform,
69
- meta: COMMUNITY_PLATFORM_META[platform],
70
- channels: COMMUNITY_CHANNELS.filter((c) => c.platform === platform),
71
- })).filter((section) => section.channels.length > 0);
72
- }
73
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/web/src/routes/__root.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Toaster } from "@pram/ui/components/sonner";
2
  import type { QueryClient } from "@tanstack/react-query";
3
  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
4
  import { HeadContent, Outlet, createRootRouteWithContext, useRouterState } from "@tanstack/react-router";
@@ -62,10 +62,7 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
62
  { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" as const },
63
  { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&display=swap" },
64
  ],
65
- scripts: [
66
- ...(import.meta.env.PROD
67
- ? [{ src: "https://umami-analytic.pram-app.vercel.app/script.js", defer: true, "data-website-id": "67a18412-12c8-44ef-9cd3-e04238d37e9a" }]
68
- : []),
69
  {
70
  type: "application/ld+json",
71
  children: JSON.stringify({
 
1
+ import { Toaster } from "@pram/ui/components/sonner";
2
  import type { QueryClient } from "@tanstack/react-query";
3
  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
4
  import { HeadContent, Outlet, createRootRouteWithContext, useRouterState } from "@tanstack/react-router";
 
62
  { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" as const },
63
  { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&display=swap" },
64
  ],
65
+
 
 
 
66
  {
67
  type: "application/ld+json",
68
  children: JSON.stringify({
apps/web/src/routes/dashboard.tsx CHANGED
@@ -8,9 +8,8 @@ import { Button } from "@pram/ui/components/button";
8
  import { Card, CardContent } from "@pram/ui/components/card";
9
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
10
  import { TourGuide } from "@/components/TourGuide";
11
- import { DonationModal } from "@/components/DonationModal";
12
- import { CommunityModal } from "@/components/CommunityModal";
13
- import { COMMUNITY_PROMPT_STORAGE_KEY } from "@/lib/community-links";
14
 
15
  export const Route = createFileRoute("/dashboard")({
16
  staticData: routeShell.app,
@@ -38,39 +37,8 @@ function formatDateShort(dateStr: string | Date | null) {
38
 
39
  function HomeComponent() {
40
  const { session } = Route.useRouteContext();
41
- const [donationModalOpen, setDonationModalOpen] = useState(false);
42
- const [donationTrigger, setDonationTrigger] = useState<"exam" | "generate" | null>(null);
43
- const [communityModalOpen, setCommunityModalOpen] = useState(false);
44
 
45
- const handleCommunityModalChange = (open: boolean) => {
46
- setCommunityModalOpen(open);
47
- if (!open) {
48
- localStorage.setItem(COMMUNITY_PROMPT_STORAGE_KEY, "1");
49
- }
50
- };
51
 
52
- useEffect(() => {
53
- const prompt = localStorage.getItem("pendingDonationPrompt") as "exam" | "generate" | null;
54
- const lastPromptStr = localStorage.getItem("lastDonationPromptTime");
55
- const lastPromptTime = lastPromptStr ? parseInt(lastPromptStr, 10) : 0;
56
- const COOLDOWN = 24 * 60 * 60 * 1000;
57
- const now = Date.now();
58
-
59
- let showedDonation = false;
60
- if (prompt) {
61
- localStorage.removeItem("pendingDonationPrompt");
62
- if (now - lastPromptTime > COOLDOWN) {
63
- setDonationTrigger(prompt);
64
- setDonationModalOpen(true);
65
- localStorage.setItem("lastDonationPromptTime", now.toString());
66
- showedDonation = true;
67
- }
68
- }
69
-
70
- if (!showedDonation && !localStorage.getItem(COMMUNITY_PROMPT_STORAGE_KEY)) {
71
- setCommunityModalOpen(true);
72
- }
73
- }, []);
74
 
75
  const overview = useQuery(trpc.stats.overview.queryOptions());
76
  const recentAttempts = useQuery(
@@ -90,12 +58,8 @@ function HomeComponent() {
90
 
91
  return (
92
  <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-6xl mx-auto bg-[var(--warm-cream)]">
93
- <DonationModal
94
- isOpen={donationModalOpen}
95
- onOpenChange={setDonationModalOpen}
96
- triggerAction={donationTrigger}
97
- />
98
- <CommunityModal isOpen={communityModalOpen} onOpenChange={handleCommunityModalChange} />
99
  {/* Header */}
100
  <section className="mb-8">
101
  <h1 className="text-4xl font-headline font-extrabold text-[var(--clay-black)] tracking-tight">
 
8
  import { Card, CardContent } from "@pram/ui/components/card";
9
  import { MaterialIcon } from "@/components/ui/MaterialIcon";
10
  import { TourGuide } from "@/components/TourGuide";
11
+
12
+
 
13
 
14
  export const Route = createFileRoute("/dashboard")({
15
  staticData: routeShell.app,
 
37
 
38
  function HomeComponent() {
39
  const { session } = Route.useRouteContext();
 
 
 
40
 
 
 
 
 
 
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  const overview = useQuery(trpc.stats.overview.queryOptions());
44
  const recentAttempts = useQuery(
 
58
 
59
  return (
60
  <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-6xl mx-auto bg-[var(--warm-cream)]">
61
+
62
+
 
 
 
 
63
  {/* Header */}
64
  <section className="mb-8">
65
  <h1 className="text-4xl font-headline font-extrabold text-[var(--clay-black)] tracking-tight">
apps/web/vite.config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import tailwindcss from "@tailwindcss/vite";
2
  import { tanstackRouter } from "@tanstack/router-plugin/vite";
3
  import react from "@vitejs/plugin-react";
4
  import { defineConfig } from "vite";
@@ -24,16 +24,7 @@ export default defineConfig({
24
  },
25
  },
26
  plugins: [
27
- {
28
- name: "inject-umami",
29
- transformIndexHtml(html) {
30
- return html.replace(
31
- "</head>",
32
- '<script defer src="https://umami-analytic.pram-app.vercel.app/script.js" data-website-id="67a18412-12c8-44ef-9cd3-e04238d37e9a"></script></head>',
33
- );
34
- },
35
- apply: "build",
36
- },
37
  tailwindcss(),
38
  tanstackRouter({
39
  target: "react",
 
1
+ import tailwindcss from "@tailwindcss/vite";
2
  import { tanstackRouter } from "@tanstack/router-plugin/vite";
3
  import react from "@vitejs/plugin-react";
4
  import { defineConfig } from "vite";
 
24
  },
25
  },
26
  plugins: [
27
+
 
 
 
 
 
 
 
 
 
28
  tailwindcss(),
29
  tanstackRouter({
30
  target: "react",