First Commit
This commit is contained in:
Executable
+9
@@ -0,0 +1,9 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
//
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("Edit Custom Subject"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("Edit Custom Subject") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-3xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ $customSubject->name }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ __("Created") }}: {{ $customSubject->created_at->format("d/m/Y H:i") }}</p>
|
||||
</div>
|
||||
|
||||
<form action="{{ route("admin.custom-subjects.update", $customSubject) }}" method="POST">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Subject Name") }} *</label>
|
||||
<input type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
value="{{ old("name", $customSubject->name) }}"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 @error("name") border-red-500 @enderror">
|
||||
@error("name")
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="is_active"
|
||||
id="is_active"
|
||||
value="1"
|
||||
{{ old("is_active", $customSubject->is_active) ? "checked" : "" }}
|
||||
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
|
||||
<label for="is_active" class="ml-2 block text-sm text-gray-900">
|
||||
{{ __("Active") }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ __("Inactive subjects will not receive scheduled insights.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __("Save Changes") }}
|
||||
</button>
|
||||
<a href="{{ route("admin.custom-subjects.index") }}"
|
||||
class="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-300 transition">
|
||||
{{ __("Cancel") }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("My Custom Subjects"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("My Custom Subjects") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Upload Form -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Upload PDF") }}</h3>
|
||||
<form action="{{ route("admin.custom-subjects.store") }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="md:col-span-1">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Subject Name") }} *</label>
|
||||
<input type="text" name="name" id="name" value="{{ old("name") }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 @error("name") border-red-500 @enderror">
|
||||
@error("name")
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="md:col-span-1">
|
||||
<label for="pdf" class="block text-sm font-medium text-gray-700 mb-2">{{ __("PDF File") }} *</label>
|
||||
<input type="file" name="pdf" id="pdf" accept=".pdf,application/pdf" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 @error("pdf") border-red-500 @enderror">
|
||||
@error("pdf")
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
<p class="text-xs text-gray-500 mt-1">{{ __("Maximum file size: 10MB") }}</p>
|
||||
</div>
|
||||
<div class="md:col-span-1 flex items-end">
|
||||
<button type="submit"
|
||||
class="w-full px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
<svg class="w-4 h-4 inline mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
||||
</svg>
|
||||
{{ __("Upload and Process") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Custom Subjects List -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("My Custom Subjects") }}</h3>
|
||||
|
||||
@if($customSubjects->count() === 0)
|
||||
<p class="text-gray-500 text-center py-8">{{ __("No custom subjects yet. Upload a PDF to get started.") }}</p>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Name") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Status") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Created") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach($customSubjects as $subject)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">{{ $subject->name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
@if($subject->processing_status === "completed")
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{{ __("Ready") }}
|
||||
</span>
|
||||
@elseif($subject->processing_status === "processing")
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
|
||||
{{ __("Processing") }}
|
||||
</span>
|
||||
@elseif($subject->processing_status === "failed")
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||
{{ __("Failed") }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $subject->created_at->format("d/m/Y H:i") }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
@if($subject->processing_status === "completed")
|
||||
<a href="{{ route("admin.custom-subjects.edit", $subject) }}"
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-3">
|
||||
{{ __("Edit") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.custom-subjects.show", $subject) }}"
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-3">
|
||||
{{ __("Ask Questions") }}
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route("admin.custom-subjects.edit", $subject) }}"
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-3">
|
||||
{{ __("Edit") }}
|
||||
</a>
|
||||
@endif
|
||||
<form action="{{ route("admin.custom-subjects.destroy", $subject) }}" method="POST" class="inline"
|
||||
onsubmit="return confirm('{{ __("Delete this custom subject? This action cannot be undone.") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-red-600 hover:text-red-900">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", $customSubject->name)
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ $customSubject->name }}
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Ask questions about the PDF content") }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route("admin.custom-subjects.index") }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
{{ __("Back") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Q&A Form -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<form action="{{ route("admin.custom-subjects.ask", $customSubject) }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Your Question") }}</label>
|
||||
<textarea name="question" id="question" rows="3" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 @error("question") border-red-500 @enderror"
|
||||
placeholder="{{ __("Ask something about the PDF content...") }}">{{ old("question") }}</textarea>
|
||||
@error("question")
|
||||
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="bg-blue-50 border-l-4 border-blue-400 p-3 mb-4">
|
||||
<p class="text-sm text-blue-700">
|
||||
{{ __("Tip: Always specify the desired language in your question, e.g. \"Answer in English\" or \"Responda em português\".") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
{{ __("Ask") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Questions History -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Questions History") }}</h3>
|
||||
|
||||
@if($questions->count() === 0)
|
||||
<p class="text-gray-500 text-center py-8">{{ __("No questions asked yet.") }}</p>
|
||||
@else
|
||||
<div class="space-y-4">
|
||||
@foreach($questions as $q)
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0 w-8 h-8 bg-indigo-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900">{{ $q->question }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ $q->asked_at->format("d/m/Y H:i") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@if($q->answer)
|
||||
<div class="mt-3 ml-11 p-3 bg-gray-50 rounded-lg">
|
||||
<div class="prose prose-sm max-w-none text-gray-700">
|
||||
{!! nl2br(e($q->answer)) !!}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex items-center gap-2 ml-11 mt-2">
|
||||
<form action="{{ route('admin.custom-subjects.questions.translate', [$customSubject, $q]) }}" method="POST" class="flex items-center gap-1">
|
||||
@csrf
|
||||
<select name="target_language" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'selected' : '' }}>PT</option>
|
||||
<option value="en" {{ app()->getLocale() === 'en' ? 'selected' : '' }}>EN</option>
|
||||
<option value="es" {{ app()->getLocale() === 'es' ? 'selected' : '' }}>ES</option>
|
||||
</select>
|
||||
<button type="submit" class="text-xs text-indigo-500 hover:text-indigo-700 font-medium" title="{{ __('Translate') }}">
|
||||
{{ __('Translate') }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route('admin.custom-subjects.questions.destroy', [$customSubject, $q]) }}" method="POST"
|
||||
onsubmit="return confirm('{{ __("Delete this question?") }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("Insights"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("My Insights") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Token Usage Summary -->
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-xl shadow-sm p-6 mb-6 text-white">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<p class="text-indigo-100 text-sm">{{ __("Total Input Tokens") }}</p>
|
||||
<p class="text-2xl font-bold">{{ number_format($totalInputTokens) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-indigo-100 text-sm">{{ __("Total Output Tokens") }}</p>
|
||||
<p class="text-2xl font-bold">{{ number_format($totalOutputTokens) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-indigo-100 text-sm">{{ __("Total Tokens Used") }}</p>
|
||||
<p class="text-2xl font-bold">{{ number_format($totalTokens) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generate Message -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Generate AI Message") }}</h3>
|
||||
<form action="{{ route("admin.insights.generate") }}" method="POST" class="space-y-4">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="subject_id" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Subject") }}</label>
|
||||
<select name="subject_id" id="subject_id" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="">{{ __("Select a subject...") }}</option>
|
||||
@foreach($subjects as $subject)
|
||||
<option value="{{ $subject->id }}" {{ $subjectId == $subject->id ? 'selected' : '' }}>
|
||||
{{ $subject->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="message_type" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Message Type") }}</label>
|
||||
<select name="message_type" id="message_type"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
@foreach(\App\Enums\MessageType::cases() as $case)
|
||||
@if(in_array($case->value, ['insight', 'language_lesson', 'game_simulator', 'technical_teaching']))
|
||||
<option value="{{ $case->value }}">{{ $case->label() }}</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="custom_prompt" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Custom Prompt (optional)") }}</label>
|
||||
<input type="text" name="custom_prompt" id="custom_prompt"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., Give me a practical tip about...") }}">
|
||||
</div>
|
||||
<div class="md:col-span-3">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Response Language") }}</label>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="language" value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'checked' : '' }}
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-700">Português (Brasil)</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="language" value="en" {{ app()->getLocale() === 'en' ? 'checked' : '' }}
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-700">English</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="language" value="es" {{ app()->getLocale() === 'es' ? 'checked' : '' }}
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-700">Español</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
{{ __("Generate Message") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="text-sm font-medium text-gray-700">{{ __("Filter by Subject") }}</label>
|
||||
<select onchange="window.location.href = '{{ route("admin.insights.index") }}?subject_id=' + this.value"
|
||||
class="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm">
|
||||
<option value="">{{ __("All Subjects") }}</option>
|
||||
@foreach($subjects as $subject)
|
||||
<option value="{{ $subject->id }}" {{ $subjectId == $subject->id ? 'selected' : '' }}>
|
||||
{{ $subject->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Insights List -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Messages History") }}</h3>
|
||||
|
||||
@forelse($insights as $insight)
|
||||
<div class="border-b border-gray-100 pb-4 mb-4 last:border-0 last:mb-0 last:pb-0">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
|
||||
{{ $insight->subject->name }}
|
||||
</span>
|
||||
<span class="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">
|
||||
{{ $insight->type->label() }}
|
||||
</span>
|
||||
@if($insight->input_tokens > 0 || $insight->output_tokens > 0)
|
||||
<span class="text-xs text-gray-400">
|
||||
🔤 {{ number_format($insight->input_tokens + $insight->output_tokens) }} tokens
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-sm text-gray-900 prose prose-sm max-w-none">
|
||||
{!! \App\Services\MarkdownService::parse($insight->content) !!}
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $insight->created_at->format('d/m/Y H:i') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<form action="{{ route("admin.insights.translate", $insight) }}" method="POST" class="flex items-center gap-1">
|
||||
@csrf
|
||||
<select name="target_language" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'selected' : '' }}>PT</option>
|
||||
<option value="en" {{ app()->getLocale() === 'en' ? 'selected' : '' }}>EN</option>
|
||||
<option value="es" {{ app()->getLocale() === 'es' ? 'selected' : '' }}>ES</option>
|
||||
</select>
|
||||
<button type="submit" class="text-xs text-indigo-500 hover:text-indigo-700 font-medium" title="{{ __('Translate') }}">
|
||||
{{ __('Translate') }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route("admin.insights.destroy", $insight) }}" method="POST"
|
||||
onsubmit="return confirm('{{ __("Delete this insight?") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-8">
|
||||
<svg class="w-12 h-12 text-gray-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
<p class="text-gray-500">{{ __("No messages yet. Generate your first message above!") }}</p>
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
@if(method_exists($insights, 'links'))
|
||||
<div class="mt-6">
|
||||
{{ $insights->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
(function() {
|
||||
if (!('speechSynthesis' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var englishVoice = null;
|
||||
|
||||
function loadVoices() {
|
||||
var voices = speechSynthesis.getVoices();
|
||||
englishVoice = voices.find(function(v) {
|
||||
return v.lang === 'en-US' || v.lang === 'en_GB' || v.lang.startsWith('en-');
|
||||
}) || null;
|
||||
}
|
||||
|
||||
loadVoices();
|
||||
if (speechSynthesis.onvoiceschanged !== undefined) {
|
||||
speechSynthesis.onvoiceschanged = loadVoices;
|
||||
}
|
||||
|
||||
function attachAudioButtons() {
|
||||
document.querySelectorAll('.tts[data-text]:not([data-tts-attached="1"])').forEach(function(el) {
|
||||
el.dataset.ttsAttached = '1';
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.innerHTML = '🎤';
|
||||
btn.title = @json(__('Listen'));
|
||||
btn.className = 'ml-1 inline-flex items-center justify-center w-6 h-6 rounded-full bg-indigo-50 text-indigo-600 hover:bg-indigo-100 text-xs';
|
||||
|
||||
var isSpeaking = false;
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var text = el.getAttribute('data-text') || el.textContent || '';
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!('speechSynthesis' in window)) {
|
||||
copyFallback(text);
|
||||
return;
|
||||
}
|
||||
|
||||
var voices = speechSynthesis.getVoices();
|
||||
if (!voices || !voices.length) {
|
||||
copyFallback(text);
|
||||
return;
|
||||
}
|
||||
|
||||
speechSynthesis.cancel();
|
||||
var utter = new SpeechSynthesisUtterance(text);
|
||||
var locale = @json(app()->getLocale());
|
||||
var langMap = {
|
||||
'pt_BR': 'pt-BR',
|
||||
'en': 'en-US',
|
||||
'es': 'es-ES'
|
||||
};
|
||||
utter.lang = langMap[locale] || 'en-US';
|
||||
utter.rate = 0.9;
|
||||
if (englishVoice) {
|
||||
utter.voice = englishVoice;
|
||||
}
|
||||
utter.onerror = function(e) {
|
||||
console.error('Speech error:', e);
|
||||
copyFallback(text);
|
||||
isSpeaking = false;
|
||||
btn.classList.remove('bg-green-50', 'text-green-600');
|
||||
btn.classList.add('bg-indigo-50', 'text-indigo-600');
|
||||
};
|
||||
utter.onend = function() {
|
||||
isSpeaking = false;
|
||||
btn.classList.remove('bg-green-50', 'text-green-600');
|
||||
btn.classList.add('bg-indigo-50', 'text-indigo-600');
|
||||
};
|
||||
speechSynthesis.speak(utter);
|
||||
isSpeaking = true;
|
||||
btn.classList.remove('bg-indigo-50', 'text-indigo-600');
|
||||
btn.classList.add('bg-green-50', 'text-green-600');
|
||||
} catch (e) {
|
||||
console.error('TTS error:', e);
|
||||
copyFallback(text);
|
||||
}
|
||||
});
|
||||
|
||||
function copyFallback(text) {
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert(@json(__('No voices available. Text copied to listen elsewhere: ')) + text);
|
||||
}).catch(function() {
|
||||
prompt(@json(__('No voices available. Copy the text to listen:')), text);
|
||||
});
|
||||
} else {
|
||||
prompt(@json(__('No voices available. Copy the text to listen:')), text);
|
||||
}
|
||||
}
|
||||
|
||||
el.insertAdjacentElement('afterend', btn);
|
||||
});
|
||||
}
|
||||
|
||||
attachAudioButtons();
|
||||
|
||||
if (window.MutationObserver) {
|
||||
var observer = new MutationObserver(function() {
|
||||
attachAudioButtons();
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("My Interests"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("My Interests") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Left column: My Interests -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Add Interest") }}</h3>
|
||||
<form method="POST" action="{{ route("admin.interests.store") }}">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="interest" class="block text-sm font-medium text-gray-700 mb-2">{{ __("What topic are you interested in?") }}</label>
|
||||
<input type="text" name="interest" id="interest" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., Business, Technology, Marketing") }}">
|
||||
</div>
|
||||
<button type="submit" class="w-full bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Add Interest") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("My Interests") }}</h3>
|
||||
@if($interests->count() > 0)
|
||||
<div class="space-y-2">
|
||||
@foreach($interests as $interest)
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<span class="text-sm text-gray-700">{{ $interest->interest }}</span>
|
||||
<form action="{{ route("admin.interests.destroy", $interest) }}" method="POST" onsubmit="return confirm('{{ __("Are you sure?") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-800 font-medium">
|
||||
{{ __("Remove") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-500">{{ __("You have not added any interests yet.") }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right column: AI Suggestions -->
|
||||
<div class="lg:col-span-2">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __("AI Suggestions") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Based on your interests, the AI suggests the following subjects") }}</p>
|
||||
</div>
|
||||
<form action="{{ route("admin.interests.generateSuggestions") }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-purple-600 text-white text-sm font-medium rounded-lg hover:bg-purple-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
{{ __("Generate Suggestions") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if(session("suggestions"))
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@foreach(session("suggestions") as $suggestion)
|
||||
<div class="border border-gray-200 rounded-lg p-5 hover:shadow-md transition">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<h4 class="font-semibold text-gray-900">{{ $suggestion["name"] ?? "" }}</h4>
|
||||
<span class="text-xs bg-purple-100 text-purple-800 px-2 py-1 rounded-full font-medium">{{ __("Suggestion") }}</span>
|
||||
</div>
|
||||
@if(!empty($suggestion["description"]))
|
||||
<p class="text-sm text-gray-600 mb-3">{{ $suggestion["description"] }}</p>
|
||||
@endif
|
||||
@if(!empty($suggestion["introduction"]))
|
||||
<p class="text-xs text-gray-500 italic mb-3">{{ \Illuminate\Support\Str::limit($suggestion["introduction"], 100) }}</p>
|
||||
@endif
|
||||
<form action="{{ route("admin.interests.createSubject") }}" method="POST" class="mt-3">
|
||||
@csrf
|
||||
<input type="hidden" name="name" value="{{ $suggestion["name"] ?? "" }}">
|
||||
<input type="hidden" name="description" value="{{ $suggestion["description"] ?? "" }}">
|
||||
<input type="hidden" name="introduction" value="{{ $suggestion["introduction"] ?? "" }}">
|
||||
<button type="submit" class="w-full text-center text-sm bg-purple-600 text-white px-3 py-2 rounded-lg hover:bg-purple-700 transition font-medium">
|
||||
{{ __("Create Automatically with AI") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="text-center py-12">
|
||||
<svg class="w-12 h-12 text-gray-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
<p class="text-gray-500">{{ __("Click 'Generate Suggestions' to receive AI-powered subject ideas based on your interests.") }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+825
@@ -0,0 +1,825 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("Subjects"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("My Subjects") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("error"))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("error") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- System Subjects -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ __("System Subjects") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Pre-registered subjects available to all users") }}</p>
|
||||
</div>
|
||||
@role('superadmin')
|
||||
<button onclick="document.getElementById('generateAiSubjectModal').classList.remove('hidden')"
|
||||
class="inline-flex items-center px-4 py-2 bg-purple-600 text-white text-sm font-medium rounded-lg hover:bg-purple-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
{{ __("Generate with AI") }}
|
||||
</button>
|
||||
@endrole
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@forelse($systemSubjects as $subject)
|
||||
@php
|
||||
$userSubject = $subject->userSubjects->first();
|
||||
@endphp
|
||||
<div class="border border-gray-200 rounded-lg p-5 hover:shadow-md transition">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<h4 class="font-semibold text-gray-900">{{ $subject->name }}</h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="openEditSystemSubjectModal({{ $subject->id }}, '{{ addslashes($subject->name) }}', '{{ addslashes($subject->description ?? '') }}', '{{ $subject->message_type ?? '' }}', '{{ addslashes($subject->custom_instructions ?? '') }}', {{ $subject->is_active ? 'true' : 'false' }})"
|
||||
class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
|
||||
{{ __("Edit") }}
|
||||
</button>
|
||||
<form action="{{ route("admin.subjects.destroy", $subject) }}" method="POST" onsubmit="return confirm('{{ __("Are you sure?") }}')" class="inline">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-800 font-medium">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full font-medium">{{ __("System") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@if($subject->description)
|
||||
<p class="text-sm text-gray-600 mb-3">{{ $subject->description }}</p>
|
||||
@endif
|
||||
@if($subject->introduction)
|
||||
<p class="text-xs text-gray-500 italic mb-3">{{ Str::limit($subject->introduction, 100) }}</p>
|
||||
@endif
|
||||
|
||||
@if($userSubject)
|
||||
<div class="mt-4 pt-3 border-t border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full {{ $userSubject->is_active ? 'bg-green-500' : 'bg-gray-300' }}"></span>
|
||||
<span class="text-sm {{ $userSubject->is_active ? 'text-green-700' : 'text-gray-500' }}">
|
||||
{{ $userSubject->is_active ? __("Active") : __("Inactive") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openEditScheduleModal({{ $userSubject->id }}, '{{ addslashes($userSubject->frequency) }}', {{ json_encode($userSubject->preferred_times ?? ['09:00']) }}, {{ json_encode($userSubject->weekdays ?? []) }}, '{{ $userSubject->insight_language ?? 'en' }}', {{ json_encode($userSubject->delivery_channels ?? ['dashboard']) }})"
|
||||
class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
|
||||
{{ __("Edit Schedule") }}
|
||||
</button>
|
||||
<form action="{{ route("admin.subjects.toggle", $userSubject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="is_active" value="{{ $userSubject->is_active ? '0' : '1' }}">
|
||||
<button type="submit" class="text-xs {{ $userSubject->is_active ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800' }} font-medium">
|
||||
{{ $userSubject->is_active ? __("Deactivate") : __("Activate") }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route("admin.subjects.unsubscribe", $userSubject) }}" method="POST" onsubmit="return confirm('{{ __("Are you sure?") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-800 font-medium">
|
||||
{{ __("Unsubscribe") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@if($userSubject->frequency)
|
||||
<div class="mt-2 text-xs text-gray-500">
|
||||
{{ ucfirst($userSubject->frequency) }}
|
||||
@if($userSubject->preferred_times)
|
||||
| {{ $userSubject->formatted_times }}
|
||||
@endif
|
||||
@if($userSubject->weekdays)
|
||||
| {{ implode(', ', array_map(function($day) {
|
||||
return ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][$day] ?? $day;
|
||||
}, $userSubject->weekdays)) }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<div class="mt-3 flex gap-2">
|
||||
<form action="{{ route("admin.subjects.send-insight", $subject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="message_type" value="{{ $subject->message_type ?? 'insight' }}">
|
||||
<button type="submit" class="flex-1 text-xs bg-indigo-600 text-white px-3 py-1.5 rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
@php
|
||||
$label = match($subject->message_type) {
|
||||
'language_lesson' => __('Send Lesson'),
|
||||
'game_simulator' => __('Send Game'),
|
||||
'technical_teaching' => __('Send Teaching'),
|
||||
default => __('Send Insight'),
|
||||
};
|
||||
@endphp
|
||||
{{ $label }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route("admin.subjects.send-introduction", $subject) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="flex-1 text-xs bg-green-600 text-white px-3 py-1.5 rounded-lg hover:bg-green-700 transition font-medium">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
{{ __("Introduction") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="mt-4 pt-3 border-t border-gray-100">
|
||||
<button onclick="openSubscribeModal({{ $subject->id }}, '{{ addslashes($subject->name) }}')"
|
||||
class="w-full text-center text-sm bg-indigo-600 text-white px-3 py-2 rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Subscribe") }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<div class="col-span-full text-center py-8 text-gray-500">
|
||||
{{ __("No system subjects available.") }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- My Custom Subjects -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ __("My Custom Subjects") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Subjects you created for your own learning") }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="document.getElementById('generateAdminAiSubjectModal').classList.remove('hidden')"
|
||||
class="inline-flex items-center px-4 py-2 bg-purple-600 text-white text-sm font-medium rounded-lg hover:bg-purple-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
{{ __("Generate with AI") }}
|
||||
</button>
|
||||
<button onclick="document.getElementById('createSubjectModal').classList.remove('hidden')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
{{ __("Create Subject") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($mySubjects->count() > 0)
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@foreach($mySubjects as $userSubject)
|
||||
@php $subject = $userSubject->subject; @endphp
|
||||
<div class="border border-gray-200 rounded-lg p-5 hover:shadow-md transition">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<h4 class="font-semibold text-gray-900">{{ $subject->name }}</h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="openEditSystemSubjectModal({{ $subject->id }}, '{{ addslashes($subject->name) }}', '{{ addslashes($subject->description ?? '') }}', '{{ $subject->message_type ?? '' }}', '{{ addslashes($subject->custom_instructions ?? '') }}', {{ $subject->is_active ? 'true' : 'false' }})"
|
||||
class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
|
||||
{{ __("Edit") }}
|
||||
</button>
|
||||
<span class="text-xs bg-purple-100 text-purple-800 px-2 py-1 rounded-full font-medium">{{ __("Custom") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@if($subject->description)
|
||||
<p class="text-sm text-gray-600 mb-3">{{ $subject->description }}</p>
|
||||
@endif
|
||||
@if($subject->introduction)
|
||||
<p class="text-xs text-gray-500 italic mb-3">{{ Str::limit($subject->introduction, 100) }}</p>
|
||||
@endif
|
||||
<div class="mt-4 pt-3 border-t border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full {{ $userSubject->is_active ? 'bg-green-500' : 'bg-gray-300' }}"></span>
|
||||
<span class="text-sm {{ $userSubject->is_active ? 'text-green-700' : 'text-gray-500' }}">
|
||||
{{ $userSubject->is_active ? __("Active") : __("Inactive") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openEditScheduleModal({{ $userSubject->id }}, '{{ addslashes($userSubject->frequency) }}', {{ json_encode($userSubject->preferred_times ?? ['09:00']) }}, {{ json_encode($userSubject->weekdays ?? []) }}, '{{ $userSubject->insight_language ?? 'en' }}')"
|
||||
class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
|
||||
{{ __("Edit Schedule") }}
|
||||
</button>
|
||||
<form action="{{ route("admin.subjects.toggle", $userSubject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="is_active" value="{{ $userSubject->is_active ? '0' : '1' }}">
|
||||
<button type="submit" class="text-xs {{ $userSubject->is_active ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800' }} font-medium">
|
||||
{{ $userSubject->is_active ? __("Deactivate") : __("Activate") }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route("admin.subjects.destroy", $subject) }}" method="POST" onsubmit="return confirm('{{ __("Are you sure?") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-800 font-medium">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@if($userSubject->frequency)
|
||||
<div class="mt-2 text-xs text-gray-500">
|
||||
{{ ucfirst($userSubject->frequency) }}
|
||||
@if($userSubject->preferred_times)
|
||||
| {{ $userSubject->formatted_times }}
|
||||
@endif
|
||||
@if($userSubject->weekdays)
|
||||
| {{ implode(', ', array_map(function($day) {
|
||||
return ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][$day] ?? $day;
|
||||
}, $userSubject->weekdays)) }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<div class="mt-3 flex gap-2">
|
||||
<form action="{{ route("admin.subjects.send-insight", $subject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="message_type" value="{{ $subject->message_type ?? 'insight' }}">
|
||||
<button type="submit" class="flex-1 text-xs bg-indigo-600 text-white px-3 py-1.5 rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
@php
|
||||
$label = match($subject->message_type) {
|
||||
'language_lesson' => __('Send Lesson'),
|
||||
'game_simulator' => __('Send Game'),
|
||||
'technical_teaching' => __('Send Teaching'),
|
||||
default => __('Send Insight'),
|
||||
};
|
||||
@endphp
|
||||
{{ $label }}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route("admin.subjects.send-introduction", $subject) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="flex-1 text-xs bg-green-600 text-white px-3 py-1.5 rounded-lg hover:bg-green-700 transition font-medium">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
{{ __("Introduction") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="text-center py-8">
|
||||
<svg class="w-12 h-12 text-gray-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
<p class="text-gray-500">{{ __("You have not created any custom subjects yet.") }}</p>
|
||||
<button onclick="document.getElementById('createSubjectModal').classList.remove('hidden')"
|
||||
class="mt-3 text-indigo-600 hover:text-indigo-800 text-sm font-medium">
|
||||
{{ __("Create your first subject") }} →
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subscribe Modal -->
|
||||
<div id="subscribeModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Subscribe to Subject") }}</h3>
|
||||
<button onclick="closeSubscribeModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="subscribeForm" method="POST">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Subject") }}</label>
|
||||
<p id="subscribeSubjectName" class="text-gray-900 font-medium"></p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="frequency" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Frequency") }}</label>
|
||||
<select name="frequency" id="frequency" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="daily">{{ __("Daily") }}</option>
|
||||
<option value="weekly">{{ __("Weekly") }}</option>
|
||||
<option value="biweekly">{{ __("Biweekly") }}</option>
|
||||
<option value="monthly">{{ __("Monthly") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Preferred Times") }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __("Add one or more times for insight delivery") }}</p>
|
||||
<div id="timesContainer">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="addTimeField()"
|
||||
class="px-3 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition text-sm font-medium">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Days of Week") }}</label>
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
@foreach([['value' => 1, 'label' => 'Seg'], ['value' => 2, 'label' => 'Ter'], ['value' => 3, 'label' => 'Qua'], ['value' => 4, 'label' => 'Qui'], ['value' => 5, 'label' => 'Sex'], ['value' => 6, 'label' => 'Sáb'], ['value' => 0, 'label' => 'Dom']] as $day)
|
||||
<label class="flex flex-col items-center">
|
||||
<input type="checkbox" name="weekdays[]" value="{{ $day['value'] }}"
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span class="text-xs text-gray-500 mt-1">{{ $day['label'] }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="insight_language" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Insight Language") }}</label>
|
||||
<select name="insight_language" id="insight_language" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR">🇧🇷 {{ __("Português (Brasil)") }}</option>
|
||||
<option value="en" selected>🇺🇸 {{ __("English") }}</option>
|
||||
<option value="es">🇪🇸 {{ __("Español") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Delivery Channels") }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __("Choose where to receive insights") }}</p>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="delivery_channels[]" value="dashboard" checked disabled
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Dashboard") }} <span class="text-xs text-gray-400">({{ __("always enabled") }})</span></span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="delivery_channels[]" value="telegram"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Telegram Bot") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="closeSubscribeModal()"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Subscribe") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Schedule Modal -->
|
||||
<div id="editScheduleModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Edit Schedule") }}</h3>
|
||||
<button onclick="closeEditScheduleModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="editScheduleForm" method="POST">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
<div class="mb-4">
|
||||
<label for="edit_frequency" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Frequency") }}</label>
|
||||
<select name="frequency" id="edit_frequency" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="daily">{{ __("Daily") }}</option>
|
||||
<option value="weekly">{{ __("Weekly") }}</option>
|
||||
<option value="biweekly">{{ __("Biweekly") }}</option>
|
||||
<option value="monthly">{{ __("Monthly") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Preferred Times") }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __("Add one or more times for insight delivery") }}</p>
|
||||
<div id="editTimesContainer">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="addEditTimeField()"
|
||||
class="px-3 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition text-sm font-medium">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Days of Week") }}</label>
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
@foreach([['value' => 1, 'label' => 'Seg'], ['value' => 2, 'label' => 'Ter'], ['value' => 3, 'label' => 'Qua'], ['value' => 4, 'label' => 'Qui'], ['value' => 5, 'label' => 'Sex'], ['value' => 6, 'label' => 'Sáb'], ['value' => 0, 'label' => 'Dom']] as $day)
|
||||
<label class="flex flex-col items-center">
|
||||
<input type="checkbox" name="weekdays[]" value="{{ $day['value'] }}"
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 edit-weekday">
|
||||
<span class="text-xs text-gray-500 mt-1">{{ $day['label'] }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_insight_language" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Insight Language") }}</label>
|
||||
<select name="insight_language" id="edit_insight_language" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR">🇧🇷 {{ __("Português (Brasil)") }}</option>
|
||||
<option value="en">🇺🇸 {{ __("English") }}</option>
|
||||
<option value="es">🇪🇸 {{ __("Español") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("Delivery Channels") }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __("Choose where to receive insights") }}</p>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="delivery_channels[]" value="dashboard" checked disabled
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 edit-channel-dashboard">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Dashboard") }} <span class="text-xs text-gray-400">({{ __("always enabled") }})</span></span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="delivery_channels[]" value="telegram"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 edit-channel-telegram">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Telegram Bot") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="closeEditScheduleModal()"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Update Schedule") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Subject Modal -->
|
||||
<div id="createSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Create New Subject") }}</h3>
|
||||
<button onclick="document.getElementById('createSubjectModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route("admin.subjects.store") }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Name") }} *</label>
|
||||
<input type="text" name="name" id="name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., Aprendizado de Python") }}">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Description") }}</label>
|
||||
<textarea name="description" id="description" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("Brief description of the subject") }}"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="introduction" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Introduction") }}</label>
|
||||
<textarea name="introduction" id="introduction" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("Detailed introduction to the subject for AI context") }}"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="message_type" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Message Type") }}</label>
|
||||
<select name="message_type" id="message_type"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="">{{ __("Default") }}</option>
|
||||
<option value="insight">Insight</option>
|
||||
<option value="language_lesson">Language Lesson</option>
|
||||
<option value="game_simulator">Game / Simulator</option>
|
||||
<option value="technical_teaching">Technical Teaching</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="custom_instructions" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Custom Instructions (optional)") }}</label>
|
||||
<textarea name="custom_instructions" id="custom_instructions" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("Additional instructions for the AI...") }}"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="document.getElementById('createSubjectModal').classList.add('hidden')"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Create") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit System Subject Modal -->
|
||||
<div id="editSystemSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Edit System Subject") }}</h3>
|
||||
<button onclick="document.getElementById('editSystemSubjectModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="editSystemSubjectForm" method="POST">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
<div class="mb-4">
|
||||
<label for="edit_system_name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Name") }} *</label>
|
||||
<input type="text" name="name" id="edit_system_name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_system_description" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Description") }}</label>
|
||||
<textarea name="description" id="edit_system_description" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_system_message_type" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Message Type") }}</label>
|
||||
<select name="message_type" id="edit_system_message_type"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="">{{ __("Default") }}</option>
|
||||
<option value="insight">Insight</option>
|
||||
<option value="language_lesson">Language Lesson</option>
|
||||
<option value="game_simulator">Game / Simulator</option>
|
||||
<option value="technical_teaching">Technical Teaching</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_system_custom_instructions" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Custom Instructions (optional)") }}</label>
|
||||
<textarea name="custom_instructions" id="edit_system_custom_instructions" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_active" id="edit_system_is_active" value="1"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Active") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="document.getElementById('editSystemSubjectModal').classList.add('hidden')"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Update") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generate AI System Subject Modal -->
|
||||
<div id="generateAiSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Create System Subject with AI") }}</h3>
|
||||
<button onclick="document.getElementById('generateAiSubjectModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="generateAiSubjectForm" method="POST" action="{{ route('superadmin.system.subjects.generateAi') }}">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="ai_theme" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Theme / Topic") }}</label>
|
||||
<input type="text" name="theme" id="ai_theme" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., Gurus dos Negócios e Riqueza") }}">
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="document.getElementById('generateAiSubjectModal').classList.add('hidden')"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition font-medium">
|
||||
{{ __("Generate with AI") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push("scripts")
|
||||
<script>
|
||||
function openSubscribeModal(subjectId, subjectName) {
|
||||
document.getElementById('subscribeSubjectName').textContent = subjectName;
|
||||
document.getElementById('subscribeForm').action = '{{ url("admin/subjects") }}/' + subjectId + '/subscribe';
|
||||
// Reset times container
|
||||
document.getElementById('timesContainer').innerHTML = `
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="addTimeField()"
|
||||
class="px-3 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition text-sm font-medium">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('subscribeModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function addTimeField() {
|
||||
const container = document.getElementById('timesContainer');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center gap-2 mb-2';
|
||||
div.innerHTML = `
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="this.parentElement.remove()"
|
||||
class="px-3 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 transition text-sm font-medium">
|
||||
×
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function openEditScheduleModal(id, frequency, times, weekdays, language, channels) {
|
||||
// Set form action
|
||||
document.getElementById('editScheduleForm').action = '{{ url("admin/subjects") }}/' + id + '/schedule';
|
||||
|
||||
// Set frequency
|
||||
document.getElementById('edit_frequency').value = frequency;
|
||||
|
||||
// Set language
|
||||
if (document.getElementById('edit_insight_language')) {
|
||||
document.getElementById('edit_insight_language').value = language || 'en';
|
||||
}
|
||||
|
||||
// Build times container
|
||||
const container = document.getElementById('editTimesContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (times && times.length > 0) {
|
||||
times.forEach(function(time, index) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center gap-2 mb-2';
|
||||
const timeVal = typeof time === 'string' ? time.substring(0, 5) : '09:00';
|
||||
div.innerHTML = `
|
||||
<input type="time" name="preferred_times[]" value="${timeVal}" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
${index === 0
|
||||
? `<button type="button" onclick="addEditTimeField()"
|
||||
class="px-3 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition text-sm font-medium">+</button>`
|
||||
: `<button type="button" onclick="this.parentElement.remove()"
|
||||
class="px-3 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 transition text-sm font-medium">×</button>`
|
||||
}
|
||||
`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
} else {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center gap-2 mb-2';
|
||||
div.innerHTML = `
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="addEditTimeField()"
|
||||
class="px-3 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition text-sm font-medium">+</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
// Set weekdays (compare as strings to match DB storage format)
|
||||
document.querySelectorAll('.edit-weekday').forEach(function(cb) {
|
||||
cb.checked = weekdays && weekdays.some(function(d) {
|
||||
return String(d) === cb.value;
|
||||
});
|
||||
});
|
||||
|
||||
// Set delivery channels (default to ['dashboard'] if not set)
|
||||
var channelsArr = channels;
|
||||
if (!channelsArr || !Array.isArray(channelsArr) || channelsArr.length === 0) {
|
||||
channelsArr = ['dashboard'];
|
||||
}
|
||||
document.querySelectorAll('.edit-channel-telegram').forEach(function(cb) {
|
||||
cb.checked = channelsArr.includes('telegram');
|
||||
});
|
||||
|
||||
document.getElementById('editScheduleModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function addEditTimeField() {
|
||||
const container = document.getElementById('editTimesContainer');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center gap-2 mb-2';
|
||||
div.innerHTML = `
|
||||
<input type="time" name="preferred_times[]" value="09:00" required
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="this.parentElement.remove()"
|
||||
class="px-3 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 transition text-sm font-medium">
|
||||
×
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function closeEditScheduleModal() {
|
||||
document.getElementById('editScheduleModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function closeSubscribeModal() {
|
||||
document.getElementById('subscribeModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Close modals on Escape key
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
document.getElementById('subscribeModal').classList.add('hidden');
|
||||
document.getElementById('createSubjectModal').classList.add('hidden');
|
||||
document.getElementById('editScheduleModal').classList.add('hidden');
|
||||
document.getElementById('editSystemSubjectModal').classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
function openEditSystemSubjectModal(id, name, description, messageType, customInstructions, isActive) {
|
||||
document.getElementById('edit_system_name').value = name;
|
||||
document.getElementById('edit_system_description').value = description;
|
||||
document.getElementById('edit_system_message_type').value = messageType;
|
||||
document.getElementById('edit_system_custom_instructions').value = customInstructions;
|
||||
document.getElementById('edit_system_is_active').checked = isActive;
|
||||
document.getElementById('editSystemSubjectForm').action = '{{ url("admin/subjects") }}/' + id;
|
||||
document.getElementById('editSystemSubjectModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeGenerateAdminAiSubjectModal() {
|
||||
document.getElementById('generateAdminAiSubjectModal').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<!-- Generate AI Subject Modal -->
|
||||
<div id="generateAdminAiSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Generate Subject with AI") }}</h3>
|
||||
<button onclick="closeGenerateAdminAiSubjectModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('admin.subjects.generateAi') }}">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="admin_ai_theme" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Theme / Topic") }}</label>
|
||||
<input type="text" name="theme" id="admin_ai_theme" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., Marketing Digital e Vendas") }}">
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="closeGenerateAdminAiSubjectModal()"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition font-medium">
|
||||
{{ __("Generate with AI") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Telegram Integration'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Telegram Integration') }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Configure Bot -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Configure Your Bot') }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ __('Set up your own Telegram bot to receive insights.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">{{ __('How to create your bot:') }}</h4>
|
||||
<ol class="text-sm text-gray-600 space-y-2 list-decimal list-inside">
|
||||
<li>{{ __('Open Telegram and search for') }} <strong>@BotFather</strong></li>
|
||||
<li>{{ __('Send') }} <code>/newbot</code> {{ __('and follow the instructions') }}</li>
|
||||
<li>{{ __('Copy the HTTP API token provided by BotFather') }}</li>
|
||||
<li>{{ __('Start a chat with your new bot and send') }} <code>/start</code></li>
|
||||
<li>{{ __('Get your Chat ID (use @userinfobot or similar)') }}</li>
|
||||
<li>{{ __('Fill in the fields below and click Connect') }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('admin.telegram.connect') }}" class="space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="bot_token" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
{{ __('Bot Token') }} *
|
||||
</label>
|
||||
<input type="text" name="bot_token" id="bot_token" required
|
||||
value="{{ old('bot_token', $telegramUser->bot_token ?? '') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono text-sm"
|
||||
placeholder="1234567890:ABCdefGHIjklMNOpqrsTUVwxyz">
|
||||
</div>
|
||||
<div>
|
||||
<label for="bot_username" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
{{ __('Bot Username (optional)') }}
|
||||
</label>
|
||||
<input type="text" name="bot_username" id="bot_username"
|
||||
value="{{ old('bot_username', $telegramUser->bot_username ?? '') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="@your_bot">
|
||||
</div>
|
||||
<div>
|
||||
<label for="chat_id" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
{{ __('Chat ID') }} *
|
||||
</label>
|
||||
<input type="text" name="chat_id" id="chat_id" required
|
||||
value="{{ old('chat_id', $telegramUser->chat_id ?? '') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono text-sm"
|
||||
placeholder="123456789">
|
||||
</div>
|
||||
<div>
|
||||
<label for="telegram_username" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
{{ __('Your Telegram Username (optional)') }}
|
||||
</label>
|
||||
<input type="text" name="telegram_username" id="telegram_username"
|
||||
value="{{ old('telegram_username', $telegramUser->telegram_username ?? '') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="@your_username">
|
||||
</div>
|
||||
<button type="submit" class="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-medium">
|
||||
{{ $telegramUser && $telegramUser->isFullyConfigured() ? __('Update Bot Configuration') : __('Connect Bot') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Connection Status & Test -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-12 h-12 {{ $telegramUser && $telegramUser->isFullyConfigured() ? 'bg-green-100' : 'bg-gray-100' }} rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 {{ $telegramUser && $telegramUser->isFullyConfigured() ? 'text-green-600' : 'text-gray-400' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Connection Status') }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ __('Your bot configuration status.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($telegramUser && $telegramUser->isFullyConfigured())
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-3 h-3 rounded-full bg-green-500"></span>
|
||||
<span class="text-sm font-medium text-green-800">{{ __('Bot Configured') }}</span>
|
||||
</div>
|
||||
@if($telegramUser->bot_username)
|
||||
<p class="text-sm text-green-700">{{ __('Bot: :username', ['username' => '@' . ltrim($telegramUser->bot_username, '@')]) }}</p>
|
||||
@endif
|
||||
<p class="text-sm text-green-700">{{ __('Chat ID: :id', ['id' => $telegramUser->chat_id]) }}</p>
|
||||
<p class="text-sm text-green-700">{{ __('Connected since: :date', ['date' => $telegramUser->created_at->format('d/m/Y H:i')]) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-4">
|
||||
<!-- Test Button -->
|
||||
<form method="POST" action="{{ route('admin.telegram.test') }}" class="flex-1">
|
||||
@csrf
|
||||
<button type="submit" class="w-full px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition font-medium">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
{{ __('Send Test Message') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Disconnect Button -->
|
||||
<form method="POST" action="{{ route('admin.telegram.disconnect') }}">
|
||||
@csrf
|
||||
<button type="submit" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition font-medium"
|
||||
onclick="return confirm('{{ __('Disconnect Telegram bot?') }}')">
|
||||
{{ __('Disconnect') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-3 h-3 rounded-full bg-gray-400"></span>
|
||||
<span class="text-sm font-medium text-gray-600">{{ __('Not Configured') }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">{{ __('Create your own Telegram bot and configure it here to receive insights.') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-indigo-50 border border-indigo-100 rounded-lg p-4">
|
||||
<h4 class="text-sm font-medium text-indigo-800 mb-2">{{ __('Why use your own bot?') }}</h4>
|
||||
<ul class="text-sm text-indigo-700 space-y-1">
|
||||
<li>✓ {{ __('Full control over your notifications') }}</li>
|
||||
<li>✓ {{ __('No shared bots - completely private') }}</li>
|
||||
<li>✓ {{ __('Receive insights in real-time') }}</li>
|
||||
<li>✓ {{ __('Works with your existing bot configuration') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
@extends("layouts.public")
|
||||
|
||||
@section("title", __("auth.Log in"))
|
||||
|
||||
@section("content")
|
||||
<div class="w-full max-w-md">
|
||||
<div class="bg-white rounded-2xl shadow-2xl p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h2 class="text-3xl font-bold text-gray-900">{{ __("auth.Welcome Back") }}</h2>
|
||||
<p class="mt-2 text-gray-600">{{ __("auth.Sign in to your account to continue") }}</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route("login") }}">
|
||||
@csrf
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Email") }}</label>
|
||||
<input type="email" name="email" id="email" value="{{ old("email") }}" required autofocus
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="{{ __('auth.your@email.com') }}">
|
||||
@error("email")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Password") }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password" id="password" required
|
||||
class="w-full px-4 py-3 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="••••••••">
|
||||
<button type="button" onclick="togglePassword('password', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@error("password")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="remember" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<span class="ml-2 text-sm text-gray-600">{{ __("auth.Remember me") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Security check") }}</label>
|
||||
<div class="bg-gray-50 rounded-lg p-3 mb-3">
|
||||
<p class="text-sm text-gray-600 text-center font-medium">{{ session('math_captcha_question') }}</p>
|
||||
</div>
|
||||
<input type="text" name="math_captcha" id="math_captcha" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="{{ __('auth.Enter the answer') }}">
|
||||
@error('math_captcha')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-3 px-4 rounded-lg hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition font-medium">
|
||||
{{ __("auth.Log in") }}
|
||||
</button>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-gray-600">
|
||||
{{ __("auth.Don't have an account?") }}
|
||||
<a href="{{ route("register") }}" class="text-indigo-600 hover:text-indigo-800 font-medium">{{ __("auth.Register") }}</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function togglePassword(fieldId, btn) {
|
||||
var input = document.getElementById(fieldId);
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
@extends("layouts.public")
|
||||
|
||||
@section("title", __("auth.Register"))
|
||||
|
||||
@section("content")
|
||||
<div class="w-full max-w-md">
|
||||
<div class="bg-white rounded-2xl shadow-2xl p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h2 class="text-3xl font-bold text-gray-900">{{ __("auth.Create Account") }}</h2>
|
||||
<p class="mt-2 text-gray-600">{{ __("auth.Join Annunk and start your learning journey") }}</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route("register") }}">
|
||||
@csrf
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Name") }}</label>
|
||||
<input type="text" name="name" id="name" value="{{ old("name") }}" required autofocus autocomplete="name"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="{{ __('auth.Your full name') }}">
|
||||
@error("name")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Email") }}</label>
|
||||
<input type="email" name="email" id="email" value="{{ old("email") }}" required autocomplete="username"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="{{ __('auth.your@email.com') }}">
|
||||
@error("email")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Password") }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password" id="password" required autocomplete="new-password"
|
||||
class="w-full px-4 py-3 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="••••••••">
|
||||
<button type="button" onclick="togglePassword('password', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@error("password")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password_confirmation" class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Confirm Password") }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password_confirmation" id="password_confirmation" required autocomplete="new-password"
|
||||
class="w-full px-4 py-3 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="••••••••">
|
||||
<button type="button" onclick="togglePassword('password_confirmation', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@error("password_confirmation")
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __("auth.Security check") }}</label>
|
||||
<div class="bg-gray-50 rounded-lg p-3 mb-3">
|
||||
<p class="text-sm text-gray-600 text-center font-medium">{{ session('math_captcha_question') }}</p>
|
||||
</div>
|
||||
<input type="text" name="math_captcha" id="math_captcha" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition"
|
||||
placeholder="{{ __('auth.Enter the answer') }}">
|
||||
@error('math_captcha')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-3 px-4 rounded-lg hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition font-medium">
|
||||
{{ __("auth.Create Account") }}
|
||||
</button>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-gray-600">
|
||||
{{ __("auth.Already have an account?") }}
|
||||
<a href="{{ route("login") }}" class="text-indigo-600 hover:text-indigo-800 font-medium">{{ __("auth.Log in") }}</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function togglePassword(fieldId, btn) {
|
||||
var input = document.getElementById(fieldId);
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
@php
|
||||
$cookieAccepted = false;
|
||||
|
||||
if (Auth::check()) {
|
||||
$user = Auth::user();
|
||||
$latestConsent = $user->consents()->orderByDesc('accepted_at')->first();
|
||||
$currentVersions = config('legal', []);
|
||||
if ($latestConsent &&
|
||||
$latestConsent->terms_version === ($currentVersions['terms_version'] ?? '1.0.0') &&
|
||||
$latestConsent->privacy_version === ($currentVersions['privacy_version'] ?? '1.0.0') &&
|
||||
$latestConsent->cookie_version === ($currentVersions['cookie_version'] ?? '1.0.0')
|
||||
) {
|
||||
$cookieAccepted = true;
|
||||
}
|
||||
} else {
|
||||
$cookieAccepted = request()->cookie('cookie_consent_accepted') === '1.0.0';
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if(!$cookieAccepted)
|
||||
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4 shadow-lg z-50">
|
||||
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm sm:text-base">
|
||||
<strong>{{ __('legal.cookie_banner_title') }}</strong>
|
||||
{{ __('legal.cookie_banner_text') }}
|
||||
<a href="{{ route('legal.terms') }}" class="underline hover:text-indigo-300">{{ __('legal.terms_of_use') }}</a>
|
||||
|
|
||||
<a href="{{ route('legal.privacy') }}" class="underline hover:text-indigo-300">{{ __('legal.privacy_policy') }}</a>
|
||||
|
|
||||
<a href="{{ route('legal.cookies') }}" class="underline hover:text-indigo-300">{{ __('legal.cookie_policy') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button onclick="acceptCookies()" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
|
||||
{{ __('legal.cookie_banner_accept') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function acceptCookies() {
|
||||
@if(Auth::check())
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('terms_version', '{{ config('legal.terms_version', '1.0.0') }}');
|
||||
formData.append('privacy_version', '{{ config('legal.privacy_version', '1.0.0') }}');
|
||||
formData.append('cookie_version', '{{ config('legal.cookie_version', '1.0.0') }}');
|
||||
|
||||
fetch('{{ route('legal.cookie-banner.accept') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: formData
|
||||
}).then(() => {
|
||||
document.getElementById('cookie-banner').style.display = 'none';
|
||||
});
|
||||
@else
|
||||
document.cookie = 'cookie_consent_accepted=1.0.0; path=/; max-age=' + (60 * 60 * 24 * 365) + '; SameSite=Lax';
|
||||
document.getElementById('cookie-banner').style.display = 'none';
|
||||
@endif
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
const banner = document.getElementById('cookie-banner');
|
||||
if (banner) {
|
||||
banner.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<div x-data="{ langOpen: false }" class="relative">
|
||||
<button @click="langOpen = !langOpen" @click.away="langOpen = false" type="button"
|
||||
class="flex items-center gap-2 px-3 py-2 text-sm transition {{ $variant ?? 'text-gray-700 hover:text-gray-900' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3.18 17.34" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">{{ strtoupper(app()->getLocale()) }}</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-show="langOpen" x-cloak
|
||||
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50 border border-gray-200">
|
||||
<a href="{{ route('language.switch', 'pt_BR') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'pt_BR' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇧🇷 Português (Brasil)
|
||||
</a>
|
||||
<a href="{{ route('language.switch', 'en') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'en' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇺🇸 English
|
||||
</a>
|
||||
<a href="{{ route('language.switch', 'es') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'es' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇪🇸 Español
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Chat with AI'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Chat with AI') }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Chat Container -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 flex flex-col" style="height: calc(100vh - 250px); min-height: 500px;">
|
||||
<!-- Chat Header -->
|
||||
<div class="p-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Chat with AI') }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ __('Ask anything to the AI models') }}</p>
|
||||
</div>
|
||||
<form action="{{ route('chat.destroy') }}" method="POST" onsubmit="return confirm('{{ __("Clear chat history?") }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-sm text-red-600 hover:text-red-800">
|
||||
{{ __('Clear History') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Warning Banner -->
|
||||
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-yellow-700">
|
||||
{{ __('Do not share sensitive personal data, use profanity, or discuss prohibited topics such as pornography, politics, religion, or prejudice. Violations may result in account restrictions.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Messages -->
|
||||
<div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
@forelse($chatHistory as $chat)
|
||||
<div class="flex flex-col space-y-2">
|
||||
<!-- User Message -->
|
||||
<div class="flex justify-end">
|
||||
<div class="bg-indigo-600 text-white rounded-lg px-4 py-2 max-w-[80%]">
|
||||
<p class="text-sm whitespace-pre-wrap">{{ $chat->message }}</p>
|
||||
<p class="text-xs text-indigo-200 mt-1">{{ $chat->created_at->format('H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AI Response -->
|
||||
@if($chat->response)
|
||||
<div class="flex justify-start">
|
||||
<div class="bg-gray-100 text-gray-800 rounded-lg px-4 py-2 max-w-[80%]">
|
||||
<div class="text-sm prose prose-sm max-w-none">
|
||||
{!! \App\Services\MarkdownService::parse($chat->response) !!}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<p class="text-xs text-gray-400">{{ $chat->created_at->format('H:i') }}</p>
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
<span class="text-xs text-gray-400">{{ ucfirst($chat->provider) }}</span>
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
<span class="text-xs text-gray-400">{{ $chat->model }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
<p class="text-gray-500 text-lg">{{ __('Start a conversation with AI!') }}</p>
|
||||
<p class="text-gray-400 text-sm mt-2">{{ __('Type a message below to begin.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
<form action="{{ route('chat.store') }}" method="POST" class="flex gap-2">
|
||||
@csrf
|
||||
<div class="flex-1 flex gap-2">
|
||||
<input type="text"
|
||||
name="message"
|
||||
placeholder="{{ __('Type your message...') }}"
|
||||
class="flex-1 text-sm border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
autocomplete="off"
|
||||
required>
|
||||
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('Send') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Auto-scroll to bottom of chat
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
// Focus input on load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const input = document.querySelector('input[name="message"]');
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Executable
+397
@@ -0,0 +1,397 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Dashboard'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<!-- Welcome Message -->
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-xl shadow-lg p-6 mb-6 text-white">
|
||||
<h1 class="text-2xl font-bold">{{ __('Welcome back, :name!', ['name' => Auth::user()->name]) }}</h1>
|
||||
<p class="mt-2 text-indigo-100">{{ __('Here\'s your learning overview for today.') }}</p>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!Auth::user()->is_approved)
|
||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ __('Your account is pending approval. You will be able to access all features once approved.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Active Subjects') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ $activeSubjectsCount }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">{{ __('Total: :count subjects', ['count' => $subjectsCount]) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Total Messages') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($insightsCount) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">{{ __('AI-powered learning messages') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Token Usage') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($totalTokens) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
{{ __('Input: :input | Output: :output', [
|
||||
'input' => number_format($totalInputTokens),
|
||||
'output' => number_format($totalOutputTokens),
|
||||
]) }}
|
||||
</p>
|
||||
@php $tokenLimit = Auth::user()->token_limit; @endphp
|
||||
@if($tokenLimit)
|
||||
<div class="mt-2">
|
||||
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{{ __('Limit: :limit', ['limit' => number_format($tokenLimit)]) }}</span>
|
||||
<span>{{ round(($totalTokens / $tokenLimit) * 100) }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="h-2 rounded-full {{ ($totalTokens / $tokenLimit) > 0.9 ? 'bg-red-500' : (($totalTokens / $tokenLimit) > 0.7 ? 'bg-yellow-500' : 'bg-blue-500') }}"
|
||||
style="width: {{ min(100, ($totalTokens / $tokenLimit) * 100) }}%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-xs text-gray-400 mt-2">{{ __('Unlimited') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Quick Actions') }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 space-y-1">
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('Manage Subjects') }} →
|
||||
</a>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('View Insights') }} →
|
||||
</a>
|
||||
<a href="{{ route('admin.telegram.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('Telegram Settings') }} →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<!-- Token Usage Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Token Usage (Last 30 Days)') }}</h3>
|
||||
<div id="tokenChart" style="min-height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Messages per Subject Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Messages per Subject') }}</h3>
|
||||
<div id="subjectChart" style="min-height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weekly Activity Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Weekly Activity') }}</h3>
|
||||
<div id="weeklyChart" style="min-height: 200px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Active Subjects and Recent Insights -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<!-- Active Subjects -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Active Subjects') }}</h3>
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('Manage') }} →
|
||||
</a>
|
||||
</div>
|
||||
@forelse($activeSubjects as $userSubject)
|
||||
<div class="flex items-center justify-between py-3 border-b border-gray-100 last:border-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2 h-2 rounded-full {{ $userSubject->is_active ? 'bg-green-500' : 'bg-gray-300' }}"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $userSubject->subject->name }}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ ucfirst($userSubject->frequency) }} |
|
||||
{{ $userSubject->formatted_times }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.subjects.toggle', $userSubject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="is_active" value="{{ $userSubject->is_active ? '0' : '1' }}">
|
||||
<button type="submit" class="text-xs {{ $userSubject->is_active ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800' }}">
|
||||
{{ $userSubject->is_active ? __('Deactivate') : __('Activate') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-gray-500 text-sm">{{ __('No active subjects. Subscribe to subjects to start learning!') }}</p>
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
||||
{{ __('Browse Subjects') }} →
|
||||
</a>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Recent Messages -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Recent Messages') }}</h3>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('View All') }} →
|
||||
</a>
|
||||
</div>
|
||||
<div class="space-y-4 max-h-96 overflow-y-auto">
|
||||
@forelse($recentInsights as $insight)
|
||||
<div class="border-b border-gray-100 pb-3 last:border-0">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
|
||||
{{ $insight->subject->name }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ $insight->type->label() }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ $insight->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-700 line-clamp-2 prose prose-sm max-w-none">
|
||||
{!! \App\Services\MarkdownService::parse($insight->content) !!}
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.insights.translate', $insight) }}" method="POST" class="flex items-center gap-1 ml-2">
|
||||
@csrf
|
||||
<select name="target_language" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'selected' : '' }}>PT</option>
|
||||
<option value="en" {{ app()->getLocale() === 'en' ? 'selected' : '' }}>EN</option>
|
||||
<option value="es" {{ app()->getLocale() === 'es' ? 'selected' : '' }}>ES</option>
|
||||
</select>
|
||||
<button type="submit" class="text-xs text-indigo-500 hover:text-indigo-700 font-medium" title="{{ __('Translate') }}">
|
||||
{{ __('Translate') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-gray-500 text-sm">{{ __('No messages yet. Generate your first message!') }}</p>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
||||
{{ __('Generate Message') }} →
|
||||
</a>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Token Usage Chart
|
||||
var tokenOptions = {
|
||||
series: [
|
||||
{
|
||||
name: '{{ __("Input Tokens") }}',
|
||||
data: @json($tokenChartData['input'] ?? [])
|
||||
},
|
||||
{
|
||||
name: '{{ __("Output Tokens") }}',
|
||||
data: @json($tokenChartData['output'] ?? [])
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 300,
|
||||
toolbar: { show: false },
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
colors: ['#6366F1', '#8B5CF6'],
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { curve: 'smooth', width: 2 },
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.3,
|
||||
opacityTo: 0.1
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: @json($tokenChartData['labels'] ?? []),
|
||||
labels: { style: { fontSize: '11px' } }
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function(val) { return val.toLocaleString(); }
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return val.toLocaleString() + ' tokens'; }
|
||||
}
|
||||
},
|
||||
legend: { position: 'top' },
|
||||
grid: { borderColor: '#f1f5f9' }
|
||||
};
|
||||
|
||||
var tokenChart = new ApexCharts(document.querySelector("#tokenChart"), tokenOptions);
|
||||
tokenChart.render();
|
||||
|
||||
// Subject Chart (Donut)
|
||||
var subjectOptions = {
|
||||
series: @json($subjectChartData['values'] ?? []),
|
||||
chart: {
|
||||
type: 'donut',
|
||||
height: 300,
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
labels: @json($subjectChartData['labels'] ?? []),
|
||||
colors: ['#6366F1', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444'],
|
||||
dataLabels: { enabled: false },
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
fontSize: '12px'
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: '65%',
|
||||
labels: {
|
||||
show: true,
|
||||
total: {
|
||||
show: true,
|
||||
label: '{{ __("Total") }}',
|
||||
formatter: function(w) {
|
||||
return w.globals.seriesTotals.reduce((a, b) => a + b, 0).toLocaleString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return val.toLocaleString() + ' messages'; }
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: { height: 250 },
|
||||
legend: { position: 'bottom' }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
var subjectChart = new ApexCharts(document.querySelector("#subjectChart"), subjectOptions);
|
||||
subjectChart.render();
|
||||
|
||||
// Weekly Activity Chart (Bar)
|
||||
var weeklyOptions = {
|
||||
series: [{
|
||||
name: '{{ __("Messages") }}',
|
||||
data: @json($weeklyActivity['values'] ?? [])
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 200,
|
||||
toolbar: { show: false },
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
colors: ['#6366F1'],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 4,
|
||||
columnWidth: '60%',
|
||||
distributed: true
|
||||
}
|
||||
},
|
||||
dataLabels: { enabled: false },
|
||||
xaxis: {
|
||||
categories: @json($weeklyActivity['labels'] ?? []),
|
||||
labels: { style: { fontSize: '12px' } }
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function(val) { return Math.floor(val).toLocaleString(); }
|
||||
}
|
||||
},
|
||||
grid: { borderColor: '#f1f5f9' },
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return Math.floor(val).toLocaleString() + ' messages'; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var weeklyChart = new ApexCharts(document.querySelector("#weeklyChart"), weeklyOptions);
|
||||
weeklyChart.render();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace("_", "-", app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>@yield("title", config("app.name", "Annunk")) - {{ config("app.name") }}</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Figtree', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@stack("styles")
|
||||
</head>
|
||||
<body class="font-sans antialiased bg-gray-50">
|
||||
<div class="min-h-screen">
|
||||
@include("layouts.navigation")
|
||||
|
||||
@isset($header)
|
||||
<header class="bg-white shadow-sm border-b border-gray-100">
|
||||
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
</header>
|
||||
@endisset
|
||||
|
||||
<!-- Page Content -->
|
||||
<main>
|
||||
@yield("content")
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-gray-200 mt-8">
|
||||
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
||||
<p class="text-center text-sm text-gray-600">
|
||||
©2026 - {{ __('auth.All rights reserved') }} - {{ __('auth.Powered by') }}
|
||||
<a href="https://syspanel.com.br" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:text-indigo-800">Syspanel</a>
|
||||
</p>
|
||||
<p class="text-center text-sm text-gray-600 mt-2">
|
||||
<a href="{{ route('legal.terms') }}" class="text-indigo-600 hover:text-indigo-800">{{ __('legal.terms_of_use') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.cookies') }}" class="text-indigo-600 hover:text-indigo-800">{{ __('legal.cookie_policy') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.privacy') }}" class="text-indigo-600 hover:text-indigo-800">{{ __('legal.privacy_policy') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@include('components.cookie-banner')
|
||||
|
||||
@stack("scripts")
|
||||
</body>
|
||||
</html>
|
||||
Executable
+339
@@ -0,0 +1,339 @@
|
||||
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100 shadow-sm">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<!-- Left side -->
|
||||
<div class="flex items-center">
|
||||
<div class="shrink-0 flex items-center">
|
||||
<a href="{{ route("dashboard") }}" class="text-xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
||||
Annunk
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden space-x-1 sm:-my-px sm:ms-8 sm:flex">
|
||||
<!-- Dashboard -->
|
||||
<a href="{{ route("dashboard") }}"
|
||||
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs("dashboard") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
|
||||
</svg>
|
||||
{{ __("Dashboard") }}
|
||||
</a>
|
||||
|
||||
@role("admin|superadmin")
|
||||
<!-- AI Chat -->
|
||||
<a href="{{ route("chat.index") }}"
|
||||
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs("chat.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
{{ __("Chat com IA") }}
|
||||
</a>
|
||||
|
||||
<!-- Learning Dropdown -->
|
||||
<div x-data="{ learningOpen: false }" class="relative">
|
||||
<button @click="learningOpen = !learningOpen" @click.away="learningOpen = false"
|
||||
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs("admin.subjects.*") || request()->routeIs("admin.insights.*") || request()->routeIs("admin.custom-subjects.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
{{ __("Learning") }}
|
||||
<svg class="ml-1 w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="learningOpen" class="origin-top-left absolute left-0 mt-2 w-56 rounded-xl shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50 py-1">
|
||||
<a href="{{ route("admin.interests.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("admin.interests.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
|
||||
</svg>
|
||||
{{ __("My Interests") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.subjects.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("admin.subjects.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
||||
</svg>
|
||||
{{ __("Subjects") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.insights.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("admin.insights.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
{{ __("Insights") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.custom-subjects.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("admin.custom-subjects.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
{{ __("My Custom Subjects") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integrations Dropdown -->
|
||||
<div x-data="{ integOpen: false }" class="relative">
|
||||
<button @click="integOpen = !integOpen" @click.away="integOpen = false"
|
||||
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs("admin.telegram.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
{{ __("Integrations") }}
|
||||
<svg class="ml-1 w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="integOpen" class="origin-top-left absolute left-0 mt-2 w-56 rounded-xl shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50 py-1">
|
||||
<a href="{{ route("admin.telegram.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("admin.telegram.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
{{ __("Telegram Bot") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endrole
|
||||
|
||||
@role("superadmin")
|
||||
<!-- Admin Dropdown -->
|
||||
<div x-data="{ adminOpen: false }" class="relative">
|
||||
<button @click="adminOpen = !adminOpen" @click.away="adminOpen = false"
|
||||
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs("superadmin.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
{{ __("Admin") }}
|
||||
<svg class="ml-1 w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="adminOpen" class="origin-top-left absolute left-0 mt-2 w-56 rounded-xl shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50 py-1">
|
||||
<a href="{{ route("superadmin.users.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("superadmin.users.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
|
||||
</svg>
|
||||
{{ __("Users") }}
|
||||
</a>
|
||||
<a href="{{ route("superadmin.system.index") }}"
|
||||
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 {{ request()->routeIs("superadmin.system.*") ? "bg-indigo-50 text-indigo-700" : "" }}">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
</svg>
|
||||
{{ __("System") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endrole
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Language Selector -->
|
||||
<x-language-selector variant="text-gray-500 hover:text-gray-700" />
|
||||
|
||||
@auth
|
||||
<!-- Notifications -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-2">
|
||||
<div x-data="{ notifOpen: false }" class="relative">
|
||||
<button @click="notifOpen = !notifOpen" @click.away="notifOpen = false"
|
||||
class="relative inline-flex items-center p-2 text-sm font-medium rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-50 focus:outline-none transition">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||
</svg>
|
||||
@if($unreadNotifications->count() > 0)
|
||||
<span class="absolute top-1 right-1 inline-flex items-center justify-center px-1.5 py-0.5 text-xs font-bold leading-none text-white bg-red-600 rounded-full">
|
||||
{{ $unreadNotifications->count() > 9 ? '9+' : $unreadNotifications->count() }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
<div x-show="notifOpen" @click.away="notifOpen = false"
|
||||
class="origin-top-right absolute right-0 mt-2 w-80 rounded-xl shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50 py-1">
|
||||
<div class="px-4 py-3 border-b border-gray-100">
|
||||
<p class="text-sm font-semibold text-gray-900">{{ __('Notifications') }}</p>
|
||||
</div>
|
||||
@forelse($unreadNotifications->take(5) as $notification)
|
||||
<a href="{{ route('notifications.index') }}"
|
||||
class="block px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 border-b border-gray-100 last:border-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ $notification->typeIcon() }}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-gray-900 truncate">{{ $notification->title }}</p>
|
||||
<p class="text-xs text-gray-500 truncate">{{ $notification->message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<div class="px-4 py-3 text-sm text-gray-500">
|
||||
{{ __('No new notifications.') }}
|
||||
</div>
|
||||
@endforelse
|
||||
<div class="border-t border-gray-100">
|
||||
<a href="{{ route('notifications.index') }}"
|
||||
class="block px-4 py-2 text-sm text-indigo-600 hover:bg-gray-50 font-medium text-center">
|
||||
{{ __('View all notifications') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-2">
|
||||
<div class="ms-3 relative">
|
||||
<div x-data="{ open: false }" class="relative">
|
||||
<button @click="open = !open" class="flex items-center text-sm focus:outline-none">
|
||||
<span class="inline-flex items-center px-3 py-2 border border-gray-200 text-sm leading-4 font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none transition">
|
||||
<span class="w-6 h-6 rounded-full bg-indigo-100 flex items-center justify-center text-xs font-bold text-indigo-600 mr-2">
|
||||
{{ substr(Auth::user()->name, 0, 1) }}
|
||||
</span>
|
||||
{{ Auth::user()->name }}
|
||||
<svg class="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div x-show="open" @click.away="open = false"
|
||||
class="origin-top-right absolute right-0 mt-2 w-56 rounded-xl shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 z-50">
|
||||
<div class="px-4 py-3 border-b border-gray-100">
|
||||
<p class="text-sm font-medium text-gray-900">{{ Auth::user()->name }}</p>
|
||||
<p class="text-xs text-gray-500">{{ Auth::user()->email }}</p>
|
||||
</div>
|
||||
|
||||
<a href="{{ route("profile.edit") }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
{{ __("Profile") }}
|
||||
</a>
|
||||
|
||||
<a href="{{ route('legal.history') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
{{ __("Legal History") }}
|
||||
</a>
|
||||
|
||||
<div class="border-t border-gray-100"></div>
|
||||
|
||||
<form method="POST" action="{{ route("logout") }}">
|
||||
@csrf
|
||||
<button type="submit" class="w-full text-left block px-4 py-2 text-sm text-red-600 hover:bg-red-50">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
{{ __("Log Out") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<button @click="open = !open" class="sm:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 focus:outline-none transition">
|
||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path :class="{'hidden': open, 'inline-flex': !open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path :class="{'hidden': !open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
@auth
|
||||
<div x-show="open" class="sm:hidden border-t border-gray-100">
|
||||
<div class="pt-2 pb-3 space-y-1 px-4">
|
||||
<a href="{{ route("dashboard") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("dashboard") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Dashboard") }}
|
||||
</a>
|
||||
|
||||
@role("admin|superadmin")
|
||||
<a href="{{ route("chat.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("chat.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Chat com IA") }}
|
||||
</a>
|
||||
|
||||
<div class="text-xs font-semibold text-gray-400 uppercase tracking-wider px-3 pt-4 pb-1">{{ __("Learning") }}</div>
|
||||
<a href="{{ route("admin.interests.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("admin.interests.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("My Interests") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.subjects.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("admin.subjects.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Subjects") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.insights.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("admin.insights.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Insights") }}
|
||||
</a>
|
||||
<a href="{{ route("admin.custom-subjects.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("admin.custom-subjects.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("My Custom Subjects") }}
|
||||
</a>
|
||||
|
||||
<div class="text-xs font-semibold text-gray-400 uppercase tracking-wider px-3 pt-4 pb-1">{{ __("Integrations") }}</div>
|
||||
<a href="{{ route("admin.telegram.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("admin.telegram.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Telegram") }}
|
||||
</a>
|
||||
@endrole
|
||||
|
||||
@role("superadmin")
|
||||
<div class="text-xs font-semibold text-gray-400 uppercase tracking-wider px-3 pt-4 pb-1">{{ __("Administration") }}</div>
|
||||
<a href="{{ route("superadmin.users.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("superadmin.users.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("Users") }}
|
||||
</a>
|
||||
<a href="{{ route("superadmin.system.index") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium {{ request()->routeIs("superadmin.system.*") ? "bg-indigo-50 text-indigo-700" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50" }}">
|
||||
{{ __("System") }}
|
||||
</a>
|
||||
@endrole
|
||||
</div>
|
||||
|
||||
<div class="pt-4 pb-3 border-t border-gray-200 px-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<span class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-sm font-bold text-indigo-600">
|
||||
{{ substr(Auth::user()->name, 0, 1) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-base font-medium text-gray-800">{{ Auth::user()->name }}</div>
|
||||
<div class="text-sm font-medium text-gray-500">{{ Auth::user()->email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1">
|
||||
<a href="{{ route("profile.edit") }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-50">
|
||||
{{ __("Profile") }}
|
||||
</a>
|
||||
<a href="{{ route('legal.history') }}"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-50">
|
||||
{{ __("Legal History") }}
|
||||
</a>
|
||||
<form method="POST" action="{{ route("logout") }}">
|
||||
@csrf
|
||||
<button type="submit" class="w-full text-left block px-3 py-2 rounded-lg text-sm font-medium text-red-600 hover:bg-red-50">
|
||||
{{ __("Log Out") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endauth
|
||||
</nav>
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>@yield('title', config('app.name', 'Annunk'))</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Figtree', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
.card-hover {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<!-- Navigation -->
|
||||
<nav class="bg-white shadow-sm border-b border-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex items-center">
|
||||
<a href="{{ url('/') }}" class="text-2xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
||||
Annunk
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector />
|
||||
@if (Route::has('login'))
|
||||
@auth
|
||||
<a href="{{ route('dashboard') }}" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('auth.Dashboard') }}
|
||||
</a>
|
||||
@else
|
||||
@if (!request()->routeIs('login'))
|
||||
<a href="{{ route('login') }}" class="inline-flex items-center px-4 py-2 text-indigo-600 text-sm font-medium hover:text-indigo-700 transition">
|
||||
{{ __('auth.Log in') }}
|
||||
</a>
|
||||
@endif
|
||||
@if (Route::has('register') && !request()->routeIs('register'))
|
||||
<a href="{{ route('register') }}" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('auth.Register') }}
|
||||
</a>
|
||||
@endif
|
||||
@endauth
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-grow {{ $lightBackground ?? false ? 'bg-gray-50' : 'hero-gradient flex items-center justify-center' }} px-4 py-12">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-900 text-white">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-gray-300">
|
||||
@2026 - {{ __('auth.All rights reserved') }} - {{ __('auth.Powered by') }} <a href="https://syspanel.com.br" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300">Syspanel</a> - <a href="https://syspanel.com.br" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300">www.syspanel.com.br</a>
|
||||
</p>
|
||||
<p class="text-sm text-gray-300 mt-2">
|
||||
<a href="{{ route('legal.terms') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('legal.terms_of_use') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.cookies') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('legal.cookie_policy') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.privacy') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('legal.privacy_policy') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@include('components.cookie-banner')
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
@extends('layouts.public', ['lightBackground' => true])
|
||||
|
||||
@section('title', __('legal.cookie_policy'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto w-full">
|
||||
<div class="bg-white shadow-xl rounded-2xl p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ __('legal.cookie_policy') }}</h1>
|
||||
<p class="text-gray-600">{{ __('legal.last_updated') }}: {{ now()->format('d/m/Y') }} | {{ __('legal.version') }}: {{ $currentVersion }}</p>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none text-gray-700 space-y-4">
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">1. {{ __('legal.what_are_cookies') }}</h2>
|
||||
<p>{{ __('legal.cookies_definition') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">2. {{ __('legal.types_of_cookies') }}</h2>
|
||||
|
||||
<h3 class="text-xl font-medium mt-4 mb-2">{{ __('legal.essential_cookies') }}</h3>
|
||||
<p>{{ __('legal.essential_text') }}</p>
|
||||
|
||||
<h3 class="text-xl font-medium mt-4 mb-2">{{ __('legal.analytics_cookies') }}</h3>
|
||||
<p>{{ __('legal.analytics_text') }}</p>
|
||||
|
||||
<h3 class="text-xl font-medium mt-4 mb-2">{{ __('legal.functional_cookies') }}</h3>
|
||||
<p>{{ __('legal.functional_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">3. {{ __('legal.management') }}</h2>
|
||||
<p>{{ __('legal.management_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">4. {{ __('legal.third_party') }}</h2>
|
||||
<p>{{ __('legal.third_party_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">5. {{ __('legal.consent_withdrawal') }}</h2>
|
||||
<p>{{ __('legal.withdrawal_text') }}</p>
|
||||
</div>
|
||||
|
||||
@auth
|
||||
@php
|
||||
$currentTerms = config('legal.terms_version', '1.0.0');
|
||||
$currentPrivacy = config('legal.privacy_version', '1.0.0');
|
||||
$currentCookies = config('legal.cookie_version', '1.0.0');
|
||||
@endphp
|
||||
<div class="mt-8 border-t pt-6">
|
||||
<form method="POST" action="{{ route('legal.accept') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="terms_version" value="{{ $currentTerms }}">
|
||||
<input type="hidden" name="privacy_version" value="{{ $currentPrivacy }}">
|
||||
<input type="hidden" name="cookie_version" value="{{ $currentCookies }}">
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input id="agree" type="checkbox" name="agree" required class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
|
||||
<label for="agree" class="ml-2 block text-sm text-gray-900">
|
||||
{{ __('legal.i_agree') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition">
|
||||
{{ __('legal.accept_and_continue') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto py-10 px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-6">{{ __('legal.consent_history') }}</h1>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('legal.date') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('legal.terms_version') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('legal.privacy_version') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('legal.cookie_version') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('legal.ip_address') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse ($consents as $consent)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $consent->accepted_at->format('d/m/Y H:i:s') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $consent->terms_version }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $consent->privacy_version }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $consent->cookie_version }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $consent->ip_address ?? 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">
|
||||
{{ __('legal.no_consents') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
{{ $consents->links() }}
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="{{ route('legal.terms') }}" class="text-indigo-600 hover:text-indigo-900">
|
||||
{{ __('legal.back_to_terms') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
@extends('layouts.public', ['lightBackground' => true])
|
||||
|
||||
@section('title', __('legal.privacy_policy'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto w-full">
|
||||
<div class="bg-white shadow-xl rounded-2xl p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ __('legal.privacy_policy') }}</h1>
|
||||
<p class="text-gray-600">{{ __('legal.last_updated') }}: {{ now()->format('d/m/Y') }} | {{ __('legal.version') }}: {{ $currentVersion }}</p>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none text-gray-700 space-y-4">
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">1. {{ __('legal.responsible') }}</h2>
|
||||
<p>{{ config('legal.company_name') }} - {{ config('legal.company_email') }}</p>
|
||||
<p>{{ __('legal.dpo') }}: {{ config('legal.dpo_email') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">2. {{ __('legal.data_collected') }}</h2>
|
||||
<p>{{ __('legal.data_collected_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">3. {{ __('legal.purpose') }}</h2>
|
||||
<p>{{ __('legal.purpose_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">4. {{ __('legal.storage') }}</h2>
|
||||
<p>{{ __('legal.storage_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">5. {{ __('legal.sharing') }}</h2>
|
||||
<p>{{ __('legal.sharing_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">6. {{ __('legal.retention') }}</h2>
|
||||
<p>{{ __('legal.retention_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">7. {{ __('legal.rights') }}</h2>
|
||||
<p>{{ __('legal.rights_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">8. {{ __('legal.security') }}</h2>
|
||||
<p>{{ __('legal.security_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">9. {{ __('legal.cookies_section') }}</h2>
|
||||
<p>{{ __('legal.cookies_text') }}</p>
|
||||
</div>
|
||||
|
||||
@auth
|
||||
@php
|
||||
$currentTerms = config('legal.terms_version', '1.0.0');
|
||||
$currentPrivacy = config('legal.privacy_version', '1.0.0');
|
||||
$currentCookies = config('legal.cookie_version', '1.0.0');
|
||||
@endphp
|
||||
<div class="mt-8 border-t pt-6">
|
||||
<form method="POST" action="{{ route('legal.accept') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="terms_version" value="{{ $currentTerms }}">
|
||||
<input type="hidden" name="privacy_version" value="{{ $currentPrivacy }}">
|
||||
<input type="hidden" name="cookie_version" value="{{ $currentCookies }}">
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input id="agree" type="checkbox" name="agree" required class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
|
||||
<label for="agree" class="ml-2 block text-sm text-gray-900">
|
||||
{{ __('legal.i_agree') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition">
|
||||
{{ __('legal.accept_and_continue') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
@extends('layouts.public', ['lightBackground' => true])
|
||||
|
||||
@section('title', __('legal.terms_of_use'))
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto w-full">
|
||||
<div class="bg-white shadow-xl rounded-2xl p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">{{ __('legal.terms_of_use') }}</h1>
|
||||
<p class="text-gray-600">{{ __('legal.last_updated') }}: {{ now()->format('d/m/Y') }} | {{ __('legal.version') }}: {{ $currentVersion }}</p>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none text-gray-700 space-y-4">
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">1. {{ __('legal.acceptance_of_terms') }}</h2>
|
||||
<p>{{ __('legal.acceptance_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">2. {{ __('legal.description_of_services') }}</h2>
|
||||
<p>{{ __('legal.services_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">3. {{ __('legal.user_obligations') }}</h2>
|
||||
<p>{{ __('legal.obligations_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">4. {{ __('legal.intellectual_property') }}</h2>
|
||||
<p>{{ __('legal.ip_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">5. {{ __('legal.limitation_of_liability') }}</h2>
|
||||
<p>{{ __('legal.liability_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">6. {{ __('legal.modifications') }}</h2>
|
||||
<p>{{ __('legal.modifications_text') }}</p>
|
||||
|
||||
<h2 class="text-2xl font-semibold mt-6 mb-3">7. {{ __('legal.applicable_law') }}</h2>
|
||||
<p>{{ __('legal.law_text') }}</p>
|
||||
</div>
|
||||
|
||||
@auth
|
||||
@php
|
||||
$latestConsent = auth()->user()->consents()->orderByDesc('accepted_at')->first();
|
||||
$currentTerms = config('legal.terms_version', '1.0.0');
|
||||
$currentPrivacy = config('legal.privacy_version', '1.0.0');
|
||||
$currentCookies = config('legal.cookie_version', '1.0.0');
|
||||
$needsConsent = !$latestConsent ||
|
||||
$latestConsent->terms_version !== $currentTerms ||
|
||||
$latestConsent->privacy_version !== $currentPrivacy ||
|
||||
$latestConsent->cookie_version !== $currentCookies;
|
||||
@endphp
|
||||
@if($needsConsent)
|
||||
<div class="mt-8 border-t pt-6">
|
||||
<form method="POST" action="{{ route('legal.accept') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="terms_version" value="{{ $currentTerms }}">
|
||||
<input type="hidden" name="privacy_version" value="{{ $currentPrivacy }}">
|
||||
<input type="hidden" name="cookie_version" value="{{ $currentCookies }}">
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input id="agree" type="checkbox" name="agree" required class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
|
||||
<label for="agree" class="ml-2 block text-sm text-gray-900">
|
||||
{{ __('legal.i_agree') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition">
|
||||
{{ __('legal.accept_and_continue') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,71 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Notifications'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Notifications') }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{{ __('Notifications') }}</h1>
|
||||
@if($notifications->total() > 0)
|
||||
<form action="{{ route('notifications.read-all') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('Mark all as read') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
@forelse($notifications as $notification)
|
||||
<div class="flex items-start gap-4 p-4 border-b border-gray-100 last:border-0 {{ $notification->is_read ? 'bg-gray-50' : 'bg-white' }}">
|
||||
<div class="text-2xl">
|
||||
{{ $notification->typeIcon() }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-gray-900">
|
||||
{{ $notification->title }}
|
||||
</h3>
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ $notification->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
{{ $notification->message }}
|
||||
</p>
|
||||
@if(!$notification->is_read)
|
||||
<form action="{{ route('notifications.read', $notification) }}" method="POST" class="mt-2">
|
||||
@csrf
|
||||
<button type="submit" class="text-xs text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('Mark as read') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="p-8 text-center text-gray-500">
|
||||
{{ __('No notifications yet.') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
{{ $notifications->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Invoice {{ $invoice->invoice_number }}</title>
|
||||
<style>
|
||||
body { font-family: 'DejaVu Sans', sans-serif; font-size: 12px; color: #333; }
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
.header h1 { color: #4F46E5; font-size: 24px; margin: 0; }
|
||||
.header p { color: #666; margin: 5px 0; }
|
||||
.info { margin-bottom: 30px; }
|
||||
.info table { width: 100%; }
|
||||
.info td { vertical-align: top; padding: 5px; }
|
||||
.info .label { font-weight: bold; color: #666; width: 120px; }
|
||||
table.items { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
|
||||
table.items th { background: #4F46E5; color: white; padding: 10px; text-align: left; }
|
||||
table.items td { padding: 10px; border-bottom: 1px solid #ddd; }
|
||||
table.items tr:nth-child(even) { background: #f9f9f9; }
|
||||
.total { text-align: right; font-size: 16px; font-weight: bold; margin-top: 20px; }
|
||||
.footer { text-align: center; color: #999; font-size: 10px; margin-top: 50px; border-top: 1px solid #ddd; padding-top: 10px; }
|
||||
.status-paid { color: #059669; font-weight: bold; }
|
||||
.status-pending { color: #D97706; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Annunk</h1>
|
||||
<p>Learning & Training Platform</p>
|
||||
<p>www.syspanel.com.br</p>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="label">Invoice:</td>
|
||||
<td>{{ $invoice->invoice_number }}</td>
|
||||
<td class="label">Date:</td>
|
||||
<td>{{ $invoice->created_at->format('d/m/Y') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Client:</td>
|
||||
<td>{{ $invoice->user->name }}</td>
|
||||
<td class="label">Email:</td>
|
||||
<td>{{ $invoice->user->email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Status:</td>
|
||||
<td class="status-{{ $invoice->status }}">{{ ucfirst($invoice->status) }}</td>
|
||||
<td class="label">Payment:</td>
|
||||
<td>{{ $invoice->paid_at ? $invoice->paid_at->format('d/m/Y') : '-' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Period</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ $invoice->subscription->plan->name }}</td>
|
||||
<td>{{ $invoice->subscription->start_date->format('d/m/Y') }} - {{ $invoice->subscription->end_date->format('d/m/Y') }}</td>
|
||||
<td>{{ $invoice->currency === 'BRL' ? 'R$' : '$' }} {{ number_format($invoice->amount, 2, ',', '.') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="total">
|
||||
Total: {{ $invoice->currency === 'BRL' ? 'R$' : '$' }} {{ number_format($invoice->amount, 2, ',', '.') }}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Annunk - Syspanel (syspanel.com.br)</p>
|
||||
<p>This invoice was generated automatically. For questions, contact contato@ank2.com</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Profile'))
|
||||
|
||||
@section('content')
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-6">{{ __('Profile') }}</h2>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('profile.update') }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Name') }}</label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name', $user->name) }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Email') }}</label>
|
||||
<input type="email" name="email" id="email" value="{{ old('email', $user->email) }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Phone') }}</label>
|
||||
<input type="text" name="phone" id="phone" value="{{ old('phone', $user->phone) }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="locale" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Language') }}</label>
|
||||
<select name="locale" id="locale"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR" {{ old('locale', $user->locale) == 'pt_BR' ? 'selected' : '' }}>Português (Brasil)</option>
|
||||
<option value="en" {{ old('locale', $user->locale) == 'en' ? 'selected' : '' }}>English</option>
|
||||
<option value="es" {{ old('locale', $user->locale) == 'es' ? 'selected' : '' }}>Español</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="timezone" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Timezone') }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __('Your local timezone for scheduling insights correctly.') }}</p>
|
||||
<select name="timezone" id="timezone" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
@php
|
||||
$selectedTimezone = old('timezone', $user->timezone ?? 'America/Sao_Paulo');
|
||||
$timezones = [
|
||||
'America/Sao_Paulo' => '🇧🇷 São Paulo (GMT-3)',
|
||||
'America/New_York' => '🇺🇸 New York (GMT-5)',
|
||||
'America/Chicago' => '🇺🇸 Chicago (GMT-6)',
|
||||
'America/Denver' => '🇺🇸 Denver (GMT-7)',
|
||||
'America/Los_Angeles' => '🇺🇸 Los Angeles (GMT-8)',
|
||||
'Europe/London' => '🇬🇧 London (GMT+0)',
|
||||
'Europe/Madrid' => '🇪🇸 Madrid (GMT+1)',
|
||||
'Europe/Berlin' => '🇩🇪 Berlin (GMT+1)',
|
||||
'Europe/Paris' => '🇫🇷 Paris (GMT+1)',
|
||||
'Asia/Tokyo' => '🇯🇵 Tokyo (GMT+9)',
|
||||
'Asia/Shanghai' => '🇨🇳 Shanghai (GMT+8)',
|
||||
'Asia/Dubai' => '🇦🇪 Dubai (GMT+4)',
|
||||
'Australia/Sydney' => '🇦🇺 Sydney (GMT+11)',
|
||||
'Pacific/Auckland' => '🇳🇿 Auckland (GMT+12)',
|
||||
'UTC' => '🌐 UTC',
|
||||
];
|
||||
@endphp
|
||||
@foreach($timezones as $value => $label)
|
||||
<option value="{{ $value }}" {{ $selectedTimezone == $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-8 border-t border-gray-200">
|
||||
<h3 class="text-xl font-semibold mb-4">{{ __('Update Password') }}</h3>
|
||||
<p class="text-sm text-gray-500 mb-4">{{ __('Leave blank to keep current password') }}</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">{{ __('New Password') }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password" id="password"
|
||||
class="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="togglePassword('password', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password_confirmation" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Confirm New Password') }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password_confirmation" id="password_confirmation"
|
||||
class="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="togglePassword('password_confirmation', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@error('password_confirmation')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
|
||||
{{ __('Update Profile') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-8 pt-8 border-t border-gray-200">
|
||||
<h3 class="text-xl font-semibold mb-4 text-red-600">{{ __('Delete Account') }}</h3>
|
||||
<p class="text-gray-600 mb-4">{{ __('Once your account is deleted, all of its resources and data will be permanently deleted.') }}</p>
|
||||
|
||||
<form method="POST" action="{{ route('profile.destroy') }}" onsubmit="return confirm('{{ __('Are you sure?') }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">{{ __('Password') }}</label>
|
||||
<input type="password" name="password" id="password" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
|
||||
{{ __('Delete Account') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function togglePassword(fieldId, btn) {
|
||||
var input = document.getElementById(fieldId);
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("Billing Management"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">{{ __("Billing Management") }}</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">{{ session("success") }}</div>
|
||||
@endif
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Active Plans") }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $stats["active_plans"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Active Subscriptions") }}</p>
|
||||
<p class="text-2xl font-bold text-green-600">{{ $stats["active_subscriptions"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Total Revenue") }}</p>
|
||||
<p class="text-2xl font-bold text-indigo-600">R$ {{ number_format($stats["total_revenue"], 2, ',', '.') }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Pending Invoices") }}</p>
|
||||
<p class="text-2xl font-bold text-yellow-600">{{ $stats["pending_invoices"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Expiring Soon") }}</p>
|
||||
<p class="text-2xl font-bold text-red-600">{{ $stats["expiring_soon"] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plans -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold">{{ __("Subscription Plans") }}</h3>
|
||||
<button onclick="document.getElementById('createPlanModal').classList.remove('hidden')"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-sm font-medium">
|
||||
+ {{ __("New Plan") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
@foreach($plans as $plan)
|
||||
<div class="border rounded-lg p-4 {{ $plan->is_active ? 'border-indigo-200' : 'border-gray-200 opacity-75' }}">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h4 class="font-semibold text-gray-900">{{ $plan->name }}</h4>
|
||||
@if($plan->is_active)
|
||||
<span class="text-xs bg-green-100 text-green-800 px-2 py-0.5 rounded-full">{{ __("Active") }}</span>
|
||||
@else
|
||||
<span class="text-xs bg-gray-100 text-gray-800 px-2 py-0.5 rounded-full">{{ __("Inactive") }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-2xl font-bold text-indigo-600 mb-2">
|
||||
R$ {{ number_format($plan->price, 2, ',', '.') }}
|
||||
<span class="text-sm font-normal text-gray-500">/{{ $plan->duration_days }}d</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 mb-3">{{ $plan->description }}</p>
|
||||
<div class="text-xs text-gray-500 space-y-1 mb-3">
|
||||
<p>📅 {{ $plan->duration_days }} {{ __("days") }}</p>
|
||||
<p>🔤 {{ $plan->token_limit ? number_format($plan->token_limit) . ' tokens' : __('Unlimited tokens') }}</p>
|
||||
<p>👥 {{ $plan->user_subscriptions_count ?? 0 }} {{ __("subscribers") }}</p>
|
||||
</div>
|
||||
<button onclick="openEditPlanModal({{ $plan->id }}, '{{ addslashes($plan->name) }}', '{{ addslashes($plan->description ?? '') }}', {{ $plan->price }}, '{{ $plan->currency }}', {{ $plan->duration_days }}, {{ $plan->token_limit ?? 'null' }}, {{ $plan->is_active ? 'true' : 'false' }})"
|
||||
class="w-full text-sm px-3 py-1.5 bg-indigo-100 text-indigo-700 rounded-lg hover:bg-indigo-200 transition font-medium">
|
||||
{{ __("Edit") }}
|
||||
</button>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Subscriptions -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">{{ __("Recent Subscriptions") }}</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("User") }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("Plan") }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("Status") }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("Payment") }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("Period") }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($subscriptions as $sub)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-sm">{{ $sub->user->name }}</td>
|
||||
<td class="px-4 py-3 text-sm">{{ $sub->plan->name }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 text-xs rounded-full {{ $sub->status === 'active' ? 'bg-green-100 text-green-800' : ($sub->status === 'cancelled' ? 'bg-red-100 text-red-800' : 'bg-gray-100 text-gray-800') }}">
|
||||
{{ ucfirst($sub->status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
@if($sub->payment_approved)
|
||||
<span class="text-xs text-green-600">✅ {{ __("Approved") }}</span>
|
||||
@else
|
||||
<span class="text-xs text-yellow-600">⏳ {{ __("Pending") }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500">
|
||||
{{ $sub->start_date->format('d/m/Y') }} - {{ $sub->end_date->format('d/m/Y') }}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex gap-1">
|
||||
@if(!$sub->payment_approved)
|
||||
<form action="{{ route("superadmin.billing.approve", $sub) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700">{{ __("Approve") }}</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($sub->status === 'active')
|
||||
<form action="{{ route("superadmin.billing.cancel", $sub) }}" method="POST" onsubmit="return confirm('{{ __("Cancel this subscription?") }}')">
|
||||
@csrf
|
||||
<button type="submit" class="px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700">{{ __("Cancel") }}</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(method_exists($subscriptions, 'links'))
|
||||
<div class="mt-4">{{ $subscriptions->links() }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Plan Modal -->
|
||||
<div id="createPlanModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium">{{ __("Create Plan") }}</h3>
|
||||
<button onclick="document.getElementById('createPlanModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">×</button>
|
||||
</div>
|
||||
<form action="{{ route("superadmin.billing.plans.store") }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Name") }}</label>
|
||||
<input type="text" name="name" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Description") }}</label>
|
||||
<textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Price") }}</label>
|
||||
<input type="number" name="price" step="0.01" min="0" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Currency") }}</label>
|
||||
<select name="currency" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="BRL">BRL (R$)</option>
|
||||
<option value="USD">USD ($)</option>
|
||||
<option value="EUR">EUR (€)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Duration (days)") }}</label>
|
||||
<input type="number" name="duration_days" min="1" value="30" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Token Limit") }}</label>
|
||||
<input type="number" name="token_limit" min="0" placeholder="{{ __("Unlimited") }}" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<button type="button" onclick="document.getElementById('createPlanModal').classList.add('hidden')" class="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200">{{ __("Cancel") }}</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">{{ __("Create") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Plan Modal -->
|
||||
<div id="editPlanModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium">{{ __("Edit Plan") }}</h3>
|
||||
<button onclick="document.getElementById('editPlanModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">×</button>
|
||||
</div>
|
||||
<form id="editPlanForm" method="POST">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Name") }}</label>
|
||||
<input type="text" name="name" id="edit_name" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Description") }}</label>
|
||||
<textarea name="description" id="edit_description" rows="2" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Price") }}</label>
|
||||
<input type="number" name="price" id="edit_price" step="0.01" min="0" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Currency") }}</label>
|
||||
<select name="currency" id="edit_currency" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="BRL">BRL (R$)</option>
|
||||
<option value="USD">USD ($)</option>
|
||||
<option value="EUR">EUR (€)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Duration (days)") }}</label>
|
||||
<input type="number" name="duration_days" id="edit_duration" min="1" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __("Token Limit") }}</label>
|
||||
<input type="number" name="token_limit" id="edit_token_limit" min="0" placeholder="{{ __("Unlimited") }}" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_active" id="edit_is_active" value="1" class="rounded border-gray-300 text-indigo-600">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Active") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<button type="button" onclick="document.getElementById('editPlanModal').classList.add('hidden')" class="px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200">{{ __("Cancel") }}</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">{{ __("Update") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push("scripts")
|
||||
<script>
|
||||
function openEditPlanModal(id, name, description, price, currency, duration, tokenLimit, isActive) {
|
||||
document.getElementById('edit_name').value = name;
|
||||
document.getElementById('edit_description').value = description;
|
||||
document.getElementById('edit_price').value = price;
|
||||
document.getElementById('edit_currency').value = currency;
|
||||
document.getElementById('edit_duration').value = duration;
|
||||
document.getElementById('edit_token_limit').value = tokenLimit || '';
|
||||
document.getElementById('edit_is_active').checked = isActive;
|
||||
document.getElementById('editPlanForm').action = '{{ url("superadmin/billing/plans") }}/' + id;
|
||||
document.getElementById('editPlanModal').classList.remove('hidden');
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("System Management"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("System Management") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<p class="text-sm font-medium text-gray-500">{{ __("Total Users") }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ $stats["total_users"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<p class="text-sm font-medium text-gray-500">{{ __("Active Users") }}</p>
|
||||
<p class="text-3xl font-bold text-green-600 mt-1">{{ $stats["active_users"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<p class="text-sm font-medium text-gray-500">{{ __("Pending Users") }}</p>
|
||||
<p class="text-3xl font-bold text-yellow-600 mt-1">{{ $stats["pending_users"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<p class="text-sm font-medium text-gray-500">{{ __("Total Subjects") }}</p>
|
||||
<p class="text-3xl font-bold text-indigo-600 mt-1">{{ $stats["total_subjects"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<p class="text-sm font-medium text-gray-500">{{ __("Total Insights") }}</p>
|
||||
<p class="text-3xl font-bold text-purple-600 mt-1">{{ $stats["total_insights"] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Subjects Management -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ __("System Subjects") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Manage global subjects available to all users") }}</p>
|
||||
</div>
|
||||
<button onclick="document.getElementById('createSubjectModal').classList.remove('hidden')"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
{{ __("Create System Subject") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Name") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Description") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Subscribers") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Status") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach($subjects as $subject)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">{{ $subject->name }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">{{ $subject->description ?? '-' }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $subject->userSubjects()->count() }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
@if($subject->is_active)
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{{ __("Active") }}
|
||||
</span>
|
||||
@else
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||
{{ __("Inactive") }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button onclick="openEditSubjectModal({{ $subject->id }}, '{{ addslashes($subject->name) }}', '{{ addslashes($subject->description ?? '') }}', '{{ addslashes($subject->introduction ?? '') }}', {{ $subject->is_active ? 'true' : 'false' }})"
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-3">
|
||||
{{ __("Edit") }}
|
||||
</button>
|
||||
<form action="{{ route("superadmin.system.subjects.destroy", $subject) }}" method="POST" class="inline"
|
||||
onsubmit="return confirm('{{ __("Delete this subject? This action cannot be undone.") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="text-red-600 hover:text-red-900">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __("Quick Actions") }}</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<a href="{{ route('superadmin.users.index') }}"
|
||||
class="flex items-center gap-3 p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
|
||||
<div class="w-10 h-10 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ __("Manage Users") }}</p>
|
||||
<p class="text-xs text-gray-500">{{ __("Approve, reject, or manage users") }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('superadmin.billing.index') }}"
|
||||
class="flex items-center gap-3 p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
|
||||
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ __("Billing") }}</p>
|
||||
<p class="text-xs text-gray-500">{{ __("Manage plans, subscriptions and invoices") }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Settings -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ __("AI Settings") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Configure the default AI provider and model, except for Chat.
|
||||
") }}</p>
|
||||
</div>
|
||||
<form action="{{ route('superadmin.system.ai-settings.update') }}" method="POST">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="ai_default_provider" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Default Provider") }}</label>
|
||||
<select name="ai_default_provider" id="ai_default_provider" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="openrouter" {{ (old('ai_default_provider', $aiDefaultProvider) === 'openrouter') ? 'selected' : '' }}>OpenRouter.ai</option>
|
||||
<option value="openai" {{ (old('ai_default_provider', $aiDefaultProvider) === 'openai') ? 'selected' : '' }}>OpenAI</option>
|
||||
<option value="stepfun" {{ (old('ai_default_provider', $aiDefaultProvider) === 'stepfun') ? 'selected' : '' }}>StepFun</option>
|
||||
<option value="llamacpp" {{ (old('ai_default_provider', $aiDefaultProvider) === 'llamacpp') ? 'selected' : '' }}>llama.cpp (Local)</option>
|
||||
<option value="qwen" {{ (old('ai_default_provider', $aiDefaultProvider) === 'qwen') ? 'selected' : '' }}>QwenCloud</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="ai_default_model" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Default Model") }}</label>
|
||||
<input type="text" name="ai_default_model" id="ai_default_model" value="{{ old('ai_default_model', $aiDefaultModel) }}" required
|
||||
placeholder="e.g., deepseek/deepseek-v4-flash-0731"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Save AI Settings") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Chat Settings -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ __("Chat AI Settings") }}</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ __("Configure default AI provider and model for chat conversations") }}</p>
|
||||
</div>
|
||||
<form action="{{ route('superadmin.system.chat-settings.update') }}" method="POST">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="chat_default_provider" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Default Chat Provider") }}</label>
|
||||
<select name="chat_default_provider" id="chat_default_provider" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="openrouter" {{ (old('chat_default_provider', $chatDefaultProvider) === 'openrouter') ? 'selected' : '' }}>OpenRouter.ai</option>
|
||||
<option value="openai" {{ (old('chat_default_provider', $chatDefaultProvider) === 'openai') ? 'selected' : '' }}>OpenAI</option>
|
||||
<option value="stepfun" {{ (old('chat_default_provider', $chatDefaultProvider) === 'stepfun') ? 'selected' : '' }}>StepFun</option>
|
||||
<option value="llamacpp" {{ (old('chat_default_provider', $chatDefaultProvider) === 'llamacpp') ? 'selected' : '' }}>llama.cpp (Local)</option>
|
||||
<option value="qwen" {{ (old('chat_default_provider', $chatDefaultProvider) === 'qwen') ? 'selected' : '' }}>QwenCloud</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="chat_default_model" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Default Chat Model") }}</label>
|
||||
<input type="text" name="chat_default_model" id="chat_default_model" value="{{ old('chat_default_model', $chatDefaultModel) }}" required
|
||||
placeholder="e.g., gpt-4o-mini"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Save Chat Settings") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Subject Modal -->
|
||||
<div id="createSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Create System Subject") }}</h3>
|
||||
<button onclick="document.getElementById('createSubjectModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form action="{{ route("superadmin.system.subjects.store") }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Name") }} *</label>
|
||||
<input type="text" name="name" id="name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Description") }}</label>
|
||||
<textarea name="description" id="description" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="introduction" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Introduction") }}</label>
|
||||
<textarea name="introduction" id="introduction" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="document.getElementById('createSubjectModal').classList.add('hidden')"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Create") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Subject Modal -->
|
||||
<div id="editSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-xl bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">{{ __("Edit System Subject") }}</h3>
|
||||
<button onclick="document.getElementById('editSubjectModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="editSubjectForm" method="POST">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
<div class="mb-4">
|
||||
<label for="edit_name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Name") }} *</label>
|
||||
<input type="text" name="name" id="edit_name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_description" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Description") }}</label>
|
||||
<textarea name="description" id="edit_description" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="edit_introduction" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Introduction") }}</label>
|
||||
<textarea name="introduction" id="edit_introduction" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_active" id="edit_is_active" value="1"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ __("Active") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button type="button" onclick="document.getElementById('editSubjectModal').classList.add('hidden')"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Update") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push("scripts")
|
||||
<script>
|
||||
function openEditSubjectModal(id, name, description, introduction, isActive) {
|
||||
document.getElementById('edit_name').value = name;
|
||||
document.getElementById('edit_description').value = description;
|
||||
document.getElementById('edit_introduction').value = introduction;
|
||||
document.getElementById('edit_is_active').checked = isActive;
|
||||
document.getElementById('editSubjectForm').action = '{{ url("superadmin/system/subjects") }}/' + id;
|
||||
document.getElementById('editSubjectModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Close modals on Escape key
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
document.getElementById('createSubjectModal').classList.add('hidden');
|
||||
document.getElementById('editSubjectModal').classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("Edit User"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("Edit User") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-3xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<span class="w-16 h-16 rounded-full bg-indigo-100 flex items-center justify-center text-xl font-bold text-indigo-600">
|
||||
{{ substr($user->name, 0, 1) }}
|
||||
</span>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ $user->name }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ $user->email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route("superadmin.users.update", $user) }}">
|
||||
@csrf
|
||||
@method("PUT")
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Name") }}</label>
|
||||
<input type="text" name="name" id="name" value="{{ old("name", $user->name) }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Email") }}</label>
|
||||
<input type="email" name="email" id="email" value="{{ old("email", $user->email) }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="role" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Role") }}</label>
|
||||
<select name="role" id="role" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="admin" {{ $user->hasRole('admin') ? 'selected' : '' }}>{{ __("Admin") }}</option>
|
||||
<option value="superadmin" {{ $user->hasRole('superadmin') ? 'selected' : '' }}>{{ __("SuperAdmin") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="plan_id" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Subscription Plan") }}</label>
|
||||
<select name="plan_id" id="plan_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="">{{ __("No plan") }}</option>
|
||||
@foreach(\App\Models\SubscriptionPlan::where('is_active', true)->get() as $plan)
|
||||
@php $activeSub = $user->subscriptions()->where('status', 'active')->first(); @endphp
|
||||
<option value="{{ $plan->id }}" {{ $activeSub && $activeSub->subscription_plan_id == $plan->id ? 'selected' : '' }}>
|
||||
{{ $plan->name }} - R$ {{ number_format($plan->price, 2, ',', '.') }} / {{ $plan->duration_days }}d
|
||||
({{ $plan->token_limit ? number_format($plan->token_limit) . ' tokens' : __('Unlimited') }})
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@if($activeSub ?? false)
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ __("Current plan ends: :date", ['date' => $activeSub->end_date->format('d/m/Y')]) }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="token_limit" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Token Limit") }}</label>
|
||||
<p class="text-xs text-gray-500 mb-2">{{ __("Maximum tokens this user can consume. Leave empty for unlimited.") }}</p>
|
||||
<input type="number" name="token_limit" id="token_limit"
|
||||
value="{{ old("token_limit", $user->token_limit) }}" min="0" step="1000"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
placeholder="{{ __("e.g., 100000 for 100k tokens") }}">
|
||||
@php
|
||||
$totalUsed = \App\Models\TokenUsage::where('user_id', $user->id)->sum('total_tokens');
|
||||
@endphp
|
||||
@if($totalUsed > 0)
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ __("Current usage: :used tokens", ['used' => number_format($totalUsed)]) }}
|
||||
@if($user->token_limit)
|
||||
| {{ __("Limit: :limit tokens", ['limit' => number_format($user->token_limit)]) }}
|
||||
| {{ round(($totalUsed / $user->token_limit) * 100) }}% {{ __("used") }}
|
||||
@endif
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">{{ __("Update Password") }}</h3>
|
||||
<p class="text-xs text-gray-500 mb-4">{{ __("Leave blank to keep current password") }}</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">{{ __("New Password") }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password" id="password"
|
||||
class="w-full px-3 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="togglePassword('password', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password_confirmation" class="block text-sm font-medium text-gray-700 mb-2">{{ __("Confirm New Password") }}</label>
|
||||
<div class="relative">
|
||||
<input type="password" name="password_confirmation" id="password_confirmation"
|
||||
class="w-full px-3 py-2 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<button type="button" onclick="togglePassword('password_confirmation', this)" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@error('password_confirmation')
|
||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||
<a href="{{ route("superadmin.users.index") }}"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition font-medium">
|
||||
{{ __("Back") }}
|
||||
</a>
|
||||
<button type="submit" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition font-medium">
|
||||
{{ __("Update User") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function togglePassword(fieldId, btn) {
|
||||
var input = document.getElementById(fieldId);
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
@extends("layouts.app")
|
||||
|
||||
@section("title", __("User Management"))
|
||||
|
||||
@section("header")
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __("User Management") }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section("content")
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session("success"))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("success") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session("info"))
|
||||
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session("info") }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Total") }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $stats["total"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Pending") }}</p>
|
||||
<p class="text-2xl font-bold text-yellow-600">{{ $stats["pending"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Active") }}</p>
|
||||
<p class="text-2xl font-bold text-green-600">{{ $stats["active"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Paused") }}</p>
|
||||
<p class="text-2xl font-bold text-red-600">{{ $stats["paused"] }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<p class="text-xs font-medium text-gray-500">{{ __("Unverified") }}</p>
|
||||
<p class="text-2xl font-bold text-orange-600">{{ $stats["unverified"] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4 mb-6">
|
||||
<form method="GET" class="flex flex-wrap gap-3 items-center">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input type="text" name="search" value="{{ request('search') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm"
|
||||
placeholder="{{ __("Search by name or email...") }}">
|
||||
</div>
|
||||
<select name="status" class="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm">
|
||||
<option value="">{{ __("All Status") }}</option>
|
||||
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>{{ __("Pending") }}</option>
|
||||
<option value="approved" {{ request('status') == 'approved' ? 'selected' : '' }}>{{ __("Approved") }}</option>
|
||||
<option value="paused" {{ request('status') == 'paused' ? 'selected' : '' }}>{{ __("Paused") }}</option>
|
||||
<option value="verified" {{ request('status') == 'verified' ? 'selected' : '' }}>{{ __("Verified") }}</option>
|
||||
<option value="unverified" {{ request('status') == 'unverified' ? 'selected' : '' }}>{{ __("Unverified") }}</option>
|
||||
</select>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition text-sm font-medium">
|
||||
{{ __("Filter") }}
|
||||
</button>
|
||||
@if(request('search') || request('status'))
|
||||
<a href="{{ route("superadmin.users.index") }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition text-sm font-medium">
|
||||
{{ __("Clear") }}
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("User") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Role") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Status") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Email Verified") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Joined") }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($users as $user)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<span class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center text-xs font-bold text-indigo-600 mr-3">
|
||||
{{ substr($user->name, 0, 1) }}
|
||||
</span>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $user->name }}</p>
|
||||
<p class="text-xs text-gray-500">{{ $user->email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ $user->hasRole('superadmin') ? 'bg-purple-100 text-purple-800' : 'bg-indigo-100 text-indigo-800' }}">
|
||||
{{ $user->getRoleNames()->first() ?? __('admin') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-col gap-1">
|
||||
@if($user->is_approved && $user->is_active)
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{{ __("Active") }}
|
||||
</span>
|
||||
@elseif(!$user->is_approved)
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
|
||||
{{ __("Pending") }}
|
||||
</span>
|
||||
@elseif(!$user->is_active)
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||
{{ __("Paused") }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
@if($user->hasVerifiedEmail())
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{{ __("Verified") }}
|
||||
</span>
|
||||
@else
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-orange-100 text-orange-800">
|
||||
{{ __("Unverified") }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $user->created_at->format('d/m/Y') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-1 flex-wrap">
|
||||
@if(!$user->is_approved)
|
||||
<form action="{{ route("superadmin.users.approve", $user) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700">
|
||||
{{ __("Approve") }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@if(!$user->hasVerifiedEmail())
|
||||
<form action="{{ route("superadmin.users.verify-email", $user) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="px-2 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700">
|
||||
{{ __("Verify Email") }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
<form action="{{ route("superadmin.users.toggle-active", $user) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="px-2 py-1 {{ $user->is_active ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700' }} text-white text-xs rounded">
|
||||
{{ $user->is_active ? __("Pause") : __("Activate") }}
|
||||
</button>
|
||||
</form>
|
||||
<a href="{{ route("superadmin.users.edit", $user) }}"
|
||||
class="px-2 py-1 bg-indigo-600 text-white text-xs rounded hover:bg-indigo-700 inline-block">
|
||||
{{ __("Edit") }}
|
||||
</a>
|
||||
<form action="{{ route("superadmin.users.destroy", $user) }}" method="POST" class="inline"
|
||||
onsubmit="return confirm('{{ __("Delete this user? All associated data will be permanently removed.") }}')">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
<button type="submit" class="px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700">
|
||||
{{ __("Delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-12 text-center text-gray-500">
|
||||
{{ __("No users found.") }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(method_exists($users, 'links'))
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Executable
+304
@@ -0,0 +1,304 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>@yield('title', config('app.name', 'Annunk'))</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Figtree', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
.card-hover {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-sans antialiased bg-gray-50">
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<!-- Navigation -->
|
||||
<nav class="bg-white shadow-sm border-b border-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center">
|
||||
<a href="/" class="text-2xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
||||
Annunk
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Right side -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Language Selector -->
|
||||
<div class="relative">
|
||||
<button id="langDropdownBtn" class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:text-gray-900 transition">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3.18 17.34" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">{{ strtoupper(app()->getLocale()) }}</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id="langDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50 border border-gray-200">
|
||||
<a href="{{ route('language.switch', 'pt_BR') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'pt_BR' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇧🇷 Português (Brasil)
|
||||
</a>
|
||||
<a href="{{ route('language.switch', 'en') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'en' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇺🇸 English
|
||||
</a>
|
||||
<a href="{{ route('language.switch', 'es') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {{ app()->getLocale() == 'es' ? 'bg-indigo-50 text-indigo-600' : '' }}">
|
||||
🇪🇸 Español
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auth Links -->
|
||||
@if (Route::has('login'))
|
||||
@auth
|
||||
<a href="{{ route('dashboard') }}" class="hidden sm:inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('welcome.dashboard') }}
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="hidden sm:inline-flex items-center px-4 py-2 text-indigo-600 text-sm font-medium hover:text-indigo-700 transition">
|
||||
{{ __('welcome.log_in') }}
|
||||
</a>
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('welcome.get_started') }}
|
||||
</a>
|
||||
@endif
|
||||
@endauth
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<main class="flex-grow">
|
||||
<div class="hero-gradient text-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 sm:py-32">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl sm:text-5xl md:text-6xl font-bold mb-6 leading-tight">
|
||||
<span class="block">{{ __('welcome.learning_insights') }}</span>
|
||||
<span class="block text-indigo-200">{{ __('welcome.powered_by_ai') }}</span>
|
||||
</h1>
|
||||
<p class="mt-3 max-w-md mx-auto text-base sm:text-lg md:text-xl text-indigo-100 md:max-w-3xl">
|
||||
{{ __('welcome.description') }}
|
||||
</p>
|
||||
<div class="mt-8 flex flex-col sm:flex-row gap-4 justify-center">
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}" class="inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-lg text-indigo-600 bg-white hover:bg-gray-50 md:py-4 md:text-lg transition">
|
||||
{{ __('welcome.get_started') }}
|
||||
</a>
|
||||
@endif
|
||||
@if (Route::has('login'))
|
||||
<a href="{{ route('login') }}" class="inline-flex items-center justify-center px-8 py-3 border-2 border-white text-base font-medium rounded-lg text-white hover:bg-white hover:text-indigo-600 md:py-4 md:text-lg transition">
|
||||
{{ __('welcome.sign_in') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features Section -->
|
||||
<div class="py-16 sm:py-24 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">
|
||||
{{ __('welcome.why_choose_ank2') }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
{{ __('welcome.why_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- Feature 1 -->
|
||||
<div class="card-hover bg-white border border-gray-200 rounded-xl p-8">
|
||||
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-6">
|
||||
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.ai_powered_insights') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.ai_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 2 -->
|
||||
<div class="card-hover bg-white border border-gray-200 rounded-xl p-8">
|
||||
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-6">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.telegram_integration') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.telegram_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 3 -->
|
||||
<div class="card-hover bg-white border border-gray-200 rounded-xl p-8">
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-6">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.track_progress') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.track_description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How It Works Section -->
|
||||
<div class="py-16 sm:py-24 bg-gray-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">
|
||||
{{ __('welcome.how_it_works') }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
{{ __('welcome.how_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- Step 1 -->
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-indigo-600 text-white rounded-full flex items-center justify-center text-2xl font-bold mx-auto mb-6">
|
||||
1
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.create_account') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.create_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-indigo-600 text-white rounded-full flex items-center justify-center text-2xl font-bold mx-auto mb-6">
|
||||
2
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.connect_telegram') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.connect_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-indigo-600 text-white rounded-full flex items-center justify-center text-2xl font-bold mx-auto mb-6">
|
||||
3
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">
|
||||
{{ __('welcome.start_learning') }}
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
{{ __('welcome.start_description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<div class="py-16 sm:py-24 bg-white">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">
|
||||
{{ __('welcome.ready_to_transform') }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 mb-8">
|
||||
{{ __('welcome.ready_description') }}
|
||||
</p>
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}" class="inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg transition">
|
||||
{{ __('welcome.get_started_now') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-900 text-white mt-auto">
|
||||
<div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-gray-300">
|
||||
@2026 - {{ __('welcome.all_rights_reserved') }} - {{ __('welcome.powered_by') }} <a href="https://syspanel.com.br" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300">Syspanel</a> - <a href="https://syspanel.com.br" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300">www.syspanel.com.br</a>
|
||||
</p>
|
||||
<p class="text-sm text-gray-300 mt-2">
|
||||
<a href="{{ route('legal.terms') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('Terms of Use') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.cookies') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('Cookies') }}</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="{{ route('legal.privacy') }}" class="text-indigo-400 hover:text-indigo-300">{{ __('Privacy Policy') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@include('components.cookie-banner')
|
||||
|
||||
<script>
|
||||
// Language dropdown toggle
|
||||
const langDropdownBtn = document.getElementById('langDropdownBtn');
|
||||
const langDropdown = document.getElementById('langDropdown');
|
||||
|
||||
if (langDropdownBtn && langDropdown) {
|
||||
langDropdownBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
langDropdown.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!langDropdown.contains(e.target) && !langDropdownBtn.contains(e.target)) {
|
||||
langDropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user