eny.space Landingpage
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(dashboard): refresh ui styling and standardize components

+164 -125
+2 -2
app/actions/components/ui/button.tsx
··· 5 5 import { cn } from "@/actions/lib/utils" 6 6 7 7 const buttonVariants = cva( 8 - "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 8 + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none cursor-pointer focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 9 9 { 10 10 variants: { 11 11 variant: { 12 - default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", 12 + default: "bg-primary text-primary-foreground", 13 13 outline: 14 14 "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", 15 15 secondary:
+19 -19
app/actions/components/ui/card.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - import { cn } from "@/actions/lib/utils" 3 + import { cn } from "@/actions/lib/utils"; 4 4 5 5 function Card({ 6 6 className, ··· 12 12 data-slot="card" 13 13 data-size={size} 14 14 className={cn( 15 - "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", 16 - className 15 + "group/card flex flex-col gap-4 overflow-hidden rounded-xl border border-white/10 bg-white/5 py-4 text-sm text-white shadow-[0_0_40px_rgba(15,23,42,0.85)] backdrop-blur-xl has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", 16 + className, 17 17 )} 18 18 {...props} 19 19 /> 20 - ) 20 + ); 21 21 } 22 22 23 23 function CardHeader({ className, ...props }: React.ComponentProps<"div">) { ··· 26 26 data-slot="card-header" 27 27 className={cn( 28 28 "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3", 29 - className 29 + className, 30 30 )} 31 31 {...props} 32 32 /> 33 - ) 33 + ); 34 34 } 35 35 36 36 function CardTitle({ className, ...props }: React.ComponentProps<"div">) { ··· 38 38 <div 39 39 data-slot="card-title" 40 40 className={cn( 41 - "text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", 42 - className 41 + "text-base leading-snug font-semibold text-white group-data-[size=sm]/card:text-sm", 42 + className, 43 43 )} 44 44 {...props} 45 45 /> 46 - ) 46 + ); 47 47 } 48 48 49 49 function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 50 50 return ( 51 51 <div 52 52 data-slot="card-description" 53 - className={cn("text-sm text-muted-foreground", className)} 53 + className={cn("text-sm text-white/70", className)} 54 54 {...props} 55 55 /> 56 - ) 56 + ); 57 57 } 58 58 59 59 function CardAction({ className, ...props }: React.ComponentProps<"div">) { ··· 62 62 data-slot="card-action" 63 63 className={cn( 64 64 "col-start-2 row-span-2 row-start-1 self-start justify-self-end", 65 - className 65 + className, 66 66 )} 67 67 {...props} 68 68 /> 69 - ) 69 + ); 70 70 } 71 71 72 72 function CardContent({ className, ...props }: React.ComponentProps<"div">) { ··· 76 76 className={cn("px-4 group-data-[size=sm]/card:px-3", className)} 77 77 {...props} 78 78 /> 79 - ) 79 + ); 80 80 } 81 81 82 82 function CardFooter({ className, ...props }: React.ComponentProps<"div">) { ··· 84 84 <div 85 85 data-slot="card-footer" 86 86 className={cn( 87 - "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3", 88 - className 87 + "flex items-center rounded-b-xl border-t border-white/10 bg-white/5 p-4 text-white group-data-[size=sm]/card:p-3", 88 + className, 89 89 )} 90 90 {...props} 91 91 /> 92 - ) 92 + ); 93 93 } 94 94 95 95 export { ··· 100 100 CardAction, 101 101 CardDescription, 102 102 CardContent, 103 - } 103 + };
+6 -2
app/components/site-header/site-header.tsx
··· 13 13 user: User | null; 14 14 } 15 15 16 + const headerCtaClass = 17 + "inline-flex items-center gap-1.5 rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950 cursor-pointer hover:bg-primary/80"; 18 + 16 19 export function SiteHeader({ user }: SiteHeaderProps) { 17 20 const [mobileOpen, setMobileOpen] = useState(false); 18 21 const [displayText, setDisplayText] = useState("."); ··· 134 137 {!user ? ( 135 138 <Button 136 139 size="default" 137 - className="rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950 hover:bg-white/90" 140 + className={headerCtaClass} 138 141 asChild 139 142 > 140 143 <Link href="/signup"> ··· 147 150 <Button 148 151 type="submit" 149 152 size="default" 150 - className="rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950" 153 + className={headerCtaClass} 151 154 > 152 155 Sign out 156 + <ArrowUpRightIcon className="ml-0.5 size-3.5" /> 153 157 </Button> 154 158 </form> 155 159 )}
+98 -76
app/dashboard/dashboard-client.tsx
··· 7 7 resumeSubscription, 8 8 createBillingPortalSession, 9 9 } from "@/actions/subscription"; 10 + import { Heading } from "@/components/heading"; 11 + import { Paragraph } from "@/components/paragraph"; 12 + import { Button } from "@/actions/components/ui/button"; 10 13 11 14 interface DashboardClientProps { 12 15 subscribed: boolean; ··· 71 74 72 75 if (!hasSubscription) { 73 76 return ( 74 - <div> 75 - <h2>Subscribe to Access</h2> 76 - <p>You need an active subscription to access the server features.</p> 77 - <button 78 - className="cursor-pointer" 77 + <div className="space-y-3 text-white"> 78 + <Heading as="h2" className="text-base font-semibold"> 79 + Subscribe to Access 80 + </Heading> 81 + <Paragraph className="text-sm text-white/80"> 82 + You need an active subscription to access the server features. 83 + </Paragraph> 84 + <Button 79 85 onClick={handleSubscribe} 80 86 disabled={loading} 87 + className="mt-1 rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950 hover:bg-primary/80" 81 88 > 82 89 {loading ? "Loading..." : "Subscribe Now"} 83 - </button> 90 + </Button> 84 91 </div> 85 92 ); 86 93 } 87 94 88 95 if (isCanceled) { 89 96 return ( 90 - <div> 91 - <h2>Subscription Canceled</h2> 92 - <p> 93 - Your subscription has been canceled. Subscribe again to regain access. 94 - </p> 95 - <button 96 - className="cursor-pointer" 97 + <div className="space-y-3 text-white"> 98 + <Heading as="h2" className="text-base font-semibold"> 99 + Subscription Canceled 100 + </Heading> 101 + <Paragraph className="text-sm text-white/80"> 102 + Your subscription has been canceled. Subscribe again to regain 103 + access. 104 + </Paragraph> 105 + <Button 97 106 onClick={handleSubscribe} 98 107 disabled={loading} 108 + className="mt-1 rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950 hover:bg-primary/80" 99 109 > 100 110 {loading ? "Loading..." : "Subscribe Again"} 101 - </button> 111 + </Button> 102 112 </div> 103 113 ); 104 114 } ··· 169 179 const isCanceling = subscription?.cancel_at_period_end === true; 170 180 171 181 return ( 172 - <div> 173 - <h2>{isCanceling ? "Subscription Canceling" : "Active Subscription"}</h2> 182 + <div className="space-y-4 text-white"> 183 + <Heading as="h2" className="text-base font-semibold"> 184 + {isCanceling ? "Subscription Canceling" : "Active Subscription"} 185 + </Heading> 174 186 {subscription && ( 175 - <div> 176 - <p> 177 - <strong>Status:</strong> {subscription.status} 178 - </p> 187 + <div className="space-y-1 text-sm text-white/80"> 188 + <Paragraph> 189 + <span className="font-semibold text-white">Status:</span>{" "} 190 + {subscription.status} 191 + </Paragraph> 179 192 {subscription.current_period_end && ( 180 - <p> 181 - <strong>{isCanceling ? "Access until:" : "Renews:"}</strong>{" "} 193 + <Paragraph> 194 + <span className="font-semibold text-white"> 195 + {isCanceling ? "Access until:" : "Renews:"} 196 + </span>{" "} 182 197 {new Date(subscription.current_period_end).toLocaleDateString()} 183 - </p> 198 + </Paragraph> 184 199 )} 185 200 {isCanceling && ( 186 - <p> 201 + <Paragraph> 187 202 Your subscription will cancel at the end of the billing period. 188 - </p> 203 + </Paragraph> 189 204 )} 190 205 </div> 191 206 )} 192 207 193 - <p> 194 - <button 195 - className="cursor-pointer" 196 - onClick={handleManageBilling} 197 - disabled={actionLoading !== null} 198 - > 199 - {actionLoading === "billing" 200 - ? "Loading..." 201 - : "Manage Payment Method"} 202 - </button> 203 - </p> 204 - 205 - <p> 206 - {isCanceling ? ( 207 - <button 208 - className="cursor-pointer" 209 - onClick={handleResume} 210 - disabled={actionLoading !== null} 211 - > 212 - {actionLoading === "resume" 213 - ? "Loading..." 214 - : "Resume Subscription"} 215 - </button> 216 - ) : ( 217 - <button 218 - className="cursor-pointer" 219 - onClick={handleCancel} 208 + <div className="space-y-3 pt-2"> 209 + <div className="flex flex-wrap gap-2"> 210 + <Button 211 + onClick={handleManageBilling} 220 212 disabled={actionLoading !== null} 213 + className="rounded-full bg-white px-4 text-xs font-medium uppercase tracking-wide text-neutral-950 hover:bg-primary/80" 221 214 > 222 - {actionLoading === "cancel" 215 + {actionLoading === "billing" 223 216 ? "Loading..." 224 - : "Cancel Subscription"} 225 - </button> 226 - )} 227 - </p> 217 + : "Manage Payment Method"} 218 + </Button> 219 + 220 + {isCanceling ? ( 221 + <Button 222 + onClick={handleResume} 223 + disabled={actionLoading !== null} 224 + className="rounded-full border border-white/60 bg-transparent px-4 text-xs font-medium uppercase tracking-wide text-white hover:bg-white/10" 225 + > 226 + {actionLoading === "resume" 227 + ? "Loading..." 228 + : "Resume Subscription"} 229 + </Button> 230 + ) : ( 231 + <Button 232 + onClick={handleCancel} 233 + disabled={actionLoading !== null} 234 + className="rounded-full border border-white/40 bg-transparent px-4 text-xs font-medium uppercase tracking-wide text-white hover:bg-white/10" 235 + > 236 + {actionLoading === "cancel" 237 + ? "Loading..." 238 + : "Cancel Subscription"} 239 + </Button> 240 + )} 241 + </div> 242 + </div> 243 + 244 + <hr className="my-4 border-white/10" /> 228 245 229 - <hr /> 230 - <h2>Server Actions</h2> 231 - <p>You have access to the following server endpoints:</p> 232 - <p> 233 - <button 234 - className="cursor-pointer" 235 - onClick={() => handleServerCall("action1")} 236 - > 237 - Call Server Action 1 238 - </button> 239 - </p> 240 - <p> 241 - <button 242 - className="cursor-pointer" 243 - onClick={() => handleServerCall("action2")} 244 - > 245 - Call Server Action 2 246 - </button> 247 - </p> 246 + <div className="space-y-2"> 247 + <Heading as="h3" className="text-sm font-semibold text-white"> 248 + Server Actions 249 + </Heading> 250 + <Paragraph className="text-sm text-white/80"> 251 + You have access to the following server endpoints: 252 + </Paragraph> 253 + <div className="flex flex-wrap gap-2 pt-1"> 254 + <Button 255 + variant="outline" 256 + className="rounded-full border-white/60 bg-transparent px-4 text-xs font-medium uppercase tracking-wide text-white hover:bg-white/10" 257 + onClick={() => handleServerCall("action1")} 258 + > 259 + Call Server Action 1 260 + </Button> 261 + <Button 262 + variant="outline" 263 + className="rounded-full border-white/60 bg-transparent px-4 text-xs font-medium uppercase tracking-wide text-white hover:bg-white/10" 264 + onClick={() => handleServerCall("action2")} 265 + > 266 + Call Server Action 2 267 + </Button> 268 + </div> 269 + </div> 248 270 </div> 249 271 ); 250 272 }
+39 -26
app/dashboard/page.tsx
··· 8 8 CardTitle, 9 9 CardDescription, 10 10 } from "@/actions/components/ui/card"; 11 - import { Button } from "@/actions/components/ui/button"; 11 + import { ButtonLink } from "@/components/button-link"; 12 + import { Heading } from "@/components/heading"; 13 + import { Paragraph } from "@/components/paragraph"; 12 14 import DashboardClient from "./dashboard-client"; 13 15 14 16 export default async function DashboardPage() { ··· 34 36 <main className="flex flex-col gap-6 px-4 py-6"> 35 37 <Card> 36 38 <CardHeader> 37 - <CardTitle>My PDS</CardTitle> 38 - <CardDescription> 39 + <Heading as="h1" className="text-xl font-semibold text-white"> 40 + My PDS 41 + </Heading> 42 + <Paragraph className="text-sm text-white/80"> 39 43 Authenticated as {user.email}. This is your Personal Data Server 40 44 overview. 41 - </CardDescription> 45 + </Paragraph> 42 46 </CardHeader> 43 47 <CardContent className="space-y-4"> 44 - <div className="grid gap-4 md:grid-cols-2"> 48 + <div className="grid gap-4 md:grid-cols-2 text-white"> 45 49 <div className="space-y-2"> 46 - <p className="text-sm font-medium text-muted-foreground"> 50 + <Paragraph className="text-sm font-medium text-white/80"> 47 51 Status 48 - </p> 49 - <p className="text-base font-semibold capitalize">{pdsStatus}</p> 52 + </Paragraph> 53 + <Paragraph className="text-base font-semibold capitalize"> 54 + {pdsStatus} 55 + </Paragraph> 50 56 </div> 51 57 <div className="space-y-2"> 52 - <p className="text-sm font-medium text-muted-foreground"> 58 + <Paragraph className="text-sm font-medium text-white/80"> 53 59 URL / Hostname 54 - </p> 60 + </Paragraph> 55 61 <a 56 62 href={pdsDashboardUrl} 57 63 target="_blank" ··· 63 69 </div> 64 70 </div> 65 71 66 - <div className="mt-4 space-y-2 rounded-md border bg-muted/40 p-4"> 67 - <p className="text-sm font-medium text-muted-foreground"> 72 + <div className="mt-4 space-y-2 rounded-md border border-white/10 bg-white/5 p-4 text-white backdrop-blur-xl"> 73 + <Paragraph className="text-sm font-medium text-white/80"> 68 74 Usage summary 69 - </p> 70 - <p className="text-sm text-muted-foreground"> 75 + </Paragraph> 76 + <Paragraph className="text-sm text-white/70"> 71 77 Usage analytics for your PDS (storage, bandwidth, user count, and 72 78 more) will appear here. 73 - </p> 79 + </Paragraph> 74 80 </div> 75 81 76 82 <div className="mt-4 flex flex-wrap gap-3"> 77 - <Button asChild variant="outline"> 78 - <a href="/dashboard/manage">Manage</a> 79 - </Button> 80 - <Button asChild variant="default"> 81 - <a href={pdsDashboardUrl} target="_blank" rel="noreferrer"> 82 - Open dashboard 83 - </a> 84 - </Button> 83 + <ButtonLink 84 + href="/dashboard/manage" 85 + className="border border-white/80 bg-transparent uppercase tracking-wide text-white hover:bg-white/10 hover:border-white focus-visible:ring-white/50" 86 + > 87 + Manage 88 + </ButtonLink> 89 + <ButtonLink 90 + href={pdsDashboardUrl} 91 + className="border border-white/80 bg-transparent uppercase tracking-wide text-white hover:bg-white/10 hover:border-white focus-visible:ring-white/50" 92 + > 93 + Open dashboard 94 + </ButtonLink> 85 95 </div> 86 96 87 97 <hr className="my-6" /> 88 98 89 - <section className="space-y-2"> 90 - <h2 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground"> 99 + <section className="space-y-2 text-white"> 100 + <Heading 101 + as="h2" 102 + className="text-sm font-semibold uppercase tracking-wide text-white/80" 103 + > 91 104 Billing & Subscription 92 - </h2> 105 + </Heading> 93 106 <DashboardClient 94 107 subscribed={subscribed} 95 108 subscription={subscription}