Skip to content

Commit 3e4f0fa

Browse files
authored
Merge pull request #4 from thakurdotdev/razorpay-payment
feat: Implement subscription plans with Razorpay integration, user en…
2 parents b370209 + 91f0451 commit 3e4f0fa

29 files changed

Lines changed: 1585 additions & 12 deletions

.github/workflows/server-deploy.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,29 +70,50 @@ jobs:
7070
username: ubuntu
7171
key: ${{ secrets.SERVER_SSH_KEY }}
7272
script: |
73+
set -e
74+
75+
# -------- DEPLOY LOCK --------
76+
LOCK=/tmp/syncvibe-deploy.lock
77+
exec 9>$LOCK || exit 1
78+
flock -n 9 || exit 0
79+
7380
cd /home/ubuntu/syncvibe
7481
git fetch origin
82+
PREV=$(git rev-parse HEAD@{1} || true)
7583
git reset --hard origin/main
7684
77-
if git diff --name-only HEAD@{1} HEAD | grep '^server/' >/dev/null; then
85+
# -------- BACKEND DEPLOY --------
86+
if [ -n "$PREV" ] && git diff --name-only "$PREV" HEAD | grep '^server/' >/dev/null; then
7887
echo "Backend code changed"
7988
80-
if git diff --name-only HEAD@{1} HEAD | grep '^server/package.json$' >/dev/null; then
89+
if git diff --name-only "$PREV" HEAD | grep '^server/package.json$' >/dev/null; then
8190
cd server
8291
/home/ubuntu/.bun/bin/bun install
8392
cd ..
8493
fi
8594
8695
sudo systemctl daemon-reexec
8796
sudo systemctl restart syncvibe-api.service
97+
sleep 2
98+
sudo systemctl is-active --quiet syncvibe-api.service
8899
else
89100
echo "Backend unchanged"
90101
fi
91102
103+
# -------- FRONTEND DEPLOY (ATOMIC) --------
92104
if [ -f /home/ubuntu/client-dist.tar.gz ]; then
93-
echo "Deploying frontend"
94-
sudo rm -rf /var/www/syncvibe-web/*
95-
sudo tar -xzf /home/ubuntu/client-dist.tar.gz -C /var/www/syncvibe-web
105+
echo "Deploying frontend atomically"
106+
107+
TMP_DIR=/var/www/.syncvibe-web-new
108+
109+
sudo rm -rf $TMP_DIR
110+
sudo mkdir -p $TMP_DIR
111+
sudo tar -xzf /home/ubuntu/client-dist.tar.gz -C $TMP_DIR
112+
113+
sudo mv /var/www/syncvibe-web /var/www/syncvibe-web-old || true
114+
sudo mv $TMP_DIR /var/www/syncvibe-web
115+
sudo rm -rf /var/www/syncvibe-web-old
116+
96117
rm /home/ubuntu/client-dist.tar.gz
97118
else
98119
echo "No frontend artifact"

client/src/Pages/Music/BottomPlayer/index.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ const BottomPlayer = () => {
7171
</div>
7272
</CardContent>
7373
</Card>
74-
75-
<FloatingVoiceControl />
74+
{!isSheetOpen && <FloatingVoiceControl />}
7675

7776
<MinimizedPlayer
7877
isMinimized={isMinimized}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
Receipt,
3+
CheckCircle,
4+
XCircle,
5+
Clock,
6+
CreditCard,
7+
Calendar,
8+
ExternalLink,
9+
} from "lucide-react"
10+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
11+
import { Badge } from "@/components/ui/badge"
12+
import { Skeleton } from "@/components/ui/skeleton"
13+
import { usePaymentHistoryQuery } from "@/hooks/queries/useEntitlementQuery"
14+
15+
const statusConfig = {
16+
PAID: { label: "Success", variant: "default", icon: CheckCircle, color: "text-green-500" },
17+
FAILED: { label: "Failed", variant: "destructive", icon: XCircle, color: "text-red-500" },
18+
CREATED: { label: "Pending", variant: "secondary", icon: Clock, color: "text-yellow-500" },
19+
}
20+
21+
const formatAmount = (amountInPaise, currency = "INR") => {
22+
const amount = amountInPaise / 100
23+
return new Intl.NumberFormat("en-IN", {
24+
style: "currency",
25+
currency,
26+
}).format(amount)
27+
}
28+
29+
const formatDate = (dateString) => {
30+
return new Date(dateString).toLocaleDateString("en-IN", {
31+
day: "numeric",
32+
month: "short",
33+
year: "numeric",
34+
hour: "2-digit",
35+
minute: "2-digit",
36+
})
37+
}
38+
39+
function PaymentCard({ payment }) {
40+
const config = statusConfig[payment.status] || statusConfig.CREATED
41+
const StatusIcon = config.icon
42+
43+
return (
44+
<Card className="group hover:shadow-md transition-all duration-200 border-border/60">
45+
<CardContent className="p-4 sm:p-5">
46+
<div className="flex items-start justify-between gap-4">
47+
<div className="flex items-start gap-3 sm:gap-4 min-w-0 flex-1">
48+
<div className="p-2.5 rounded-xl bg-primary/10 shrink-0">
49+
<CreditCard className="h-5 w-5 text-primary" />
50+
</div>
51+
<div className="min-w-0 flex-1 space-y-1">
52+
<div className="flex items-center gap-2 flex-wrap">
53+
<p className="font-semibold text-base">PRO Subscription</p>
54+
<Badge variant={config.variant} className="text-xs">
55+
<StatusIcon className={`h-3 w-3 mr-1 ${config.color}`} />
56+
{config.label}
57+
</Badge>
58+
</div>
59+
<div className="flex items-center gap-1.5 text-muted-foreground text-sm">
60+
<Calendar className="h-3.5 w-3.5" />
61+
<span>{formatDate(payment.createdAt)}</span>
62+
</div>
63+
{payment.razorpayPaymentId && (
64+
<p className="text-xs text-muted-foreground font-mono truncate">
65+
ID: {payment.razorpayPaymentId}
66+
</p>
67+
)}
68+
</div>
69+
</div>
70+
<div className="text-right shrink-0">
71+
<p className="font-bold text-lg">{formatAmount(payment.amount, payment.currency)}</p>
72+
</div>
73+
</div>
74+
</CardContent>
75+
</Card>
76+
)
77+
}
78+
79+
function PaymentSkeleton() {
80+
return (
81+
<Card>
82+
<CardContent className="p-5">
83+
<div className="flex items-start gap-4">
84+
<Skeleton className="h-10 w-10 rounded-xl" />
85+
<div className="flex-1 space-y-2">
86+
<Skeleton className="h-5 w-40" />
87+
<Skeleton className="h-4 w-32" />
88+
</div>
89+
<Skeleton className="h-6 w-20" />
90+
</div>
91+
</CardContent>
92+
</Card>
93+
)
94+
}
95+
96+
function EmptyState() {
97+
return (
98+
<Card className="border-dashed">
99+
<CardContent className="py-16 text-center">
100+
<div className="mx-auto w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4">
101+
<Receipt className="h-8 w-8 text-muted-foreground" />
102+
</div>
103+
<h3 className="text-lg font-semibold mb-2">No payment history</h3>
104+
<p className="text-muted-foreground text-sm max-w-sm mx-auto">
105+
Your payment transactions will appear here once you make a purchase.
106+
</p>
107+
</CardContent>
108+
</Card>
109+
)
110+
}
111+
112+
export default function PaymentHistoryPage() {
113+
const { data: payments, isLoading, isError } = usePaymentHistoryQuery()
114+
115+
return (
116+
<div className="container mx-auto px-4 py-8 max-w-3xl">
117+
<Card className="mb-6 bg-gradient-to-br from-primary/5 to-primary/10 border-primary/20">
118+
<CardHeader className="pb-4">
119+
<div className="flex items-center gap-3">
120+
<div className="p-2.5 rounded-xl bg-primary/15">
121+
<Receipt className="h-6 w-6 text-primary" />
122+
</div>
123+
<div>
124+
<CardTitle className="text-2xl">Payment History</CardTitle>
125+
<CardDescription>View all your payment transactions</CardDescription>
126+
</div>
127+
</div>
128+
</CardHeader>
129+
</Card>
130+
131+
<div className="space-y-3">
132+
{isLoading ? (
133+
Array.from({ length: 3 }).map((_, i) => <PaymentSkeleton key={i} />)
134+
) : isError ? (
135+
<Card className="border-destructive/50">
136+
<CardContent className="py-8 text-center">
137+
<XCircle className="h-10 w-10 text-destructive mx-auto mb-3" />
138+
<p className="text-destructive font-medium">Failed to load payment history</p>
139+
</CardContent>
140+
</Card>
141+
) : !payments?.length ? (
142+
<EmptyState />
143+
) : (
144+
payments.map((payment) => <PaymentCard key={payment.paymentid} payment={payment} />)
145+
)}
146+
</div>
147+
</div>
148+
)
149+
}

0 commit comments

Comments
 (0)