Skip to content

Commit c4a43e9

Browse files
committed
feat: replace fake paywall with under-development notice and add admin promo offer management
1 parent 35f3a9a commit c4a43e9

6 files changed

Lines changed: 191 additions & 152 deletions

File tree

client/src/pages/admin/free-audit-queue.tsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
1313
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
1414
import { Badge } from "@/components/ui/badge";
1515
import { useToast } from "@/hooks/use-toast";
16+
import { useState, useEffect } from "react";
17+
import { Input } from "@/components/ui/input";
18+
import { Label } from "@/components/ui/label";
19+
import { Calendar, Save } from "lucide-react";
1620

1721
type FreeAuditRequest = {
1822
id: string;
@@ -29,6 +33,14 @@ type FreeAuditsResponse = {
2933
todayCost: number;
3034
};
3135

36+
type PromoOffer = {
37+
id: string;
38+
name: string;
39+
startsAt: string;
40+
endsAt: string;
41+
status: string;
42+
};
43+
3244
export default function AdminFreeAuditQueue() {
3345
const { toast } = useToast();
3446
const queryClient = useQueryClient();
@@ -85,6 +97,61 @@ export default function AdminFreeAuditQueue() {
8597
);
8698
}
8799

100+
return <AdminFreeAuditQueueContent data={data!} approveMutation={approveMutation} rejectMutation={rejectMutation} />;
101+
}
102+
103+
function AdminFreeAuditQueueContent({
104+
data,
105+
approveMutation,
106+
rejectMutation
107+
}: {
108+
data: FreeAuditsResponse,
109+
approveMutation: any,
110+
rejectMutation: any
111+
}) {
112+
const { toast } = useToast();
113+
const queryClient = useQueryClient();
114+
115+
const { data: offer, isLoading: isOfferLoading } = useQuery<PromoOffer | null>({
116+
queryKey: ["/api/admin/promo-offer"],
117+
});
118+
119+
const [startsAt, setStartsAt] = useState("");
120+
const [endsAt, setEndsAt] = useState("");
121+
122+
// Update local state when offer loads
123+
useEffect(() => {
124+
if (offer) {
125+
if (offer.startsAt) setStartsAt(new Date(offer.startsAt).toISOString().slice(0, 16));
126+
if (offer.endsAt) setEndsAt(new Date(offer.endsAt).toISOString().slice(0, 16));
127+
}
128+
}, [offer]);
129+
130+
const updateOfferMutation = useMutation({
131+
mutationFn: async (values: { startsAt: string, endsAt: string }) => {
132+
if (!offer?.id) throw new Error("No active offer found");
133+
const res = await fetch(`/api/admin/promo-offer/${offer.id}`, {
134+
method: "PATCH",
135+
headers: { "Content-Type": "application/json" },
136+
body: JSON.stringify(values),
137+
});
138+
if (!res.ok) throw new Error("Failed to update offer");
139+
return res.json();
140+
},
141+
onSuccess: () => {
142+
toast({ title: "Campaign Updated", description: "The promo offer dates have been updated." });
143+
queryClient.invalidateQueries({ queryKey: ["/api/admin/promo-offer"] });
144+
queryClient.invalidateQueries({ queryKey: ["/api/public/promo-offer"] });
145+
},
146+
onError: (error: any) => {
147+
toast({ title: "Update Failed", description: error.message, variant: "destructive" });
148+
}
149+
});
150+
151+
const handleUpdateOffer = () => {
152+
updateOfferMutation.mutate({ startsAt, endsAt });
153+
};
154+
88155
const requests = data?.requests || [];
89156
const todayCost = data?.todayCost || 0;
90157
const isCeilingReached = todayCost >= 100;
@@ -114,6 +181,59 @@ export default function AdminFreeAuditQueue() {
114181
</div>
115182
)}
116183

184+
{/* Campaign Settings Card */}
185+
<Card>
186+
<CardHeader className="pb-4">
187+
<CardTitle className="text-lg flex items-center gap-2">
188+
<Calendar className="w-5 h-5 text-primary" />
189+
Campaign Settings
190+
</CardTitle>
191+
<CardDescription>
192+
Manage the start and end dates for the active Free Audit promo offer.
193+
</CardDescription>
194+
</CardHeader>
195+
<CardContent>
196+
{isOfferLoading ? (
197+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
198+
<Loader2 className="h-4 w-4 animate-spin" /> Loading campaign data...
199+
</div>
200+
) : offer ? (
201+
<div className="flex flex-col sm:flex-row items-end gap-4 max-w-2xl">
202+
<div className="space-y-1.5 w-full sm:w-auto flex-1">
203+
<Label htmlFor="startsAt" className="text-xs">Start Date & Time</Label>
204+
<Input
205+
id="startsAt"
206+
type="datetime-local"
207+
value={startsAt}
208+
onChange={(e) => setStartsAt(e.target.value)}
209+
className="h-9"
210+
/>
211+
</div>
212+
<div className="space-y-1.5 w-full sm:w-auto flex-1">
213+
<Label htmlFor="endsAt" className="text-xs">End Date & Time</Label>
214+
<Input
215+
id="endsAt"
216+
type="datetime-local"
217+
value={endsAt}
218+
onChange={(e) => setEndsAt(e.target.value)}
219+
className="h-9"
220+
/>
221+
</div>
222+
<Button
223+
onClick={handleUpdateOffer}
224+
disabled={updateOfferMutation.isPending}
225+
className="w-full sm:w-auto h-9"
226+
>
227+
{updateOfferMutation.isPending ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Save className="w-4 h-4 mr-2" />}
228+
Save Changes
229+
</Button>
230+
</div>
231+
) : (
232+
<p className="text-sm text-muted-foreground">No active promo offer found. Please seed the database.</p>
233+
)}
234+
</CardContent>
235+
</Card>
236+
117237
<Card>
118238
<CardHeader>
119239
<CardTitle>Pending Requests</CardTitle>

client/src/pages/pricing.tsx

Lines changed: 17 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -218,162 +218,30 @@ export default function PricingPage() {
218218
<div className="p-5 border-b border-border/60 bg-muted/35">
219219
<DialogTitle className="text-base font-semibold flex items-center gap-2">
220220
<CreditCard className="w-5 h-5 text-primary" />
221-
{step === "success" ? "Subscription Activated" : "Complete Your Subscription"}
221+
CodeGuard is Under Development
222222
</DialogTitle>
223223
<DialogDescription className="text-xs text-muted-foreground mt-1">
224-
{step === "success"
225-
? `Welcome to CodeGuard ${selectedTier.name}!`
226-
: `Authenticate payment details to unlock the ${selectedTier.name} tier.`}
224+
We are not currently accepting real payments or card details.
227225
</DialogDescription>
228226
</div>
229227

230-
{/* Steps rendering */}
231-
{step === "checkout" && (
232-
<div className="p-5 space-y-4">
233-
{/* Order Summary */}
234-
<div className="bg-muted/40 p-3 rounded-lg border border-border/40 space-y-1.5 text-xs">
235-
<div className="flex justify-between font-medium">
236-
<span>{selectedTier.name} Plan ({billingCycle})</span>
237-
<span>${calculatePrice(selectedTier.priceUsd)} / mo</span>
238-
</div>
239-
{promoApplied && (
240-
<div className="flex justify-between text-green-500 font-medium">
241-
<span>Promo Code Applied (SECURE20)</span>
242-
<span>-20%</span>
243-
</div>
244-
)}
245-
<div className="border-t border-border/60 pt-1.5 flex justify-between font-bold text-foreground">
246-
<span>Total Amount:</span>
247-
<span>
248-
${promoApplied
249-
? Math.round(calculatePrice(selectedTier.priceUsd) * 0.8)
250-
: calculatePrice(selectedTier.priceUsd)} / mo
251-
</span>
252-
</div>
253-
</div>
254-
255-
{/* Payment Details Form */}
256-
<div className="space-y-3">
257-
<div className="space-y-1">
258-
<Label htmlFor="card-name" className="text-xs">Cardholder Name</Label>
259-
<Input
260-
id="card-name"
261-
value={name}
262-
onChange={(e) => setName(e.target.value)}
263-
className="h-8 text-xs"
264-
/>
265-
</div>
266-
<div className="space-y-1">
267-
<Label htmlFor="card-num" className="text-xs">Card Number</Label>
268-
<div className="relative">
269-
<Input
270-
id="card-num"
271-
value={cardNumber}
272-
onChange={(e) => setCardNumber(e.target.value)}
273-
className="h-8 pl-8 text-xs font-mono"
274-
/>
275-
<CreditCard className="w-4 h-4 text-muted-foreground/60 absolute left-2.5 top-2" />
276-
</div>
277-
</div>
278-
<div className="grid grid-cols-2 gap-3">
279-
<div className="space-y-1">
280-
<Label htmlFor="card-exp" className="text-xs">Expiry Date</Label>
281-
<Input
282-
id="card-exp"
283-
value={expiry}
284-
onChange={(e) => setExpiry(e.target.value)}
285-
placeholder="MM/YY"
286-
className="h-8 text-xs font-mono"
287-
/>
288-
</div>
289-
<div className="space-y-1">
290-
<Label htmlFor="card-cvc" className="text-xs">CVC</Label>
291-
<Input
292-
id="card-cvc"
293-
value={cvc}
294-
onChange={(e) => setCvc(e.target.value)}
295-
type="password"
296-
className="h-8 text-xs font-mono"
297-
/>
298-
</div>
299-
</div>
300-
301-
{/* Promo Code Fields */}
302-
<div className="space-y-1.5 pt-2">
303-
<Label htmlFor="promo" className="text-xs flex items-center gap-1">
304-
<Percent className="w-3 h-3 text-primary" />
305-
Promo Code
306-
</Label>
307-
<div className="flex gap-2">
308-
<Input
309-
id="promo"
310-
placeholder="e.g. SECURE20"
311-
value={promoCode}
312-
onChange={(e) => setPromoCode(e.target.value)}
313-
className="h-8 text-xs uppercase"
314-
disabled={promoApplied}
315-
/>
316-
<Button
317-
type="button"
318-
variant="outline"
319-
size="sm"
320-
className="h-8 text-xs"
321-
onClick={handleApplyPromo}
322-
disabled={promoApplied}
323-
>
324-
Apply
325-
</Button>
326-
</div>
327-
{promoApplied && (
328-
<p className="text-[10px] text-green-500 font-medium">Extra 20% discount code SECURE20 successfully applied!</p>
329-
)}
330-
</div>
331-
</div>
332-
333-
{/* Actions */}
334-
<div className="pt-2 flex flex-col gap-2">
335-
<Button onClick={handlePayment} className="w-full text-xs h-9 font-semibold">
336-
<Lock className="w-3.5 h-3.5 mr-2" />
337-
Authorize & Pay
338-
</Button>
339-
<Button variant="ghost" onClick={closeCheckout} className="w-full text-xs h-9 text-muted-foreground">
340-
Cancel
341-
</Button>
342-
</div>
343-
344-
<div className="flex items-center justify-center gap-1.5 text-[10px] text-muted-foreground/80 mt-1">
345-
<ShieldCheck className="w-3.5 h-3.5 text-green-600" />
346-
<span>Secure Stripe-simulated sandbox payment processing.</span>
347-
</div>
348-
</div>
349-
)}
350-
351-
{step === "processing" && (
352-
<div className="p-10 flex flex-col items-center justify-center space-y-4 text-center">
353-
<Loader2 className="w-8 h-8 text-primary animate-spin" />
354-
<div className="space-y-1">
355-
<p className="text-sm font-semibold">Processing Secure Transaction</p>
356-
<p className="text-xs text-muted-foreground">Contacting issuing bank and acquiring tokenized key...</p>
357-
</div>
228+
<div className="p-8 flex flex-col items-center justify-center space-y-5 text-center">
229+
<div className="w-12 h-12 rounded-full bg-primary/10 text-primary flex items-center justify-center">
230+
<Sparkles className="w-7 h-7" />
358231
</div>
359-
)}
360-
361-
{step === "success" && (
362-
<div className="p-8 flex flex-col items-center justify-center space-y-5 text-center">
363-
<div className="w-12 h-12 rounded-full bg-green-500/10 text-green-500 flex items-center justify-center">
364-
<ShieldCheck className="w-7 h-7" />
365-
</div>
366-
<div className="space-y-1.5">
367-
<h3 className="text-base font-bold text-foreground">Payment Confirmed</h3>
368-
<p className="text-xs text-muted-foreground leading-relaxed px-4">
369-
Thank you for your order! Your subscription is active. Your account is now upgraded to the <strong className="text-foreground">{selectedTier.name}</strong> tier.
370-
</p>
371-
</div>
372-
<Button onClick={closeCheckout} className="w-full text-xs h-9 font-semibold">
373-
Go to Dashboard
374-
</Button>
232+
<div className="space-y-1.5">
233+
<h3 className="text-base font-bold text-foreground">Thank you for your interest!</h3>
234+
<p className="text-sm text-muted-foreground leading-relaxed px-4">
235+
Please do not pay or add any card details here. If you want to know more about the product or discuss enterprise access, please contact:
236+
</p>
237+
<p className="font-semibold text-primary mt-2">
238+
try.prit24@gmail.com
239+
</p>
375240
</div>
376-
)}
241+
<Button onClick={closeCheckout} className="w-full text-xs h-9 font-semibold mt-4">
242+
Close
243+
</Button>
244+
</div>
377245
</div>
378246
)}
379247
</DialogContent>

server/middleware/request-logger.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,13 @@ export function requestLogger(req: Request, res: Response, next: NextFunction) {
9696
geoCity,
9797
geoLat,
9898
geoLng,
99-
}).catch(err => {
100-
console.error("[RequestLogger] Failed to insert log:", err);
101-
});
99+
}).returning()
100+
.then(([log]) => {
101+
if (log) emitAdminRequestUpdate(log);
102+
})
103+
.catch(err => {
104+
console.error("[RequestLogger] Failed to insert log:", err);
105+
});
102106
});
103107

104108
next();

server/routes/admin.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,31 @@ router.post("/free-audits/:id/reject", async (req, res) => {
118118
}
119119
});
120120

121+
// Promo Offer Management
122+
router.get("/promo-offer", async (req, res) => {
123+
try {
124+
const offer = await storage.getActivePromoOffer();
125+
res.json(offer || null);
126+
} catch (error: any) {
127+
res.status(500).json({ error: error.message });
128+
}
129+
});
130+
131+
router.patch("/promo-offer/:id", async (req, res) => {
132+
try {
133+
const { startsAt, endsAt } = req.body;
134+
const updated = await storage.updatePromoOffer(req.params.id, {
135+
startsAt: startsAt ? new Date(startsAt) : undefined,
136+
endsAt: endsAt ? new Date(endsAt) : undefined,
137+
});
138+
139+
await logAdminAction(req.user!.id, "update_promo_offer", req.params.id, null, { startsAt, endsAt });
140+
res.json(updated);
141+
} catch (error: any) {
142+
res.status(500).json({ error: error.message });
143+
}
144+
});
145+
121146
router.get("/overview", async (req, res) => {
122147
try {
123148
const [{ count: totalUsers }] = await db.select({ count: sql<number>`count(*)` }).from(users);

server/socket.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,17 @@ export function emitReviewUpdate() {
4040
ioInstance.emit("review_update");
4141
}
4242
}
43+
44+
export function emitAdminRequestUpdate(log: any) {
45+
const ioInstance = getIO();
46+
if (ioInstance) {
47+
ioInstance.emit("admin_request_update", log);
48+
}
49+
}
50+
51+
export function emitAdminSystemUpdate(log: any) {
52+
const ioInstance = getIO();
53+
if (ioInstance) {
54+
ioInstance.emit("admin_system_update", log);
55+
}
56+
}

0 commit comments

Comments
 (0)