Skip to content

Commit 7d8820e

Browse files
committed
feat: add public website support
1 parent 3635123 commit 7d8820e

File tree

6 files changed

+127
-15
lines changed

6 files changed

+127
-15
lines changed

apps/dashboard/app/(main)/websites/[id]/_components/tabs/settings-tab.tsx

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
GlobeIcon,
1313
InfoIcon,
1414
PencilIcon,
15+
ShareIcon,
1516
SlidersIcon,
1617
TableIcon,
1718
TrashIcon,
@@ -47,7 +48,7 @@ import {
4748
TooltipTrigger,
4849
} from '@/components/ui/tooltip';
4950
import { WebsiteDialog } from '@/components/website-dialog';
50-
import { useDeleteWebsite } from '@/hooks/use-websites';
51+
import { useDeleteWebsite, useUpdateWebsite } from '@/hooks/use-websites';
5152
import {
5253
generateNpmCode,
5354
generateNpmComponentCode,
@@ -75,8 +76,10 @@ export function WebsiteSettingsTab({
7576
const [installMethod] = useState<'script' | 'npm'>('script');
7677
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
7778
const [showEditDialog, setShowEditDialog] = useState(false);
79+
const [isPublic, setIsPublic] = useState(websiteData.isPublic);
80+
const updateWebsiteMutation = useUpdateWebsite();
7881
const [activeTab, setActiveTab] = useState<
79-
'tracking' | 'basic' | 'advanced' | 'optimization'
82+
'tracking' | 'basic' | 'advanced' | 'optimization' | 'privacy'
8083
>('tracking');
8184
const [trackingOptions, setTrackingOptions] =
8285
useState<TrackingOptions>(RECOMMENDED_DEFAULTS);
@@ -89,6 +92,23 @@ export function WebsiteSettingsTab({
8992
setTimeout(() => setCopiedBlockId(null), 2000);
9093
};
9194

95+
const handleTogglePublic = () => {
96+
const newIsPublic = !isPublic;
97+
setIsPublic(newIsPublic);
98+
toast.promise(
99+
updateWebsiteMutation.mutateAsync({
100+
id: websiteId,
101+
isPublic: newIsPublic,
102+
name: websiteData.name,
103+
}),
104+
{
105+
loading: 'Updating privacy settings...',
106+
success: 'Privacy settings updated!',
107+
error: 'Failed to update settings.',
108+
}
109+
);
110+
};
111+
92112
const handleToggleOption = (option: keyof TrackingOptions) => {
93113
setTrackingOptions((prev) => toggleTrackingOption(prev, option));
94114
};
@@ -167,6 +187,14 @@ export function WebsiteSettingsTab({
167187
/>
168188
)}
169189

190+
{activeTab === 'privacy' && (
191+
<PrivacyTab
192+
isPublic={isPublic}
193+
onTogglePublic={handleTogglePublic}
194+
websiteId={websiteId}
195+
/>
196+
)}
197+
170198
{activeTab !== 'tracking' && (
171199
<TabActions
172200
installMethod={installMethod}
@@ -327,7 +355,7 @@ function SettingsNavigation({
327355
}: {
328356
activeTab: string;
329357
setActiveTab: (
330-
tab: 'tracking' | 'basic' | 'advanced' | 'optimization'
358+
tab: 'tracking' | 'basic' | 'advanced' | 'optimization' | 'privacy'
331359
) => void;
332360
onDeleteClick: () => void;
333361
trackingOptions: TrackingOptions;
@@ -434,6 +462,17 @@ function SettingsNavigation({
434462
</Badge>
435463
</Button>
436464

465+
<Button
466+
className="h-10 w-full justify-between gap-2 transition-all duration-200"
467+
onClick={() => setActiveTab('privacy')}
468+
variant={activeTab === 'privacy' ? 'default' : 'ghost'}
469+
>
470+
<div className="flex items-center gap-2">
471+
<ShareIcon className="h-4 w-4" />
472+
<span>Sharing</span>
473+
</div>
474+
</Button>
475+
437476
<div className="border-t pt-4">
438477
<div className="px-3 py-2">
439478
<h3 className="font-semibold text-muted-foreground text-xs uppercase tracking-wide">
@@ -1449,3 +1488,75 @@ function DeleteWebsiteDialog({
14491488
</AlertDialog>
14501489
);
14511490
}
1491+
1492+
function PrivacyTab({
1493+
isPublic,
1494+
onTogglePublic,
1495+
websiteId,
1496+
}: {
1497+
isPublic: boolean;
1498+
onTogglePublic: () => void;
1499+
websiteId: string;
1500+
}) {
1501+
const shareableLink = `${window.location.origin}/demo/${websiteId}`;
1502+
1503+
const handleCopyLink = () => {
1504+
navigator.clipboard.writeText(shareableLink);
1505+
toast.success('Shareable link copied to clipboard!');
1506+
};
1507+
1508+
return (
1509+
<div className="space-y-4">
1510+
<div className="flex flex-col space-y-1.5">
1511+
<h3 className="font-semibold text-lg">Sharing & Privacy</h3>
1512+
<p className="text-muted-foreground text-sm">
1513+
Manage your website's public visibility and shareable link.
1514+
</p>
1515+
</div>
1516+
<div className="rounded border p-4">
1517+
<div className="flex items-start justify-between">
1518+
<div className="space-y-1">
1519+
<Label className="font-medium" htmlFor="public-access">
1520+
Public Access
1521+
</Label>
1522+
<p className="text-muted-foreground text-xs">
1523+
Allow anyone with the link to view your website's dashboard.
1524+
</p>
1525+
</div>
1526+
<Switch
1527+
checked={isPublic}
1528+
id="public-access"
1529+
onCheckedChange={onTogglePublic}
1530+
/>
1531+
</div>
1532+
1533+
{isPublic && (
1534+
<div className="mt-4 space-y-2 border-t pt-4">
1535+
<Label htmlFor="shareable-link">Shareable Link</Label>
1536+
<div className="flex items-center gap-2">
1537+
<input
1538+
className="flex-grow rounded border bg-background px-3 py-1.5 text-sm"
1539+
id="shareable-link"
1540+
readOnly
1541+
type="text"
1542+
value={shareableLink}
1543+
/>
1544+
<Button
1545+
className="h-8 gap-1.5 px-3 text-xs"
1546+
onClick={handleCopyLink}
1547+
size="sm"
1548+
variant="outline"
1549+
>
1550+
<ClipboardIcon className="h-3.5 w-3.5" />
1551+
Copy
1552+
</Button>
1553+
</div>
1554+
<p className="text-muted-foreground text-xs">
1555+
Anyone with this link can view the analytics for this website.
1556+
</p>
1557+
</div>
1558+
)}
1559+
</div>
1560+
</div>
1561+
);
1562+
}

packages/rpc/src/routers/autocomplete.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createDrizzleCache, redis } from '@databuddy/redis';
33
import { TRPCError } from '@trpc/server';
44
import { z } from 'zod/v4';
55
import { logger } from '../lib/logger';
6-
import { createTRPCRouter, protectedProcedure } from '../trpc';
6+
import { createTRPCRouter, publicProcedure } from '../trpc';
77
import { authorizeWebsiteAccess } from '../utils/auth';
88

99
const drizzleCache = createDrizzleCache({ redis, namespace: 'autocomplete' });
@@ -139,7 +139,7 @@ const categorizeAutocompleteResults = (
139139
});
140140

141141
export const autocompleteRouter = createTRPCRouter({
142-
get: protectedProcedure
142+
get: publicProcedure
143143
.input(analyticsDateRangeSchema)
144144
.query(({ ctx, input }) => {
145145
const { startDate, endDate } =

packages/rpc/src/routers/funnels.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
processFunnelAnalyticsByReferrer,
1010
} from '../lib/analytics-utils';
1111
import { logger } from '../lib/logger';
12-
import { createTRPCRouter, protectedProcedure } from '../trpc';
12+
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
1313
import { authorizeWebsiteAccess } from '../utils/auth';
1414

1515
const drizzleCache = createDrizzleCache({ redis, namespace: 'funnels' });
@@ -63,7 +63,7 @@ const getDefaultDateRange = () => {
6363
};
6464

6565
export const funnelsRouter = createTRPCRouter({
66-
list: protectedProcedure
66+
list: publicProcedure
6767
.input(z.object({ websiteId: z.string() }))
6868
.query(({ ctx, input }) => {
6969
const cacheKey = `funnels:list:${input.websiteId}`;
@@ -316,7 +316,7 @@ export const funnelsRouter = createTRPCRouter({
316316
}
317317
}),
318318

319-
getAnalytics: protectedProcedure
319+
getAnalytics: publicProcedure
320320
.input(funnelAnalyticsSchema)
321321
.query(({ ctx, input }) => {
322322
const { startDate, endDate } =
@@ -403,7 +403,7 @@ export const funnelsRouter = createTRPCRouter({
403403
});
404404
}),
405405

406-
getAnalyticsByReferrer: protectedProcedure
406+
getAnalyticsByReferrer: publicProcedure
407407
.input(funnelAnalyticsSchema)
408408
.query(({ ctx, input }) => {
409409
const { startDate, endDate } =

packages/rpc/src/routers/goals.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
processGoalAnalytics,
1010
} from '../lib/analytics-utils';
1111
import { logger } from '../lib/logger';
12-
import { createTRPCRouter, protectedProcedure } from '../trpc';
12+
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
1313
import { authorizeWebsiteAccess } from '../utils/auth';
1414

1515
const drizzleCache = createDrizzleCache({ redis, namespace: 'goals' });
@@ -72,7 +72,7 @@ const getDefaultDateRange = () => {
7272
};
7373

7474
export const goalsRouter = createTRPCRouter({
75-
list: protectedProcedure
75+
list: publicProcedure
7676
.input(z.object({ websiteId: z.string() }))
7777
.query(({ ctx, input }) => {
7878
const cacheKey = `goals:list:${input.websiteId}`;
@@ -94,7 +94,7 @@ export const goalsRouter = createTRPCRouter({
9494
});
9595
}),
9696

97-
getById: protectedProcedure
97+
getById: publicProcedure
9898
.input(z.object({ id: z.string(), websiteId: z.string() }))
9999
.query(({ ctx, input }) => {
100100
const cacheKey = `goals:byId:${input.id}:${input.websiteId}`;
@@ -214,7 +214,7 @@ export const goalsRouter = createTRPCRouter({
214214
return { success: true };
215215
}),
216216

217-
getAnalytics: protectedProcedure
217+
getAnalytics: publicProcedure
218218
.input(analyticsDateRangeSchema)
219219
.query(({ ctx, input }) => {
220220
const { startDate, endDate } =
@@ -282,7 +282,7 @@ export const goalsRouter = createTRPCRouter({
282282
});
283283
}),
284284

285-
bulkAnalytics: protectedProcedure
285+
bulkAnalytics: publicProcedure
286286
.input(
287287
z.object({
288288
websiteId: z.string(),

packages/rpc/src/routers/websites.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ export const websitesRouter = createTRPCRouter({
317317
const updatedWebsite = await ctx.db.transaction(async (tx) => {
318318
const [website] = await tx
319319
.update(websites)
320-
.set({ name: input.name })
320+
.set({ name: input.name, isPublic: input.isPublic })
321321
.where(eq(websites.id, input.id))
322322
.returning();
323323

packages/validation/src/schemas/websites.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const createWebsiteSchema = z.object({
4141
export const updateWebsiteSchema = z.object({
4242
id: z.string(),
4343
name: websiteNameSchema,
44+
isPublic: z.boolean().optional(),
4445
});
4546

4647
export const transferWebsiteSchema = z.object({

0 commit comments

Comments
 (0)