Skip to content
Open
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
530 changes: 530 additions & 0 deletions apps/web/messages/en.json

Large diffs are not rendered by default.

531 changes: 531 additions & 0 deletions apps/web/messages/es.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin();

const nextConfig: NextConfig = {
images: {
Expand All @@ -7,4 +10,4 @@ const nextConfig: NextConfig = {
},
};

export default nextConfig;
export default withNextIntl(nextConfig);
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"input-otp": "^1.4.2",
"lucide-react": "^0.539.0",
"next": "^15.4.6",
"next-intl": "^4.3.11",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-day-picker": "^9.8.1",
Expand All @@ -66,9 +67,9 @@
"zod": "^4.0.17"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4.1.12",
"@types/node": "^20.19.11",
"@types/react": "^19.1.10",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { useState, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslations } from 'next-intl'; // 1. Import useTranslations
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Plus, Search, Users } from 'lucide-react';
Expand All @@ -16,6 +17,7 @@ import { CreateAvatarDialog } from '@/components/avatars/create-avatar-dialog';
import { Spinner } from '@/components/ui/spinner';

export default function AvatarsPage() {
const t = useTranslations('dashboard.avatarsPage'); // 2. Initialize translations
const { user } = useAuth();
const router = useRouter();
const queryClient = useQueryClient();
Expand Down Expand Up @@ -67,26 +69,30 @@ export default function AvatarsPage() {
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-white mb-2">
Predefined Avatars
{/* 3. Translate Title */}
{t('title')}
</h1>
<p className="text-slate-400">
Create and manage reusable avatar profiles for your webhooks
{/* 4. Translate Subtitle */}
{t('subtitle')}
</p>
</div>
<Button
onClick={handleOpenCreateDialog}
className="bg-purple-600 hover:bg-purple-700 text-white"
>
<Plus className="w-4 h-4 mr-2" />
Create Avatar
{/* 5. Translate Create Button */}
{t('createButton')}
</Button>
</div>

{/* Search */}
<div className="relative mb-6">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
<Input
placeholder="Search avatars..."
// 6. Translate Search Placeholder
placeholder={t('searchPlaceholder')}
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="pl-10 bg-slate-800/50 border-slate-700 text-white placeholder:text-slate-400 focus:border-purple-500"
Expand All @@ -96,27 +102,34 @@ export default function AvatarsPage() {
{/* Content */}
{isLoading ? (
<div className="flex min-h-[50vh] items-center justify-center">
{/* 7. Loading state is purely visual, but if you wanted a message:
<p className="text-slate-400">{t('loading')}</p> */}
<Spinner size={48} className="text-primary" />
</div>
) : filteredAvatars.length === 0 ? (
<div className="text-center py-12">
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700/50 rounded-lg p-8 max-w-md mx-auto">
<Users className="w-12 h-12 text-slate-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-white mb-2">
{searchQuery ? 'No avatars found' : 'No avatars yet'}
{/* 8. Translate No Avatars/No Results Title */}
{searchQuery
? t('emptyState.noResultsTitle')
: t('emptyState.noAvatarsTitle')}
</h3>
<p className="text-slate-400 mb-4">
{/* 9. Translate No Avatars/No Results Message */}
{searchQuery
? 'Try adjusting your search terms'
: 'Create your first predefined avatar to get started'}
? t('emptyState.noResultsMessage')
: t('emptyState.noAvatarsMessage')}
</p>
{!searchQuery && (
<Button
onClick={handleOpenCreateDialog}
className="bg-purple-600 hover:bg-purple-700 text-white"
>
<Plus className="w-4 h-4 mr-2" />
Create Avatar
{/* 10. Translate Create Button in empty state */}
{t('createButton')}
</Button>
)}
</div>
Expand All @@ -135,7 +148,7 @@ export default function AvatarsPage() {
</div>
)}

{/* Create/Edit Dialog */}
{/* Create/Edit Dialog is assumed to be translated internally by nextintl */}
<CreateAvatarDialog
open={showCreateDialog}
onOpenChange={handleCloseDialog}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
'use client';

import React from 'react';
import { useTranslations } from 'next-intl';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Webhook, Clock, FileText, Zap } from 'lucide-react';
import Link from 'next/link';

export default function DashboardPage() {
const t = useTranslations('dashboard');

return (
<div className="min-h-screen">
<div className="space-y-8 p-6">
<div className="text-center space-y-2">
<h2 className="text-4xl font-bold tracking-tight bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
Welcome back!
{t('welcome')}
</h2>
<p className="text-slate-300 text-lg">
Manage your Discord webhooks and messages
</p>
<p className="text-slate-300 text-lg">{t('manage')}</p>
</div>

<Card className="bg-slate-800/50 backdrop-blur-xl border-slate-700/50 shadow-2xl">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-white">
<Zap className="h-5 w-5 text-purple-400" />
Quick Actions
{t('quickActions')}
</CardTitle>
</CardHeader>
<CardContent>
Expand All @@ -33,25 +35,27 @@ export default function DashboardPage() {
variant="outline"
>
<Webhook className="w-8 h-8 text-purple-400" />
<span className="font-medium">Webhook</span>
<span className="font-medium">{t('webhook')}</span>
</Button>
</Link>

<Link href="/dashboard/send">
<Button
className="w-full h-24 flex-col gap-3 bg-gradient-to-r from-blue-600/20 to-purple-600/20 border-blue-500/30 hover:from-blue-600/30 hover:to-purple-600/30 hover:border-blue-400/50 transition-all duration-300 text-white"
variant="outline"
>
<Clock className="w-8 h-8 text-blue-400" />
<span className="font-medium">Send Message</span>
<span className="font-medium">{t('sendMessage')}</span>
</Button>
</Link>

<Link href="/dashboard/templates">
<Button
className="w-full h-24 flex-col gap-3 bg-gradient-to-r from-pink-600/20 to-purple-600/20 border-pink-500/30 hover:from-pink-600/30 hover:to-purple-600/30 hover:border-pink-400/50 transition-all duration-300 text-white"
variant="outline"
>
<FileText className="w-8 h-8 text-pink-400" />
<span className="font-medium">Template</span>
<span className="font-medium">{t('template')}</span>
</Button>
</Link>
</div>
Expand All @@ -60,57 +64,49 @@ export default function DashboardPage() {

<Card className="bg-slate-800/50 backdrop-blur-xl border-slate-700/50 shadow-2xl">
<CardHeader>
<CardTitle className="text-white">Getting Started</CardTitle>
<p className="text-slate-300">
New to webhook management? Here&apos;s how to get started quickly.
</p>
<CardTitle className="text-white">{t('gettingStarted')}</CardTitle>
<p className="text-slate-300">{t('gettingStartedDesc')}</p>
</CardHeader>
<CardContent>
<div className="space-y-6">
<div className="flex items-start gap-4">
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 text-white flex items-center justify-center text-sm font-bold shadow-lg">
1
</div>
<div>
<h4 className="font-semibold text-white mb-1">
Connect your first webhook
</h4>
<p className="text-slate-300">
Add a Discord webhook URL to start sending messages
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 text-white flex items-center justify-center text-sm font-bold shadow-lg">
2
</div>
<div>
<h4 className="font-semibold text-white mb-1">
Send your first message
</h4>
<p className="text-slate-300">
Test your webhook by sending a message to your Discord
channel
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-pink-500 to-purple-500 text-white flex items-center justify-center text-sm font-bold shadow-lg">
3
</div>
<div>
<h4 className="font-semibold text-white mb-1">
Create templates and schedule
</h4>
<p className="text-slate-300">
Build reusable templates and schedule messages for later
</p>
</div>
</div>
<Step
number="1"
title={t('steps.oneTitle')}
description={t('steps.oneDesc')}
color="from-purple-500 to-pink-500"
/>
<Step
number="2"
title={t('steps.twoTitle')}
description={t('steps.twoDesc')}
color="from-blue-500 to-purple-500"
/>
<Step
number="3"
title={t('steps.threeTitle')}
description={t('steps.threeDesc')}
color="from-pink-500 to-purple-500"
/>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

function Step({ number, title, description, color }: any) {
return (
<div className="flex items-start gap-4">
<div
className={`w-8 h-8 rounded-full bg-gradient-to-r ${color} text-white flex items-center justify-center text-sm font-bold shadow-lg`}
>
{number}
</div>
<div>
<h4 className="font-semibold text-white mb-1">{title}</h4>
<p className="text-slate-300">{description}</p>
</div>
</div>
);
}
Loading