diff options
| author | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 19:38:03 -0500 |
|---|---|---|
| committer | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 19:38:03 -0500 |
| commit | fb6cff2fe3101e80d2932285c0b03e0d5be872f8 (patch) | |
| tree | 5cad6b431044b9e622ad2b30b4f7db214d87905a /src | |
| parent | 4bc03002e17a0d128059c9d6d76c91ba7bac2e8f (diff) | |
Share button
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/groups/[groupId]/expenses/expense-list.tsx | 2 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/layout.tsx | 14 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/share-button.tsx | 42 | ||||
| -rw-r--r-- | src/app/layout.tsx | 2 | ||||
| -rw-r--r-- | src/components/copy-button.tsx | 34 | ||||
| -rw-r--r-- | src/components/ui/alert.tsx | 59 | ||||
| -rw-r--r-- | src/components/ui/popover.tsx | 31 |
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 } |
