Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 36 additions & 33 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,22 @@
"description": "Messages are not encrypted and can be read by relays. For maximum privacy host your own relay.",
"placeholder": "Choose visibility"
},
"access": {
"label": "Access",
"anyone": "Anyone",
"invite-only": "Invite only",
"placeholder": "Choose policy"
"access-settings": "Access Settings",
"private": {
"label": "Private",
"description": "Only members can read group messages"
},
"restricted": {
"label": "Restricted",
"description": "Only members can send messages"
},
"hidden": {
"label": "Hidden",
"description": "Group info is hidden from non-members"
},
"closed": {
"label": "Closed",
"description": "Join requests are ignored (invite-only)"
},
"submit": {
"trigger": "Create",
Expand Down Expand Up @@ -281,27 +292,21 @@
"metadata": {
"community": "This is a profile based community",
"group": "Group",
"private": "Private",
"closed": "Closed",
"visibility": {
"private": {
"trigger": "Members only",
"content": "Only members can read content"
},
"public": {
"trigger": "Public",
"content": "Can be read by anyone"
}
"private": {
"trigger": "Private",
"content": "Only members can read messages"
},
"access": {
"closed": {
"trigger": "Closed",
"content": "Requires approval or an invitation to join"
},
"open": {
"trigger": "Open",
"content": "Anyone can join"
}
"restricted": {
"trigger": "Restricted",
"content": "Only members can send messages"
},
"hidden": {
"trigger": "Hidden",
"content": "Group info is hidden from non-members"
},
"closed": {
"trigger": "Closed",
"content": "Join requests are ignored (invite-only)"
},
"join-the-conversation": "Join the conversation",
"map_picker": {
Expand Down Expand Up @@ -347,16 +352,14 @@
},
"privacy": {
"tab": "Privacy",
"visibility": "Visibility",
"access": "Access Control",
"public": "Public",
"public_description": "Anyone can see the group and its content",
"private": "Private",
"private_description": "Only members can see the group content",
"open": "Open",
"open_description": "Anyone can join the group",
"private_description": "Only members can read group messages",
"restricted": "Restricted",
"restricted_description": "Only members can send messages to the group",
"hidden": "Hidden",
"hidden_description": "Relay hides group metadata from non-members",
"closed": "Closed",
"closed_description": "Members must be approved to join",
"closed_description": "Join requests are ignored (invite-only)",
"save": "Save Privacy Settings",
"saving": "Saving...",
"success": "Group privacy settings updated successfully",
Expand Down
11 changes: 9 additions & 2 deletions src/components/nostr/groups/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ import { formatShortNumber } from "@/lib/number";
import { Badge } from "@/components/ui/badge";
import { Name } from "@/components/nostr/name";
import Amount from "@/components/amount";
import { useCommunity, useFetchGroupParticipants } from "@/lib/nostr/groups";
import {
useCommunity,
useFetchGroupParticipants,
useGroup,
} from "@/lib/nostr/groups";
import { Zap } from "@/components/nostr/zap";
import { validateZap } from "@/lib/nip-57";
import { ChatInput } from "@/components/nostr/chat/input";
Expand Down Expand Up @@ -812,6 +816,7 @@ export const GroupChat = forwardRef(
({ group }: { group: Group }, ref: ForwardedRef<HTMLDivElement | null>) => {
// todo: load older messages when scrolling up
const { data: participants } = useFetchGroupParticipants(group);
const { data: metadata } = useGroup(group);
const members = participants?.members || [];
const admins = participants?.admins || [];
const { data: relayInfo } = useRelayInfo(group.relay);
Expand Down Expand Up @@ -964,7 +969,9 @@ export const GroupChat = forwardRef(
...(isRelayGroup ? [["-"]] : []),
]}
showJoinRequest={
!canIPoast && relayInfo?.supported_nips?.includes(29)
!canIPoast &&
relayInfo?.supported_nips?.includes(29) &&
!metadata?.isClosed
}
>
{replyingTo ? (
Expand Down
201 changes: 118 additions & 83 deletions src/components/nostr/groups/create-on-relay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { z } from "zod";
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Plus } from "lucide-react";
import { Plus, BookLock, PenOff, EyeOff, ShieldOff } from "lucide-react";
import { NostrEvent } from "nostr-tools";
import { groupsContentAtom } from "@/app/store";
import { Button } from "@/components/ui/button";
Expand All @@ -29,13 +29,7 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { UploadImage } from "@/components/upload-image";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { useRelays, useRelaySet } from "@/lib/nostr";
import { useCreateGroup, useEditGroup } from "@/lib/nostr/groups";
import { getRelayHost, isRelayURL } from "@/lib/relay";
Expand All @@ -51,8 +45,10 @@ const formSchema = z.object({
name: z.string().min(1).max(140),
picture: z.string().url().optional(),
about: z.string().min(0).max(500).optional(),
visibility: z.enum(["public", "private"]).default("public"),
access: z.enum(["open", "closed"]).default("open"),
isPrivate: z.boolean().default(false),
isRestricted: z.boolean().default(false),
isHidden: z.boolean().default(false),
isClosed: z.boolean().default(false),
});

/**
Expand Down Expand Up @@ -85,8 +81,10 @@ export function CreateGroupOnRelay({
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
visibility: "public",
access: "open",
isPrivate: false,
isRestricted: false,
isHidden: false,
isClosed: false,
},
});

Expand Down Expand Up @@ -135,8 +133,10 @@ export function CreateGroupOnRelay({
name: "",
picture: "",
about: "",
visibility: "public",
access: "open",
isPrivate: false,
isRestricted: false,
isHidden: false,
isClosed: false,
});
}

Expand Down Expand Up @@ -235,81 +235,116 @@ export function CreateGroupOnRelay({
{t("group.create.form.relay.description")}
</FormDescription>
</FormItem>
<FormField
control={form.control}
name="visibility"
render={({ field }) => (
<FormItem>
<div className="flex flex-row justify-between items-center">
<FormLabel>
{t("group.create.form.visibility.label")}
</FormLabel>
{/* Group Access Settings */}
<div className="space-y-3 pt-2">
<h4 className="text-sm font-medium text-muted-foreground">
{t("group.create.form.access-settings")}
</h4>
<FormField
control={form.control}
name="isPrivate"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="flex items-center gap-2">
<BookLock className="h-4 w-4 text-muted-foreground" />
<div className="space-y-0.5">
<FormLabel className="text-sm font-medium">
{t("group.create.form.private.label")}
</FormLabel>
<FormDescription className="text-xs">
{t("group.create.form.private.description")}
</FormDescription>
</div>
</div>
<FormControl>
<Select
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
onValueChange={field.onChange}
defaultValue={"public"}
>
<SelectTrigger className="w-32">
<SelectValue
placeholder={t(
"group.create.form.visibility.placeholder",
)}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="public">
{t("group.create.form.visibility.anyone")}
</SelectItem>
<SelectItem value="private">
{t("group.create.form.visibility.members-only")}
</SelectItem>
</SelectContent>
</Select>
/>
</FormControl>
</div>
<FormDescription>
{t("group.create.form.visibility.description")}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="access"
render={({ field }) => (
<FormItem>
<div className="flex flex-row justify-between items-center">
<FormLabel>{t("group.create.form.access.label")}</FormLabel>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isRestricted"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="flex items-center gap-2">
<PenOff className="h-4 w-4 text-muted-foreground" />
<div className="space-y-0.5">
<FormLabel className="text-sm font-medium">
{t("group.create.form.restricted.label")}
</FormLabel>
<FormDescription className="text-xs">
{t("group.create.form.restricted.description")}
</FormDescription>
</div>
</div>
<FormControl>
<Select
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
onValueChange={field.onChange}
defaultValue={"open"}
>
<SelectTrigger className="w-32">
<SelectValue
placeholder={t(
"group.create.form.access.placeholder",
)}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="open">
{t("group.create.form.access.anyone")}
</SelectItem>
<SelectItem value="closed">
{t("group.create.form.access.invite-only")}
</SelectItem>
</SelectContent>
</Select>
/>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isHidden"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="flex items-center gap-2">
<EyeOff className="h-4 w-4 text-muted-foreground" />
<div className="space-y-0.5">
<FormLabel className="text-sm font-medium">
{t("group.create.form.hidden.label")}
</FormLabel>
<FormDescription className="text-xs">
{t("group.create.form.hidden.description")}
</FormDescription>
</div>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isClosed"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="flex items-center gap-2">
<ShieldOff className="h-4 w-4 text-muted-foreground" />
<div className="space-y-0.5">
<FormLabel className="text-sm font-medium">
{t("group.create.form.closed.label")}
</FormLabel>
<FormDescription className="text-xs">
{t("group.create.form.closed.description")}
</FormDescription>
</div>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex justify-end mt-6">
<Button disabled={isLoading} type="submit" className="h-10">
{t("group.create.form.submit.trigger")}
Expand Down
Loading