aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSebastien Castiel <sebastien@castiel.me>2023-12-07 19:38:03 -0500
committerSebastien Castiel <sebastien@castiel.me>2023-12-07 19:38:03 -0500
commitfb6cff2fe3101e80d2932285c0b03e0d5be872f8 (patch)
tree5cad6b431044b9e622ad2b30b4f7db214d87905a /src
parent4bc03002e17a0d128059c9d6d76c91ba7bac2e8f (diff)
Share button
Diffstat (limited to 'src')
-rw-r--r--src/app/groups/[groupId]/expenses/expense-list.tsx2
-rw-r--r--src/app/groups/[groupId]/layout.tsx14
-rw-r--r--src/app/groups/[groupId]/share-button.tsx42
-rw-r--r--src/app/layout.tsx2
-rw-r--r--src/components/copy-button.tsx34
-rw-r--r--src/components/ui/alert.tsx59
-rw-r--r--src/components/ui/popover.tsx31
7 files changed, 178 insertions, 6 deletions
diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx
index 907a37a..13a85bd 100644
--- a/src/app/groups/[groupId]/expenses/expense-list.tsx
+++ b/src/app/groups/[groupId]/expenses/expense-list.tsx
@@ -29,7 +29,7 @@ export function ExpenseList({
<div
key={expense.id}
className={cn(
- 'border-t flex justify-between pl-6 pr-2 py-4 text-sm cursor-pointer hover:bg-slate-50',
+ 'border-t flex justify-between pl-6 pr-2 py-4 text-sm cursor-pointer hover:bg-accent',
expense.isReimbursement && 'italic',
)}
onClick={() => {
diff --git a/src/app/groups/[groupId]/layout.tsx b/src/app/groups/[groupId]/layout.tsx
index 5fbd7cb..ca50762 100644
--- a/src/app/groups/[groupId]/layout.tsx
+++ b/src/app/groups/[groupId]/layout.tsx
@@ -1,5 +1,6 @@
import { GroupTabs } from '@/app/groups/[groupId]/group-tabs'
import { SaveGroupLocally } from '@/app/groups/[groupId]/save-recent-group'
+import { ShareButton } from '@/app/groups/[groupId]/share-button'
import { getGroup } from '@/lib/api'
import { Metadata } from 'next'
import Link from 'next/link'
@@ -34,11 +35,16 @@ export default async function GroupLayout({
return (
<>
- <h1 className="font-bold text-2xl mb-4">
- <Link href={`/groups/${groupId}`}>{group.name}</Link>
- </h1>
+ <div className="flex flex-col sm:flex-row justify-between">
+ <h1 className="font-bold text-2xl mb-4">
+ <Link href={`/groups/${groupId}`}>{group.name}</Link>
+ </h1>
- <GroupTabs groupId={groupId} />
+ <div className="flex gap-2 justify-between">
+ <GroupTabs groupId={groupId} />
+ <ShareButton groupId={groupId} />
+ </div>
+ </div>
{children}
diff --git a/src/app/groups/[groupId]/share-button.tsx b/src/app/groups/[groupId]/share-button.tsx
new file mode 100644
index 0000000..808f3a0
--- /dev/null
+++ b/src/app/groups/[groupId]/share-button.tsx
@@ -0,0 +1,42 @@
+import { CopyButton } from '@/components/copy-button'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover'
+import { env } from '@/lib/env'
+import { Share } from 'lucide-react'
+
+type Props = {
+ groupId: string
+}
+
+export function ShareButton({ groupId }: Props) {
+ const url = `${env.NEXT_PUBLIC_BASE_URL}/groups/${groupId}`
+
+ return (
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button size="icon">
+ <Share className="w-4 h-4" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent align="end" className="[&_p]:text-sm flex flex-col gap-3">
+ <p>
+ For other participants to see the group and add expenses, share its
+ URL with them.
+ </p>
+ <div className="flex gap-2">
+ <Input className="flex-1" defaultValue={url} readOnly />
+ <CopyButton text={url} />
+ </div>
+ <p>
+ <strong>Warning!</strong> Every person with the group URL will be able
+ to see and edit expenses. Share with caution!
+ </p>
+ </PopoverContent>
+ </Popover>
+ )
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 8943688..2c08513 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -55,7 +55,7 @@ export default function RootLayout({
<h1>
<Image
src="/logo-with-text.png"
- className="m-1"
+ className="m-1 h-auto"
width={(35 * 522) / 180}
height={35}
alt="Spliit"
diff --git a/src/components/copy-button.tsx b/src/components/copy-button.tsx
new file mode 100644
index 0000000..4220353
--- /dev/null
+++ b/src/components/copy-button.tsx
@@ -0,0 +1,34 @@
+'use client'
+import { Button } from '@/components/ui/button'
+import { Check, Copy } from 'lucide-react'
+import { useEffect, useState } from 'react'
+
+type Props = { text: string }
+
+export function CopyButton({ text }: Props) {
+ const [copied, setCopied] = useState(false)
+
+ useEffect(() => {
+ if (copied) {
+ let timeout = setTimeout(() => setCopied(false), 1000)
+ return () => {
+ setCopied(false)
+ clearTimeout(timeout)
+ }
+ }
+ }, [copied])
+
+ return (
+ <Button
+ size="icon"
+ variant="secondary"
+ type="button"
+ onClick={() => {
+ navigator.clipboard.writeText(text)
+ setCopied(true)
+ }}
+ >
+ {copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
+ </Button>
+ )
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..41fa7e0
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
+>(({ className, variant, ...props }, ref) => (
+ <div
+ ref={ref}
+ role="alert"
+ className={cn(alertVariants({ variant }), className)}
+ {...props}
+ />
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+ <h5
+ ref={ref}
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
+ {...props}
+ />
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
+ {...props}
+ />
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000..a0ec48b
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef<typeof PopoverPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+ <PopoverPrimitive.Portal>
+ <PopoverPrimitive.Content
+ ref={ref}
+ align={align}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+ </PopoverPrimitive.Portal>
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }