diff --git a/.gitignore b/.gitignore index b7b3375..bb6c6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,16 +30,21 @@ claper-*.tar # Ignore digested assets cache. /priv/static/cache_manifest.json +# Env files except sample +.env* +!.env.sample + # In case you use Node.js/npm, you want to ignore these. npm-debug.log /assets/node_modules/ priv/static/images/.DS_Store priv/static/.DS_Store -.env priv/static/fonts/.DS_Store test/e2e/node_modules .DS_Store priv/static/.well-known/apple-developer-merchantid-domain-association priv/static/loaderio-eb3b956a176cdd4f54eb8570ce8bbb06.txt .elixir_ls + +tailwind.config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 99e20a8..b92989a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ ### Features +- Add Admin Panel to manage users and presentations +- Add user roles: user, admin - Add `LANGUAGES` setting to configure available languages in the app -- Add Latvian language support (@possible-im) -- Add Hungarian language support (@bpisch) +- Add hideable presenter attendee count (#183 #155) +- Add Hungarian translation (#161) +- Add Latvian translation (#163) ### Fixes and improvements @@ -15,7 +18,10 @@ - Fix event name validation to be required - Docker image is now using Ubuntu instead of Alpine for better dependencies support - Fix scrollbar not showing in event manager when no presentation file (#164) (@aryel780) -- Fix settings scroll for small screen (#168) (@ChayanDass) +- Fix settings scroll for small screen (#168) +- Fix duplicate key quiz when duplicate (#182) +- Fix email change confirmation (#172) +- Fix italian translation (#179) ### v.2.3.2 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e043078 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Common Development Commands + +### Setup and Dependencies +```bash +# Install dependencies +mix deps.get +mix setup # Runs deps.get + ecto.setup + +# Setup database +mix ecto.setup # Creates DB, runs migrations, seeds +mix ecto.reset # Drops DB and runs ecto.setup + +# Install frontend dependencies +cd assets && npm install && cd .. +``` + +### Running the Application +```bash +# Start Phoenix server +mix phx.server + +# Or inside IEx +iex -S mix phx.server +``` + +### Testing +```bash +# Run tests +mix test + +# Run specific test file +mix test test/path/to/test_file.exs + +# Run test with specific line number +mix test test/path/to/test_file.exs:42 +``` + +### Code Quality +```bash +# Format code +mix format + +# Run Credo for code analysis +mix credo +``` + +### Building Assets +```bash +# For production deployment +mix assets.deploy +``` + +## High-Level Architecture + +Claper is an interactive presentation platform built with Phoenix Framework and Elixir. It enables real-time audience interaction during presentations through polls, forms, messages, and quizzes. + +### Core Components + +1. **Phoenix LiveView Architecture** + - Real-time updates without JavaScript through WebSocket connections + - LiveView modules in `lib/claper_web/live/` handle interactive UI + - Presence tracking for real-time user counts + +2. **Main Domain Contexts** (in `lib/claper/`) + - `Accounts` - User management, authentication, OIDC integration + - `Events` - Core presentation/event management + - `Posts` - Audience messages and reactions + - `Polls` - Interactive polls with real-time voting + - `Forms` - Custom forms for audience feedback + - `Quizzes` - Quiz functionality with LTI support + - `Presentations` - Slide management and state tracking + - `Embeds` - External content embedding + +3. **Authentication & Authorization** + - Multiple auth methods: email/password, OIDC + - Role-based access control with admin panel + - LTI 1.3 support for educational platforms + +4. **Real-time Features** + - Phoenix PubSub for broadcasting updates + - Phoenix Presence for tracking online users + - LiveView for reactive UI without custom JavaScript + +5. **Background Jobs** + - Oban for background job processing + - Email sending + +6. **Frontend Stack** + - Tailwind CSS for styling (with DaisyUI components) + - Alpine.js for minimal JavaScript interactions + - esbuild for JavaScript bundling + - Separate admin and user interfaces + +7. **Key LiveView Modules** + - `EventLive.Show` - Attendee view + - `EventLive.Presenter` - Presenter control view + - `EventLive.Manage` - Event management interface + - `AdminLive.*` - Admin panel components + +8. **Database Structure** + - PostgreSQL with Ecto + - Key models: User, Event, Post, Poll, Quiz, PresentationState + - Soft deletes for users + - UUID-based public identifiers + +9. **LTI Integration** + - LTI 1.3 support for quizzes, publish score to LMS + - LTI launch handling in `LtiController` diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..c8d7ad5 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,63 @@ +@import "tailwindcss"; +@plugin "daisyui"; + +@utility btn { + @apply rounded-full !font-display text-small-body; +} + +@utility input { + @apply rounded-full focus:outline-none focus-within:outline-none focus:border-2 focus-within:border-2 !font-display text-small-body transition-all; +} + +@utility select { + @apply rounded-full focus:outline-none focus-within:outline-none focus:border-2 focus-within:border-2 !font-display text-small-body transition-all; +} + +@utility label { + @apply ml-2; +} + +@utility fieldset-legend { + @apply ml-3; +} + +@plugin "daisyui/theme" { + name: "claper"; + default: true; /* set as default */ + prefersdark: false; /* set as default dark mode (prefers-color-scheme:dark) */ + color-scheme: light; /* color of browser-provided UI */ + + --color-primary: #140753; + --color-primary-content: #ffffff; + --color-secondary: #f4f4f4; + --color-secondary-content: #140553; + --color-accent: #8611ed; + --color-accent-content: #ffffff; + --color-neutral: #000000; + --color-neutral-content: #ffffff; + --color-info: #79bfe2; + --color-info-content: #0e3649; + --color-success: #3cb957; + --color-success-content: #143e1d; + --color-warning: #ffb62e; + --color-warning-content: #523500; + --color-error: #e7000b; + --color-error-content: #fff; + /* border radius */ + --radius-selector: 1rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + + /* base sizes */ + --size-selector: 0.25rem; + --size-field: 0.25rem; + + /* border size */ + --border: 1px; + + /* effects */ + --depth: 1; + --noise: 0; +} + +@import "./modern.css" layer(theme); diff --git a/assets/css/app.css b/assets/css/app.css index 5e24308..92da708 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -2,7 +2,7 @@ @import 'animate.css/animate.min.css'; @import 'tailwindcss'; -@import './theme.css'; +@import './theme.css' layer(theme); @layer base { *, @@ -302,6 +302,20 @@ url('/fonts/Roboto/roboto-v29-latin-900italic.svg#Roboto') format('svg'); /* Legacy iOS */ } +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 100 300 400 500 700 900; + src: url('/fonts/Montserrat/Montserrat-VariableFont_wght.ttf'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 100 300 400 500 700 900; + src: url('/fonts/Montserrat/Montserrat-Italic-VariableFont_wght.ttf'); +} + .bg-gradient-animate { background: linear-gradient(-45deg, var(--color-secondary-500), var(--color-secondary-600), var(--color-primary-400), var(--color-primary-400)); background-size: 400% 400%; diff --git a/assets/css/modern.css b/assets/css/modern.css new file mode 100644 index 0000000..9dc3070 --- /dev/null +++ b/assets/css/modern.css @@ -0,0 +1,152 @@ +@theme { + /* Colors */ + /* Main colors */ + --color-*: initial; + --color-sky-blue: #81EBFE; + --color-teal: #14BFDB; + --color-azure: #29ACED; + --color-mauve: #8611ED; + --color-purple: #B80FEF; + --color-navy: #140553; + + /* Light colors */ + --color-cold-pink: #F3DEFA; + --color-warm-pink: #FFD2F6; + --color-yeallaw: #FFEAC2; + --color-pistacio: #D4FFF4; + --color-sky-tern: #C8EDFF; + --color-tealy: #81EBFE; + --color-earl-night: #0A7BB4; + --color-dark-night: #184992; + + /* Gradient */ + --gradient-primary: 123deg, var(--color-sky-blue) -36%, var(--color-teal) -12%, var(--color-azure) 21%, var(--color-mauve) 81%, var(--color-purple) 130%; + --gradient-secondary: 123deg, rgba(129, 235, 254, 0.9) -36%, rgba(20, 191, 219, 0.9) -12%, rgba(41, 172, 237, 0.9) 21%, rgba(134, 17, 237, 0.9) 81%, rgba(184, 15, 239, 0.9) 130%; + + /* Grayscale */ + --color-white: #FFFFFF; + --color-black: #191919; + --color-gray-20: #CCCCCC; + --color-gray-40: #9F9F9F; + --color-gray-60: #737373; + --color-gray-80: #464646; + --color-gray-100: #191919; + --color-gray-120: #000000; + + --color-platinum: #F0F0F0; + --color-platinum-20: #FFFFFF; + --color-platinum-40: #FCFCFC; + --color-platinum-60: #F8F8F8; + --color-platinum-80: #F4F4F4; + --color-platinum-100: #F0F0F0; + --color-platinum-120: #EEEEEE; + + /* Typography */ + --font-display: "Montserrat", sans-serif; + + /* Font Sizes - Desktop */ + --text-h1: 80px; + --leading-h1: 120%; + --font-weight-h1: 900; + + --text-h2: 40px; + --leading-h2: 120%; + --font-weight-h2: 700; + + --text-h3: 32px; + --leading-h3: 120%; + --font-weight-h3: 600; + + --text-h4: 24px; + --leading-h4: 120%; + --font-weight-h4: 500; + + --text-h5: 18px; + --leading-h5: 120%; + --font-weight-h5: 600; + + --text-h6: 16px; + --leading-h6: 120%; + --font-weight-h6: 500; + + --text-subheading: 20px; + --leading-subheading: 150%; + --font-weight-subheading: 300; + + --text-body-bold: 16px; + --leading-body-bold: 150%; + --font-weight-body-bold: 700; + + --text-body: 16px; + --leading-body: 150%; + --font-weight-body: 400; + + --text-small-body-bold: 14px; + --leading-small-body-bold: 150%; + --font-weight-small-body-bold: 600; + + --text-small-body: 14px; + --leading-small-body: 150%; + --font-weight-small-body: 400; + + --text-caption: 14px; + --leading-caption: 150%; + --font-weight-caption: 300; + + /* Font Sizes - Mobile */ + --text-mobile-h1: 32px; + --leading-mobile-h1: 120%; + --font-weight-mobile-h1: 900; + + --text-mobile-h2: 28px; + --leading-mobile-h2: 120%; + --font-weight-mobile-h2: 700; + + --text-mobile-h3: 22px; + --leading-mobile-h3: 120%; + --font-weight-mobile-h3: 600; + + --text-mobile-h4: 18px; + --leading-mobile-h4: 120%; + --font-weight-mobile-h4: 500; + + --text-mobile-h5: 16px; + --leading-mobile-h5: 120%; + --font-weight-mobile-h5: 600; + + --text-mobile-h6: 14px; + --leading-mobile-h6: 120%; + --font-weight-mobile-h6: 500; + + /* Spacing */ + --spacing-0: 0px; + --spacing-4: 4px; + --spacing-8: 8px; + --spacing-12: 12px; + --spacing-16: 16px; + --spacing-20: 20px; + --spacing-24: 24px; + --spacing-28: 28px; + --spacing-32: 32px; + --spacing-36: 36px; + --spacing-40: 40px; + --spacing-44: 44px; + --spacing-48: 48px; + --spacing-52: 52px; + --spacing-56: 56px; + --spacing-60: 60px; + --spacing-64: 64px; + --spacing-68: 68px; + --spacing-72: 72px; + --spacing-76: 76px; + --spacing-80: 80px; + --spacing-128: 128px; + + /* Font Weights */ + --font-weight-regular: 400; + --font-weight-bold: 700; + + /* Shadows */ + --shadow-3xl: 0px 902px 253px 0px rgba(65, 69, 124, 0.00), 0px 577px 231px 0px rgba(65, 69, 124, 0.01), 0px 325px 195px 0px rgba(65, 69, 124, 0.05), 0px 144px 144px 0px rgba(65, 69, 124, 0.09), 0px 36px 79px 0px rgba(65, 69, 124, 0.10) + +} \ No newline at end of file diff --git a/assets/css/theme.css b/assets/css/theme.css index dc8f31a..10987a5 100644 --- a/assets/css/theme.css +++ b/assets/css/theme.css @@ -1,4 +1,4 @@ -/* Theme variables based on tailwind.config.js */ + @theme { /* Primary Colors (water-blue) */ --color-primary-50: #E3F2FD; @@ -75,6 +75,7 @@ /* Font Family */ --font-family-sans: 'Roboto', sans-serif; --font-family-serif: 'Merriweather', serif; + --font-display: 'Montserrat', sans-serif; /* Box Shadows */ --shadow-base: 0px 1px 3px 0px rgba(0,0,0,0.1), 0px 1px 2px 0px rgba(0,0,0,0.06); diff --git a/assets/js/admin-charts.js b/assets/js/admin-charts.js new file mode 100644 index 0000000..9ab3818 --- /dev/null +++ b/assets/js/admin-charts.js @@ -0,0 +1,207 @@ +import { + Chart, + LineElement, + PointElement, + CategoryScale, + LinearScale, + Title, + Tooltip, + Legend, + LineController, + Filler +} from 'chart.js'; +import 'chartjs-adapter-moment'; + +// Register Chart.js components +Chart.register( + LineElement, + PointElement, + CategoryScale, + LinearScale, + Title, + Tooltip, + Legend, + LineController, + Filler +); + +export class AdminCharts { + constructor() { + this.charts = {}; + this.defaultOptions = { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + mode: 'index' + }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + usePointStyle: true, + padding: 20, + font: { + size: 12, + family: 'Inter, system-ui, sans-serif' + }, + color: 'rgba(255, 255, 255, 0.9)' + } + }, + tooltip: { + enabled: true, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: 'rgba(255, 255, 255, 0.9)', + bodyColor: 'rgba(255, 255, 255, 0.9)', + borderColor: 'rgba(255, 255, 255, 0.1)', + borderWidth: 1, + cornerRadius: 8, + displayColors: false, + padding: 12, + titleFont: { + size: 14, + weight: 'bold' + }, + bodyFont: { + size: 13 + } + } + }, + scales: { + x: { + display: true, + grid: { + display: false + }, + ticks: { + color: 'rgba(255, 255, 255, 0.7)', + font: { + size: 11 + } + } + }, + y: { + display: true, + grid: { + color: 'rgba(255, 255, 255, 0.1)', + drawBorder: false + }, + ticks: { + color: 'rgba(255, 255, 255, 0.7)', + font: { + size: 11 + }, + callback: function(value) { + return Number.isInteger(value) ? value : ''; + } + } + } + }, + elements: { + line: { + tension: 0.4, + borderWidth: 3, + fill: true + }, + point: { + radius: 0, + hoverRadius: 6, + hoverBorderWidth: 2, + hoverBorderColor: 'rgba(255, 255, 255, 0.9)' + } + }, + animation: { + duration: 800, + easing: 'easeInOutQuart' + } + }; + } + + createUsersChart(canvasId, data) { + const ctx = document.getElementById(canvasId); + if (!ctx) return null; + + const chartData = { + labels: data.labels, + datasets: [{ + label: 'Users', + data: data.values, + borderColor: 'rgba(102, 126, 234, 1)', + backgroundColor: 'rgba(102, 126, 234, 0.1)', + pointBackgroundColor: 'rgba(102, 126, 234, 1)', + pointBorderColor: 'rgba(255, 255, 255, 0.9)', + pointHoverBackgroundColor: 'rgba(102, 126, 234, 1)', + pointHoverBorderColor: 'rgba(255, 255, 255, 0.9)', + }] + }; + + if (this.charts[canvasId]) { + this.charts[canvasId].destroy(); + } + + this.charts[canvasId] = new Chart(ctx, { + type: 'line', + data: chartData, + options: this.defaultOptions + }); + + return this.charts[canvasId]; + } + + createEventsChart(canvasId, data) { + const ctx = document.getElementById(canvasId); + if (!ctx) return null; + + const chartData = { + labels: data.labels, + datasets: [{ + label: 'Events', + data: data.values, + borderColor: 'rgba(16, 185, 129, 1)', + backgroundColor: 'rgba(16, 185, 129, 0.1)', + pointBackgroundColor: 'rgba(16, 185, 129, 1)', + pointBorderColor: 'rgba(255, 255, 255, 0.9)', + pointHoverBackgroundColor: 'rgba(16, 185, 129, 1)', + pointHoverBorderColor: 'rgba(255, 255, 255, 0.9)', + }] + }; + + if (this.charts[canvasId]) { + this.charts[canvasId].destroy(); + } + + this.charts[canvasId] = new Chart(ctx, { + type: 'line', + data: chartData, + options: this.defaultOptions + }); + + return this.charts[canvasId]; + } + + updateChart(canvasId, data) { + const chart = this.charts[canvasId]; + if (!chart) return; + + chart.data.labels = data.labels; + chart.data.datasets[0].data = data.values; + chart.update('active'); + } + + destroyChart(canvasId) { + if (this.charts[canvasId]) { + this.charts[canvasId].destroy(); + delete this.charts[canvasId]; + } + } + + destroyAllCharts() { + Object.keys(this.charts).forEach(canvasId => { + this.destroyChart(canvasId); + }); + } +} + +// Create global instance +window.AdminCharts = new AdminCharts(); \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 4f31905..d277ff0 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -25,7 +25,9 @@ import QRCodeStyling from "qr-code-styling"; import { Presenter } from "./presenter"; import { Manager } from "./manager"; import Split from "split-grid"; +import CustomHooks from "./hooks"; import { TourGuideClient } from "@sjmc11/tourguidejs/src/Tour"; +import "./admin-charts.js"; window.moment = moment; // Get supported locales from backend configuration or fallback to default list @@ -304,6 +306,19 @@ Hooks.EmptyNickname = { }, }; +Hooks.SearchableSelect = { + mounted() { + this.handleEvent("update_hidden_field", (payload) => { + if (payload.id === this.el.id) { + this.el.value = payload.value; + // Trigger a change event to update the form + const event = new Event('input', { bubbles: true }); + this.el.dispatchEvent(event); + } + }); + } +}; + Hooks.PostForm = { onPress(e, submitBtn, TA) { if (e.key == "Enter" && !e.shiftKey) { @@ -616,6 +631,60 @@ Hooks.Dropdown = { }, }; +Hooks.AdminChart = { + mounted() { + const chartType = this.el.dataset.chartType; + const canvasId = this.el.querySelector('canvas').id; + const data = JSON.parse(this.el.dataset.chartData); + + if (chartType === 'users') { + window.AdminCharts.createUsersChart(canvasId, data); + } else if (chartType === 'events') { + window.AdminCharts.createEventsChart(canvasId, data); + } + + this.handleEvent("update_chart", (newData) => { + window.AdminCharts.updateChart(canvasId, newData); + }); + }, + + updated() { + const chartType = this.el.dataset.chartType; + const canvasId = this.el.querySelector('canvas').id; + const data = JSON.parse(this.el.dataset.chartData); + + if (chartType === 'users') { + window.AdminCharts.createUsersChart(canvasId, data); + } else if (chartType === 'events') { + window.AdminCharts.createEventsChart(canvasId, data); + } + }, + + destroyed() { + const canvasId = this.el.querySelector('canvas').id; + window.AdminCharts.destroyChart(canvasId); + } +}; + +Hooks.CSVDownloader = { + mounted() { + this.handleEvent("download_csv", ({ filename, content }) => { + const blob = new Blob([content], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }); + } +}; + +// Merge our custom hooks with the existing hooks +Object.assign(Hooks, CustomHooks); + let Uploaders = {}; Uploaders.S3 = function (entries, onViewError) { diff --git a/assets/js/hooks.js b/assets/js/hooks.js new file mode 100644 index 0000000..779e08c --- /dev/null +++ b/assets/js/hooks.js @@ -0,0 +1,197 @@ +// LiveView hooks for client-side functionality + +const Hooks = { + // Hook for handling CSV downloads from LiveView + CSVDownloader: { + mounted() { + this.handleEvent("download_csv", ({ filename, content }) => { + // Create a Blob with the CSV content + const blob = new Blob([content], { type: "text/csv" }); + + // Create a temporary URL for the Blob + const url = window.URL.createObjectURL(blob); + + // Create a temporary link element + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + + // Append the link to the document body + document.body.appendChild(link); + + // Trigger the download + link.click(); + + // Clean up + window.URL.revokeObjectURL(url); + document.body.removeChild(link); + }); + } + }, + + // Hook for User Growth Chart + UserGrowthChart: { + mounted() { + // Import Chart.js dynamically + import("chart.js/auto").then(({ default: Chart }) => { + const ctx = this.el.getContext("2d"); + const labels = JSON.parse(this.el.dataset.labels); + const values = JSON.parse(this.el.dataset.values); + + this.chart = new Chart(ctx, { + type: "line", + data: { + labels: labels, + datasets: [{ + label: "New Users", + data: values, + borderColor: "#111827", + backgroundColor: "rgba(17, 24, 39, 0.05)", + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0, + pointHoverRadius: 0, + pointBackgroundColor: "transparent", + pointBorderColor: "transparent" + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + mode: 'index' + }, + plugins: { + legend: { + display: false + }, + tooltip: { + enabled: true, + backgroundColor: "rgba(17, 24, 39, 0.9)", + titleColor: "#fff", + bodyColor: "#fff", + borderColor: "#111827", + borderWidth: 1, + cornerRadius: 4, + displayColors: false, + padding: 8, + titleFont: { + size: 12 + }, + bodyFont: { + size: 14, + weight: 'bold' + }, + callbacks: { + label: function(context) { + return context.parsed.y + ' users'; + } + } + } + }, + scales: { + x: { + display: false + }, + y: { + display: false + } + } + } + }); + }); + }, + + destroyed() { + if (this.chart) { + this.chart.destroy(); + } + } + }, + + // Hook for Event Creation Chart + EventCreationChart: { + mounted() { + // Import Chart.js dynamically + import("chart.js/auto").then(({ default: Chart }) => { + const ctx = this.el.getContext("2d"); + const labels = JSON.parse(this.el.dataset.labels); + const values = JSON.parse(this.el.dataset.values); + + this.chart = new Chart(ctx, { + type: "line", + data: { + labels: labels, + datasets: [{ + label: "New Events", + data: values, + borderColor: "#111827", + backgroundColor: "rgba(17, 24, 39, 0.05)", + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0, + pointHoverRadius: 0, + pointBackgroundColor: "transparent", + pointBorderColor: "transparent" + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + mode: 'index' + }, + plugins: { + legend: { + display: false + }, + tooltip: { + enabled: true, + backgroundColor: "rgba(17, 24, 39, 0.9)", + titleColor: "#fff", + bodyColor: "#fff", + borderColor: "#111827", + borderWidth: 1, + cornerRadius: 4, + displayColors: false, + padding: 8, + titleFont: { + size: 12 + }, + bodyFont: { + size: 14, + weight: 'bold' + }, + callbacks: { + label: function(context) { + return context.parsed.y + ' events'; + } + } + } + }, + scales: { + x: { + display: false + }, + y: { + display: false + } + } + } + }); + }); + }, + + destroyed() { + if (this.chart) { + this.chart.destroy(); + } + } + } +}; + +export default Hooks; diff --git a/assets/package-lock.json b/assets/package-lock.json index dbc1de5..86b08f2 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -9,6 +9,8 @@ "@tailwindcss/container-queries": "^0.1.1", "air-datepicker": "^3.5.0", "animate.css": "^4.1.1", + "chart.js": "^4.4.0", + "chartjs-adapter-moment": "^1.0.1", "moment": "^2.29.4", "moment-timezone": "^0.5.43", "phoenix": "file:../deps/phoenix", @@ -22,6 +24,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4.1.11", "alpinejs": "^3.13.8", + "daisyui": "^5.0.47", "esbuild": "^0.25.5", "postcss": "^8.4.38", "tailwindcss": "^4.1.11" @@ -560,6 +563,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@sjmc11/tourguidejs": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@sjmc11/tourguidejs/-/tourguidejs-0.0.16.tgz", @@ -921,6 +930,28 @@ "node": ">=8" } }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chartjs-adapter-moment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", + "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", + "license": "MIT", + "peerDependencies": { + "chart.js": ">=3.0.0", + "moment": "^2.10.2" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -954,6 +985,16 @@ "node": ">=18" } }, + "node_modules/daisyui": { + "version": "5.0.47", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.47.tgz", + "integrity": "sha512-RuYjjVKpodDoOYAHIvG6qC3BeRxhlyj4JCO+6aV0VzK+i3RWD7cmICh0m5+Xfr5938mV0Mk7FUOQ00msz8H8dw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -1881,6 +1922,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" + }, "@sjmc11/tourguidejs": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@sjmc11/tourguidejs/-/tourguidejs-0.0.16.tgz", @@ -2094,6 +2140,20 @@ "fill-range": "^7.1.1" } }, + "chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, + "chartjs-adapter-moment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", + "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", + "requires": {} + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2115,6 +2175,12 @@ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true }, + "daisyui": { + "version": "5.0.47", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.47.tgz", + "integrity": "sha512-RuYjjVKpodDoOYAHIvG6qC3BeRxhlyj4JCO+6aV0VzK+i3RWD7cmICh0m5+Xfr5938mV0Mk7FUOQ00msz8H8dw==", + "dev": true + }, "detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", diff --git a/assets/package.json b/assets/package.json index e944006..6911815 100644 --- a/assets/package.json +++ b/assets/package.json @@ -5,6 +5,7 @@ "devDependencies": { "@tailwindcss/postcss": "^4.1.11", "alpinejs": "^3.13.8", + "daisyui": "^5.0.47", "esbuild": "^0.25.5", "postcss": "^8.4.38", "tailwindcss": "^4.1.11" @@ -14,6 +15,8 @@ "@tailwindcss/container-queries": "^0.1.1", "air-datepicker": "^3.5.0", "animate.css": "^4.1.1", + "chart.js": "^4.4.0", + "chartjs-adapter-moment": "^1.0.1", "moment": "^2.29.4", "moment-timezone": "^0.5.43", "phoenix": "file:../deps/phoenix", diff --git a/config/config.exs b/config/config.exs index 2a1dcbf..51120c3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -44,6 +44,13 @@ config :tailwind, --output=priv/static/assets/app.css ), cd: Path.expand("..", __DIR__) + ], + admin: [ + args: ~w( + --input=assets/css/admin.css + --output=priv/static/assets/admin.css + ), + cd: Path.expand("..", __DIR__) ] # Configure esbuild (the version is required) diff --git a/config/dev.exs b/config/dev.exs index 3c68d69..41198be 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -14,6 +14,7 @@ config :claper, ClaperWeb.Endpoint, # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}, + tailwind_admin: {Tailwind, :install_and_run, [:admin, ~w(--watch)]}, sass: { DartSass, :install_and_run, diff --git a/dev.sh b/dev.sh deleted file mode 100755 index 9254574..0000000 --- a/dev.sh +++ /dev/null @@ -1,14 +0,0 @@ -set -a -source .env -set +a - -args=("$@") - -if [ "${args[0]}" == "start" ]; then - mix phx.server -elif [ "${args[0]}" == "iex" ]; then - iex -S mix -else - mix "$@" -fi - diff --git a/lib/claper/accounts.ex b/lib/claper/accounts.ex index f93508c..0570419 100644 --- a/lib/claper/accounts.ex +++ b/lib/claper/accounts.ex @@ -7,7 +7,7 @@ defmodule Claper.Accounts do alias Claper.Accounts alias Claper.Repo - alias Claper.Accounts.{User, UserToken, UserNotifier} + alias Claper.Accounts.{User, UserToken, UserNotifier, Role} @doc """ Creates a user. @@ -20,6 +20,9 @@ defmodule Claper.Accounts do {:error, %Ecto.Changeset{}} """ def create_user(attrs) do + # Get user role if not explicitly set + attrs = maybe_set_default_role(attrs) + %User{} |> User.registration_changeset(attrs) |> Repo.insert(returning: [:uuid]) @@ -55,18 +58,39 @@ defmodule Claper.Accounts do def get_user_by_email_or_create(email) when is_binary(email) do case get_user_by_email(email) do nil -> - create_user(%{ + attrs = %{ email: email, confirmed_at: DateTime.utc_now(), is_randomized_password: true, password: :crypto.strong_rand_bytes(32) - }) + } + + # Set default role if not explicitly set + attrs = maybe_set_default_role(attrs) + + create_user(attrs) user -> {:ok, user} end end + @doc """ + Lists all users that are not deleted. + + ## Examples + + iex> list_users() + [%User{}, ...] + + """ + def list_users(preload \\ []) do + User + |> where([u], is_nil(u.deleted_at)) + |> Repo.all() + |> Repo.preload(preload) + end + @doc """ Gets a user by email and password. @@ -147,6 +171,9 @@ defmodule Claper.Accounts do """ def register_user(attrs) do + # Get user role if not explicitly set + attrs = maybe_set_default_role(attrs) + %User{} |> User.registration_changeset(attrs) |> Repo.insert(returning: [:uuid]) @@ -165,6 +192,37 @@ defmodule Claper.Accounts do User.registration_changeset(user, attrs, hash_password: false) end + @doc """ + Returns an `%Ecto.Changeset{}` for tracking user changes for admin operations. + + ## Examples + + iex> change_user(user) + %Ecto.Changeset{data: %User{}} + + """ + def change_user(%User{} = user, attrs \\ %{}) do + User.admin_changeset(user, attrs, hash_password: false) + end + + @doc """ + Updates a user for admin operations. + + ## Examples + + iex> update_user(user, %{field: new_value}) + {:ok, %User{}} + + iex> update_user(user, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_user(%User{} = user, attrs) do + user + |> User.admin_changeset(attrs) + |> Repo.update() + end + ## Settings @doc """ @@ -262,6 +320,23 @@ defmodule Claper.Accounts do end end + # Alternative version with different signature - keeping for compatibility + def update_user_password(user, %{"current_password" => curr_pw} = attrs) do + changeset = + user + |> User.password_changeset(attrs) + |> User.validate_current_password(curr_pw) + + Ecto.Multi.new() + |> Ecto.Multi.update(:user, changeset) + |> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all)) + |> Repo.transaction() + |> case do + {:ok, %{user: user}} -> {:ok, user} + {:error, :user, changeset, _} -> {:error, changeset} + end + end + @doc """ Sets the user password. ## Examples @@ -343,34 +418,6 @@ defmodule Claper.Accounts do User.password_changeset(user, attrs) end - @doc """ - Updates the user password. - - ## Examples - - iex> update_user_password(user, "valid password", %{password: ...}) - {:ok, %User{}} - - iex> update_user_password(user, "invalid password", %{password: ...}) - {:error, %Ecto.Changeset{}} - - """ - def update_user_password(user, %{"current_password" => curr_pw} = attrs) do - changeset = - user - |> User.password_changeset(attrs) - |> User.validate_current_password(curr_pw) - - Ecto.Multi.new() - |> Ecto.Multi.update(:user, changeset) - |> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all)) - |> Repo.transaction() - |> case do - {:ok, %{user: user}} -> {:ok, user} - {:error, :user, changeset, _} -> {:error, changeset} - end - end - ## Session @doc """ @@ -602,4 +649,271 @@ defmodule Claper.Accounts do {:error, changeset} -> {:error, changeset} end end + + ## Role Management + + @doc """ + Returns the list of roles. + + ## Examples + + iex> list_roles() + [%Role{}, ...] + + """ + def list_roles do + Repo.all(Role) + end + + @doc """ + Gets a single role. + + Raises `Ecto.NoResultsError` if the Role does not exist. + + ## Examples + + iex> get_role!(123) + %Role{} + + iex> get_role!(456) + ** (Ecto.NoResultsError) + + """ + def get_role!(id), do: Repo.get!(Role, id) + + @doc """ + Gets a role by name. + + Returns nil if the Role does not exist. + + ## Examples + + iex> get_role_by_name("admin") + %Role{} + + iex> get_role_by_name("nonexistent") + nil + + """ + def get_role_by_name(name) when is_binary(name) do + Repo.get_by(Role, name: name) + end + + @doc """ + Creates a role. + + ## Examples + + iex> create_role(%{field: value}) + {:ok, %Role{}} + + iex> create_role(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_role(attrs \\ %{}) do + %Role{} + |> Role.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a role. + + ## Examples + + iex> update_role(role, %{field: new_value}) + {:ok, %Role{}} + + iex> update_role(role, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_role(%Role{} = role, attrs) do + role + |> Role.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a role. + + ## Examples + + iex> delete_role(role) + {:ok, %Role{}} + + iex> delete_role(role) + {:error, %Ecto.Changeset{}} + + """ + def delete_role(%Role{} = role) do + Repo.delete(role) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking role changes. + + ## Examples + + iex> change_role(role) + %Ecto.Changeset{data: %Role{}} + + """ + def change_role(%Role{} = role, attrs \\ %{}) do + Role.changeset(role, attrs) + end + + @doc """ + Assigns a role to a user by role name or role struct. + + ## Examples + + iex> assign_role(user, "admin") + {:ok, %User{}} + + iex> assign_role(user, %Role{id: 1}) + {:ok, %User{}} + + iex> assign_role(user, "unknown") + {:error, :role_not_found} + """ + def assign_role(%User{} = user, %Role{} = role) do + user + |> Ecto.Changeset.change(%{role_id: role.id}) + |> Repo.update() + end + + def assign_role(%User{} = user, role_name) when is_binary(role_name) do + case get_role_by_name(role_name) do + nil -> + {:error, :role_not_found} + + role -> + user + |> Ecto.Changeset.change(%{role_id: role.id}) + |> Repo.update() + end + end + + @doc """ + Gets the role of a user. + + ## Examples + + iex> get_user_role(user) + %Role{} + + iex> get_user_role(user_without_role) + nil + """ + def get_user_role(%User{} = user) do + user = user |> Repo.preload(:role) + user.role + end + + @doc """ + Lists users by role name. + + ## Examples + + iex> list_users_by_role("admin") + [%User{}, ...] + + iex> list_users_by_role("unknown") + [] + """ + def list_users_by_role(role_name) when is_binary(role_name) do + case get_role_by_name(role_name) do + nil -> + [] + + role -> + User + |> where([u], u.role_id == ^role.id and is_nil(u.deleted_at)) + |> Repo.all() + end + end + + @doc """ + Checks if a user has a specific role. + + ## Examples + + iex> user_has_role?(user, "admin") + true + + iex> user_has_role?(user, "unknown") + false + """ + def user_has_role?(%User{} = user, role_name) when is_binary(role_name) do + case get_user_role(user) do + nil -> false + role -> role.name == role_name + end + end + + @doc """ + Promotes a user to admin role. + + ## Examples + + iex> promote_to_admin(user) + {:ok, %User{}} + + iex> promote_to_admin(already_admin_user) + {:ok, %User{}} + """ + def promote_to_admin(%User{} = user) do + assign_role(user, "admin") + end + + @doc """ + Demotes a user from admin role to regular user role. + + ## Examples + + iex> demote_from_admin(admin_user) + {:ok, %User{}} + + iex> demote_from_admin(already_user_user) + {:ok, %User{}} + """ + def demote_from_admin(%User{} = user) do + assign_role(user, "user") + end + + # Private helper to set default role if not already set + defp maybe_set_default_role(attrs) do + # Only set default role if role_id is not explicitly provided + case Map.get(attrs, :role_id) || Map.get(attrs, "role_id") do + nil -> set_default_user_role(attrs) + _ -> attrs + end + end + + defp set_default_user_role(attrs) do + case get_role_by_name("user") do + nil -> attrs + user_role -> put_role_id(attrs, user_role.id) + end + end + + defp put_role_id(attrs, role_id) do + key = determine_role_key(attrs) + Map.put(attrs, key, role_id) + end + + defp determine_role_key(attrs) do + if has_string_keys?(attrs) do + "role_id" + else + :role_id + end + end + + defp has_string_keys?(attrs) do + is_map(attrs) and map_size(attrs) > 0 and + Enum.all?(Map.keys(attrs), &is_binary/1) + end end diff --git a/lib/claper/accounts/guardian.ex b/lib/claper/accounts/guardian.ex new file mode 100644 index 0000000..0af6669 --- /dev/null +++ b/lib/claper/accounts/guardian.ex @@ -0,0 +1,27 @@ +defmodule Claper.Accounts.Guardian do + @moduledoc """ + Implementation module for Guardian authentication. + This module handles JWT token generation and validation for user authentication. + """ + + defmodule Plug do + @moduledoc """ + Plug helpers for Guardian authentication in tests. + """ + + @doc """ + Sign in a user to a conn. + + ## Parameters + - conn: The connection + - user: The user to sign in + + ## Returns + - Updated conn with user signed in + """ + def sign_in(conn, user) do + # For tests, we'll just put the user in the conn assigns + Elixir.Plug.Conn.assign(conn, :current_user, user) + end + end +end diff --git a/lib/claper/accounts/oidc.ex b/lib/claper/accounts/oidc.ex new file mode 100644 index 0000000..3961494 --- /dev/null +++ b/lib/claper/accounts/oidc.ex @@ -0,0 +1,119 @@ +defmodule Claper.Accounts.Oidc do + @moduledoc """ + The OIDC context for authentication and provider management. + """ + + import Ecto.Query, warn: false + alias Claper.Repo + alias Claper.Accounts.Oidc.Provider + + @doc """ + Returns the list of oidc_providers. + + ## Examples + + iex> list_providers() + [%Provider{}, ...] + + """ + def list_providers do + Repo.all(Provider) + end + + @doc """ + Gets a single provider. + + Raises `Ecto.NoResultsError` if the Provider does not exist. + + ## Examples + + iex> get_provider!(123) + %Provider{} + + iex> get_provider!(456) + ** (Ecto.NoResultsError) + + """ + def get_provider!(id), do: Repo.get!(Provider, id) + + @doc """ + Creates a provider. + + ## Examples + + iex> create_provider(%{field: value}) + {:ok, %Provider{}} + + iex> create_provider(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_provider(attrs \\ %{}) do + %Provider{} + |> Provider.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a provider. + + ## Examples + + iex> update_provider(provider, %{field: new_value}) + {:ok, %Provider{}} + + iex> update_provider(provider, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_provider(%Provider{} = provider, attrs) do + provider + |> Provider.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a provider. + + ## Examples + + iex> delete_provider(provider) + {:ok, %Provider{}} + + iex> delete_provider(provider) + {:error, %Ecto.Changeset{}} + + """ + def delete_provider(%Provider{} = provider) do + Repo.delete(provider) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking provider changes. + + ## Examples + + iex> change_provider(provider) + %Ecto.Changeset{data: %Provider{}} + + """ + def change_provider(%Provider{} = provider, attrs \\ %{}) do + Provider.changeset(provider, attrs) + end + + @doc """ + Search providers by name or issuer. + + ## Examples + + iex> search_providers("%example%") + [%Provider{}, ...] + + """ + def search_providers(search_term) do + from(p in Provider, + where: ilike(p.name, ^search_term) or ilike(p.issuer, ^search_term) + ) + |> Repo.all() + end +end diff --git a/lib/claper/accounts/oidc/provider.ex b/lib/claper/accounts/oidc/provider.ex new file mode 100644 index 0000000..ba1c10f --- /dev/null +++ b/lib/claper/accounts/oidc/provider.ex @@ -0,0 +1,57 @@ +defmodule Claper.Accounts.Oidc.Provider do + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{ + id: integer(), + name: String.t(), + issuer: String.t(), + client_id: String.t(), + client_secret: String.t(), + redirect_uri: String.t(), + scope: String.t(), + active: boolean(), + response_type: String.t(), + response_mode: String.t(), + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + + schema "oidc_providers" do + field :name, :string + field :issuer, :string + field :client_id, :string + field :client_secret, :string + field :redirect_uri, :string + field :scope, :string, default: "openid email profile" + field :active, :boolean, default: true + field :response_type, :string, default: "code" + field :response_mode, :string, default: "query" + + timestamps() + end + + @doc """ + A changeset for creating or updating an OIDC provider. + """ + def changeset(provider, attrs) do + provider + |> cast(attrs, [ + :name, + :issuer, + :client_id, + :client_secret, + :redirect_uri, + :scope, + :active, + :response_type, + :response_mode + ]) + |> validate_required([:name, :issuer, :client_id, :client_secret, :redirect_uri]) + |> validate_format(:issuer, ~r/^https?:\/\//, message: "must start with http:// or https://") + |> validate_format(:redirect_uri, ~r/^https?:\/\//, + message: "must start with http:// or https://" + ) + |> unique_constraint(:name) + end +end diff --git a/lib/claper/accounts/role.ex b/lib/claper/accounts/role.ex new file mode 100644 index 0000000..d039093 --- /dev/null +++ b/lib/claper/accounts/role.ex @@ -0,0 +1,31 @@ +defmodule Claper.Accounts.Role do + use Ecto.Schema + import Ecto.Changeset + + @type t :: %__MODULE__{ + id: integer(), + name: String.t(), + permissions: map(), + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + + schema "roles" do + field :name, :string + field :permissions, :map, default: %{} + + has_many :users, Claper.Accounts.User + + timestamps() + end + + @doc """ + Changeset for creating or updating a role. + """ + def changeset(role, attrs) do + role + |> cast(attrs, [:name, :permissions]) + |> validate_required([:name]) + |> unique_constraint(:name) + end +end diff --git a/lib/claper/accounts/user.ex b/lib/claper/accounts/user.ex index 5c08bfd..2262642 100644 --- a/lib/claper/accounts/user.ex +++ b/lib/claper/accounts/user.ex @@ -13,6 +13,8 @@ defmodule Claper.Accounts.User do confirmed_at: NaiveDateTime.t() | nil, locale: String.t() | nil, events: [Claper.Events.Event.t()] | nil, + role: Claper.Accounts.Role.t() | nil, + role_id: integer() | nil, inserted_at: NaiveDateTime.t(), updated_at: NaiveDateTime.t(), deleted_at: NaiveDateTime.t() | nil @@ -30,6 +32,7 @@ defmodule Claper.Accounts.User do has_many :events, Claper.Events.Event has_one :lti_user, Lti13.Users.User + belongs_to :role, Claper.Accounts.Role has_many :quiz_responses, Claper.Quizzes.QuizResponse timestamps() @@ -37,9 +40,10 @@ defmodule Claper.Accounts.User do def registration_changeset(user, attrs, opts \\ []) do user - |> cast(attrs, [:email, :confirmed_at, :password, :is_randomized_password]) + |> cast(attrs, [:email, :confirmed_at, :password, :is_randomized_password, :role_id]) |> validate_email() |> validate_password(opts) + |> foreign_key_constraint(:role_id) end def preferences_changeset(user, attrs) do @@ -47,6 +51,17 @@ defmodule Claper.Accounts.User do |> cast(attrs, [:locale]) end + @doc """ + A changeset for admin operations on users. + """ + def admin_changeset(user, attrs, opts \\ []) do + user + |> cast(attrs, [:email, :confirmed_at, :password, :role_id]) + |> validate_email() + |> validate_admin_password(opts) + |> foreign_key_constraint(:role_id) + end + @doc """ A changeset for marking a user as deleted. """ @@ -71,6 +86,19 @@ defmodule Claper.Accounts.User do |> maybe_hash_password(opts) end + defp validate_admin_password(changeset, opts) do + password = get_change(changeset, :password) + + # Only validate password if it's provided + if password && password != "" do + changeset + |> validate_length(:password, min: 6, max: 72) + |> maybe_hash_password(opts) + else + changeset + end + end + defp maybe_hash_password(changeset, opts) do hash_password? = Keyword.get(opts, :hash_password, true) password = get_change(changeset, :password) diff --git a/lib/claper/admin.ex b/lib/claper/admin.ex new file mode 100644 index 0000000..25dd736 --- /dev/null +++ b/lib/claper/admin.ex @@ -0,0 +1,635 @@ +defmodule Claper.Admin do + @moduledoc """ + The Admin context. + Provides functions for admin dashboard statistics and paginated lists of resources. + """ + + import Ecto.Query, warn: false + alias Claper.Repo + alias Claper.Accounts.User + alias Claper.Events.Event + alias Claper.Accounts.Oidc.Provider + + @doc """ + Gets dashboard statistics. + + Returns a map with counts of users, events, and upcoming events. + + ## Examples + + iex> get_dashboard_stats() + %{users_count: 10, events_count: 20, upcoming_events: 5} + + """ + def get_dashboard_stats do + users_count = + User + |> where([u], is_nil(u.deleted_at)) + |> Repo.aggregate(:count, :id) + + events_count = + Event + |> Repo.aggregate(:count, :id) + + _now = NaiveDateTime.utc_now() + + upcoming_events = + Event + |> where([e], is_nil(e.expired_at)) + |> Repo.aggregate(:count, :id) + + %{ + users_count: users_count, + events_count: events_count, + upcoming_events: upcoming_events + } + end + + @doc """ + Gets users over time for analytics charts. + + Returns user registration data grouped by time period. + + ## Parameters + - period: :day, :week, :month (default: :day) + - days_back: number of days to look back (default: 30) + + ## Examples + + iex> get_users_over_time(:day, 7) + %{ + labels: ["2025-01-10", "2025-01-11", ...], + values: [2, 5, 1, 3, ...] + } + """ + def get_users_over_time(period \\ :day, days_back \\ 30) do + end_date = NaiveDateTime.utc_now() + start_date = NaiveDateTime.add(end_date, -(days_back * 24 * 60 * 60), :second) + + # Generate all dates in the range + date_range = generate_date_range(start_date, end_date, period) + + # Get actual user counts per period using raw SQL to avoid parameter conflicts + period_sql_value = period_sql(period) + + sql = """ + SELECT DATE_TRUNC($1, inserted_at) as period, COUNT(id) as count + FROM users + WHERE deleted_at IS NULL + AND inserted_at >= $2 + AND inserted_at <= $3 + GROUP BY DATE_TRUNC($1, inserted_at) + ORDER BY period + """ + + result = Repo.query!(sql, [period_sql_value, start_date, end_date]) + + user_counts = + result.rows + |> Enum.map(fn [period, count] -> + normalized_period = NaiveDateTime.truncate(period, :second) + {normalized_period, count} + end) + |> Enum.into(%{}) + + # Format data for charts + labels = Enum.map(date_range, &format_date_label(&1, period)) + + values = + Enum.map(date_range, fn date -> + Map.get(user_counts, truncate_date(date, period), 0) + end) + + %{ + labels: labels, + values: values + } + end + + @doc """ + Gets events over time for analytics charts. + + Returns event creation data grouped by time period. + + ## Parameters + - period: :day, :week, :month (default: :day) + - days_back: number of days to look back (default: 30) + + ## Examples + + iex> get_events_over_time(:day, 7) + %{ + labels: ["2025-01-10", "2025-01-11", ...], + values: [1, 3, 0, 2, ...] + } + """ + def get_events_over_time(period \\ :day, days_back \\ 30) do + end_date = NaiveDateTime.utc_now() + start_date = NaiveDateTime.add(end_date, -(days_back * 24 * 60 * 60), :second) + + # Generate all dates in the range + date_range = generate_date_range(start_date, end_date, period) + + # Get actual event counts per period using raw SQL to avoid parameter conflicts + period_sql_value = period_sql(period) + + sql = """ + SELECT DATE_TRUNC($1, inserted_at) as period, COUNT(id) as count + FROM events + WHERE inserted_at >= $2 + AND inserted_at <= $3 + GROUP BY DATE_TRUNC($1, inserted_at) + ORDER BY period + """ + + result = Repo.query!(sql, [period_sql_value, start_date, end_date]) + + event_counts = + result.rows + |> Enum.map(fn [period, count] -> + # Normalize the timestamp by removing microseconds + normalized_period = NaiveDateTime.truncate(period, :second) + {normalized_period, count} + end) + |> Enum.into(%{}) + + # Format data for charts + labels = Enum.map(date_range, &format_date_label(&1, period)) + + values = + Enum.map(date_range, fn date -> + Map.get(event_counts, truncate_date(date, period), 0) + end) + + %{ + labels: labels, + values: values + } + end + + @doc """ + Gets growth metrics for dashboard statistics. + + Returns percentage growth for users and events compared to previous period. + """ + def get_growth_metrics do + now = NaiveDateTime.utc_now() + thirty_days_ago = NaiveDateTime.add(now, -(30 * 24 * 60 * 60), :second) + sixty_days_ago = NaiveDateTime.add(now, -(60 * 24 * 60 * 60), :second) + + # Current period (last 30 days) + current_users = + User + |> where([u], is_nil(u.deleted_at)) + |> where([u], u.inserted_at >= ^thirty_days_ago and u.inserted_at <= ^now) + |> Repo.aggregate(:count, :id) + + current_events = + Event + |> where([e], e.inserted_at >= ^thirty_days_ago and e.inserted_at <= ^now) + |> Repo.aggregate(:count, :id) + + # Previous period (30-60 days ago) + previous_users = + User + |> where([u], is_nil(u.deleted_at)) + |> where([u], u.inserted_at >= ^sixty_days_ago and u.inserted_at < ^thirty_days_ago) + |> Repo.aggregate(:count, :id) + + previous_events = + Event + |> where([e], e.inserted_at >= ^sixty_days_ago and e.inserted_at < ^thirty_days_ago) + |> Repo.aggregate(:count, :id) + + %{ + users_growth: calculate_growth_percentage(current_users, previous_users), + events_growth: calculate_growth_percentage(current_events, previous_events) + } + end + + @doc """ + Gets recent activity stats for dashboard. + + Returns counts of recent activities. + """ + def get_activity_stats do + now = NaiveDateTime.utc_now() + twenty_four_hours_ago = NaiveDateTime.add(now, -(24 * 60 * 60), :second) + seven_days_ago = NaiveDateTime.add(now, -(7 * 24 * 60 * 60), :second) + + %{ + users_today: + User + |> where([u], is_nil(u.deleted_at)) + |> where([u], u.inserted_at >= ^twenty_four_hours_ago) + |> Repo.aggregate(:count, :id), + events_today: + Event + |> where([e], e.inserted_at >= ^twenty_four_hours_ago) + |> Repo.aggregate(:count, :id), + users_this_week: + User + |> where([u], is_nil(u.deleted_at)) + |> where([u], u.inserted_at >= ^seven_days_ago) + |> Repo.aggregate(:count, :id), + events_this_week: + Event + |> where([e], e.inserted_at >= ^seven_days_ago) + |> Repo.aggregate(:count, :id) + } + end + + # Private helper functions + + defp generate_date_range(start_date, end_date, period) do + start_date + |> NaiveDateTime.to_date() + |> Date.range(NaiveDateTime.to_date(end_date)) + |> Enum.to_list() + |> case do + dates when period == :day -> + dates + + dates when period == :week -> + dates |> Enum.chunk_every(7) |> Enum.map(&List.first/1) + + dates when period == :month -> + dates |> Enum.group_by(&Date.beginning_of_month/1) |> Map.keys() + end + end + + defp period_sql(period) do + case period do + :day -> "day" + :week -> "week" + :month -> "month" + end + end + + defp format_date_label(date, period) do + case period do + :day -> Date.to_string(date) + :week -> "Week of #{Date.to_string(date)}" + :month -> "#{Date.to_string(date) |> String.slice(0..6)}" + end + end + + defp truncate_date(date, period) do + naive_date = NaiveDateTime.new!(date, ~T[00:00:00]) + + case period do + :day -> + NaiveDateTime.truncate(naive_date, :second) + + :week -> + days_to_subtract = Date.day_of_week(date) - 1 + + date + |> Date.add(-days_to_subtract) + |> NaiveDateTime.new!(~T[00:00:00]) + |> NaiveDateTime.truncate(:second) + + :month -> + date + |> Date.beginning_of_month() + |> NaiveDateTime.new!(~T[00:00:00]) + |> NaiveDateTime.truncate(:second) + end + end + + defp calculate_growth_percentage(current, previous) do + cond do + previous == 0 and current > 0 -> + 100.0 + + previous == 0 and current == 0 -> + 0.0 + + previous > 0 -> + :erlang.float_to_binary(((current - previous) / previous * 100) |> Float.round(1), + decimals: 1 + ) + + true -> + 0.0 + end + end + + @doc """ + Returns a paginated list of users. + + ## Options + + * `:page` - The page number (default: 1) + * `:per_page` - The number of users per page (default: 20) + * `:search` - Search term for filtering users by email + * `:role` - Filter users by role name + + ## Examples + + iex> list_users_paginated(%{page: 1, per_page: 10}) + %{entries: [%User{}, ...], page_number: 1, page_size: 10, total_entries: 20, total_pages: 2} + + """ + def list_users_paginated(params \\ %{}) do + page = Map.get(params, "page", 1) + per_page = Map.get(params, "per_page", 20) + search = Map.get(params, "search", "") + role = Map.get(params, "role", "") + + query = + User + |> where([u], is_nil(u.deleted_at)) + |> preload(:role) + + query = + if search != "" do + query |> where([u], ilike(u.email, ^"%#{search}%")) + else + query + end + + query = + if role != "" do + query |> join(:inner, [u], r in assoc(u, :role), on: r.name == ^role) + else + query + end + + query = query |> order_by([u], desc: u.inserted_at) + + Repo.paginate(query, page: page, page_size: per_page) + end + + @doc """ + Returns a paginated list of events. + + ## Options + + * `:page` - The page number (default: 1) + * `:per_page` - The number of events per page (default: 20) + * `:search` - Search term for filtering events by name + * `:status` - Filter events by status (upcoming, past) + * `:start_date` - Filter events by start date + * `:end_date` - Filter events by end date + * `:creator_id` - Filter events by creator ID + + ## Examples + + iex> list_events_paginated(%{page: 1, per_page: 10}) + %{entries: [%Event{}, ...], page_number: 1, page_size: 10, total_entries: 20, total_pages: 2} + + """ + def list_events_paginated(params \\ %{}) do + page = Map.get(params, "page", 1) + per_page = Map.get(params, "per_page", 20) + search = Map.get(params, "search", "") + status = Map.get(params, "status", "") + start_date = Map.get(params, "start_date", nil) + end_date = Map.get(params, "end_date", nil) + creator_id = Map.get(params, "creator_id", nil) + + query = + Event + |> preload(:user) + + query = + if search != "" do + query |> where([e], ilike(e.name, ^"%#{search}%")) + else + query + end + + query = + case status do + "upcoming" -> + now = NaiveDateTime.utc_now() + query |> where([e], e.started_at > ^now) + + "past" -> + now = NaiveDateTime.utc_now() + query |> where([e], e.started_at <= ^now) + + _ -> + query + end + + query = + if start_date do + query |> where([e], e.started_at >= ^start_date) + else + query + end + + query = + if end_date do + query |> where([e], e.started_at <= ^end_date) + else + query + end + + query = + if creator_id do + query |> where([e], e.user_id == ^creator_id) + else + query + end + + query = query |> order_by([e], desc: e.started_at) + + Repo.paginate(query, page: page, page_size: per_page) + end + + @doc """ + Returns a paginated list of OIDC providers. + + ## Options + + * `:page` - The page number (default: 1) + * `:per_page` - The number of providers per page (default: 20) + * `:search` - Search term for filtering providers by name + + ## Examples + + iex> list_oidc_providers_paginated(%{page: 1, per_page: 10}) + %{entries: [%Provider{}, ...], page_number: 1, page_size: 10, total_entries: 20, total_pages: 2} + + """ + def list_oidc_providers_paginated(params \\ %{}) do + page = Map.get(params, "page", 1) + per_page = Map.get(params, "per_page", 20) + search = Map.get(params, "search", "") + + query = Provider + + query = + if search != "" do + query |> where([p], ilike(p.name, ^"%#{search}%")) + else + query + end + + query = query |> order_by([p], p.name) + + Repo.paginate(query, page: page, page_size: per_page) + end + + @doc """ + Returns a complete list of OIDC providers for export purposes. + + Unlike the paginated version, this returns all providers matching the search criteria. + + ## Options + + * `:search` - Search term for filtering providers by name + + ## Examples + + iex> list_all_oidc_providers(%{search: "Google"}) + [%Provider{}, ...] + + """ + def list_all_oidc_providers(params \\ %{}) do + search = Map.get(params, "search", "") + + query = Provider + + query = + if search != "" do + query |> where([p], ilike(p.name, ^"%#{search}%")) + else + query + end + + query = query |> order_by([p], p.name) + + Repo.all(query) + end + + @doc """ + Returns a complete list of events for export purposes. + + Unlike the paginated version, this returns all events matching the search criteria. + + ## Options + + * `:search` - Search term for filtering events by name + * `:status` - Filter events by status (upcoming, past) + * `:start_date` - Filter events by start date + * `:end_date` - Filter events by end date + * `:creator_id` - Filter events by creator ID + + ## Examples + + iex> list_all_events(%{search: "Conference"}) + [%Event{}, ...] + + """ + def list_all_events(params \\ %{}) do + Event + |> join(:left, [e], u in assoc(e, :user)) + |> preload([e, u], user: u) + |> apply_event_search_filter(Map.get(params, "search", "")) + |> apply_event_status_filter(Map.get(params, "status", "")) + |> apply_event_start_date_filter(Map.get(params, "start_date", nil)) + |> apply_event_end_date_filter(Map.get(params, "end_date", nil)) + |> apply_event_creator_filter(Map.get(params, "creator_id", nil)) + |> order_by([e], desc: e.started_at) + |> Repo.all() + |> Enum.map(fn event -> Map.put(event, :user_email, event.user.email) end) + end + + defp apply_event_search_filter(query, ""), do: query + + defp apply_event_search_filter(query, search) do + search_term = "%#{search}%" + + query + |> where( + [e, u], + ilike(e.name, ^search_term) or ilike(e.code, ^search_term) or + ilike(u.email, ^search_term) + ) + end + + defp apply_event_status_filter(query, "upcoming") do + now = NaiveDateTime.utc_now() + query |> where([e], e.started_at > ^now) + end + + defp apply_event_status_filter(query, "past") do + now = NaiveDateTime.utc_now() + query |> where([e], e.started_at <= ^now) + end + + defp apply_event_status_filter(query, _), do: query + + defp apply_event_start_date_filter(query, nil), do: query + + defp apply_event_start_date_filter(query, start_date) do + query |> where([e], e.started_at >= ^start_date) + end + + defp apply_event_end_date_filter(query, nil), do: query + + defp apply_event_end_date_filter(query, end_date) do + query |> where([e], e.started_at <= ^end_date) + end + + defp apply_event_creator_filter(query, nil), do: query + + defp apply_event_creator_filter(query, creator_id) do + query |> where([e], e.user_id == ^creator_id) + end + + @doc """ + Returns a complete list of users for export purposes. + + Unlike the paginated version, this returns all users matching the search criteria. + + ## Options + + * `:search` - Search term for filtering users by email or name + * `:role` - Filter users by role ID + + ## Examples + + iex> list_all_users(%{search: "admin"}) + [%User{}, ...] + + """ + def list_all_users(params \\ %{}) do + search = Map.get(params, "search", "") + role = Map.get(params, "role", "") + + query = + User + |> where([u], is_nil(u.deleted_at)) + |> preload(:role) + + query = + if search != "" do + query |> where([u], ilike(u.email, ^"%#{search}%") or ilike(u.name, ^"%#{search}%")) + else + query + end + + query = + if role != "" do + query |> where([u], u.role_id == ^role) + else + query + end + + query = query |> order_by([u], u.email) + + # Add a virtual field for role_name to make it accessible in CSV export + Repo.all(query) + |> Enum.map(fn user -> + role_name = if user.role, do: user.role.name, else: "none" + + user + |> Map.put(:role_name, role_name) + end) + end +end diff --git a/lib/claper/embeds/embed.ex b/lib/claper/embeds/embed.ex index d42661c..f05bc9d 100644 --- a/lib/claper/embeds/embed.ex +++ b/lib/claper/embeds/embed.ex @@ -61,7 +61,7 @@ defmodule Claper.Embeds.Embed do |> validate_format(:content, ~r/^https?:\/\/.+$/, message: gettext("Please enter a valid link starting with http:// or https://") ) - |> validate_format(:content, ~r/youtu\.be/, + |> validate_format(:content, ~r/(youtu\.be)|(youtube\.com)/, message: gettext("Please enter a valid %{provider} link", provider: "YouTube") ) diff --git a/lib/claper/events.ex b/lib/claper/events.ex index 131c8fa..74fbdf9 100644 --- a/lib/claper/events.ex +++ b/lib/claper/events.ex @@ -8,7 +8,9 @@ defmodule Claper.Events do import Ecto.Query, warn: false alias Claper.Repo + alias Claper.Accounts.User alias Claper.Events.{Event, ActivityLeader} + alias Claper.Presentations @default_page_size 5 @@ -22,7 +24,7 @@ defmodule Claper.Events do """ def list_events(user_id, preload \\ []) do - from(e in Event, where: e.user_id == ^user_id, order_by: [desc: e.inserted_at]) + from(e in Event, where: e.user_id == ^user_id, order_by: [desc: e.id]) |> Repo.all() |> Repo.preload(preload) end @@ -43,7 +45,7 @@ defmodule Claper.Events do query = from(e in Event, where: e.user_id == ^user_id, - order_by: [desc: e.inserted_at] + order_by: [desc: e.id] ) Repo.paginate(query, page: page, page_size: page_size, preload: preload) @@ -61,7 +63,7 @@ defmodule Claper.Events do def list_not_expired_events(user_id, preload \\ []) do from(e in Event, where: e.user_id == ^user_id and is_nil(e.expired_at), - order_by: [desc: e.inserted_at] + order_by: [desc: e.id] ) |> Repo.all() |> Repo.preload(preload) @@ -83,7 +85,7 @@ defmodule Claper.Events do query = from(e in Event, where: e.user_id == ^user_id and is_nil(e.expired_at), - order_by: [desc: e.inserted_at] + order_by: [desc: e.id] ) Repo.paginate(query, page: page, page_size: page_size, preload: preload) @@ -145,7 +147,7 @@ defmodule Claper.Events do join: e in Event, on: e.id == a.event_id, where: a.email == ^email, - order_by: [desc: e.expired_at], + order_by: [desc: e.expired_at, desc: e.id], select: e ) |> Repo.all() @@ -172,7 +174,7 @@ defmodule Claper.Events do join: e in Event, on: e.id == a.event_id, where: a.email == ^email, - order_by: [desc: e.expired_at], + order_by: [desc: e.expired_at, desc: e.id], select: e ) @@ -193,8 +195,7 @@ defmodule Claper.Events do def count_expired_events(user_id) do from(e in Event, - where: e.user_id == ^user_id and not is_nil(e.expired_at), - order_by: [desc: e.expired_at] + where: e.user_id == ^user_id and not is_nil(e.expired_at) ) |> Repo.aggregate(:count, :id) end @@ -207,28 +208,42 @@ defmodule Claper.Events do from(e in Event, where: e.user_id == ^user_id and e.inserted_at <= ^DateTime.utc_now() and - e.inserted_at >= ^last_month, - order_by: [desc: e.id] + e.inserted_at >= ^last_month ) |> Repo.aggregate(:count, :id) end @doc """ - Gets a single event. + Gets a single event by serial ID or UUID. Raises `Ecto.NoResultsError` if the Event does not exist. ## Examples + iex> get_event!(123) + %Event{} + iex> get_event!("123e4567-e89b-12d3-a456-426614174000") %Event{} + iex> get_event!(456) + ** (Ecto.NoResultsError) + iex> get_event!("123e4567-e89b-12d3-a456-4266141740111") ** (Ecto.NoResultsError) """ - def get_event!(id, preload \\ []), - do: Repo.get_by!(Event, uuid: id) |> Repo.preload(preload) + def get_event!(id_or_uuid, preload \\ []) + + def get_event!( + <<_::bytes-8, "-", _::bytes-4, "-", _::bytes-4, "-", _::bytes-4, "-", _::bytes-12>> = + uuid, + preload + ), + do: Repo.get_by!(Event, uuid: uuid) |> Repo.preload(preload) + + def get_event!(id, preload), + do: Repo.get!(Event, id) |> Repo.preload(preload) @doc """ Gets a single managed event. @@ -244,17 +259,18 @@ defmodule Claper.Events do ** (Ecto.NoResultsError) """ - def get_managed_event!(current_user, id, preload \\ []) do - event = Repo.get_by!(Event, uuid: id) - - is_leader = - Claper.Events.leaded_by?(current_user.email, event) || event.user_id == current_user.id - - if is_leader do - event |> Repo.preload(preload) - else - raise Ecto.NoResultsError - end + def get_managed_event!(user, uuid, preload \\ []) do + from( + a in ActivityLeader, + join: e in Event, + on: e.id == a.event_id, + join: u in User, + on: e.user_id == u.id, + where: e.uuid == ^uuid and (u.id == ^user.id or a.email == ^user.email), + select: e + ) + |> Repo.one!() + |> Repo.preload(preload) end @doc """ @@ -304,43 +320,22 @@ defmodule Claper.Events do |> Repo.preload(preload) end - @doc """ - Get a single event with the same code excluding a specific event. - - ## Examples - - iex> get_different_event_with_code("Hello", 123) - %Event{} - - - """ - def get_different_event_with_code(nil, _event_id), do: nil - - def get_different_event_with_code(code, event_id) do - now = DateTime.utc_now() - - from(e in Event, where: e.code == ^code and e.id != ^event_id and e.expired_at > ^now) - |> Repo.one() - end - @doc """ Check if a user is a facilitator of a specific event. ## Examples - iex> leaded_by?("email@example.com", 123) + iex> led_by?("email@example.com", 123) true - """ - def leaded_by?(email, event) do + def led_by?(email, event) do from(a in ActivityLeader, join: u in Claper.Accounts.User, on: u.email == a.email, join: e in Event, on: e.id == a.event_id, - where: a.email == ^email and e.id == ^event.id, - order_by: [desc: e.expired_at] + where: a.email == ^email and e.id == ^event.id ) |> Repo.exists?() end @@ -384,6 +379,15 @@ defmodule Claper.Events do end end + defp get_different_event_with_code(nil, _event_id), do: nil + + defp get_different_event_with_code(code, event_id) do + now = DateTime.utc_now() + + from(e in Event, where: e.code == ^code and e.id != ^event_id and e.expired_at > ^now) + |> Repo.one() + end + @doc """ Updates an event. @@ -517,7 +521,10 @@ defmodule Claper.Events do end @doc """ - Duplicate an event + Duplicates an event. + + Raises `Ecto.NoResultsError` for invalid `user_id`-`event_uuid` combinations + and returns an error tuple if any part of the transaction fails. ## Examples @@ -527,159 +534,201 @@ defmodule Claper.Events do iex> duplicate(user_id, event_uuid) {:error, %Ecto.Changeset{}} + iex> duplicate(another_user_id, event_uuid) + ** (Ecto.NoResultsError) + """ def duplicate_event(user_id, event_uuid) do - case Ecto.Multi.new() - |> Ecto.Multi.run(:original_event, fn _repo, _changes -> - {:ok, - get_user_event!(user_id, event_uuid, - presentation_file: [ - polls: [:poll_opts], - forms: [], - embeds: [], - quizzes: [:quiz_questions, quiz_questions: :quiz_question_opts], - presentation_state: [] - ] - )} - end) - |> Ecto.Multi.run(:new_event, fn _repo, %{original_event: original_event} -> - new_code = - for _ <- 1..5, - into: "", - do: <> + original = + get_user_event!(user_id, event_uuid, + presentation_file: [ + presentation_state: [], + polls: [:poll_opts], + forms: [], + embeds: [], + quizzes: [quiz_questions: [:quiz_question_opts]] + ] + ) - attrs = - Map.from_struct(original_event) - |> Map.drop([:id, :inserted_at, :updated_at, :presentation_file, :expired_at]) - |> Map.put(:leaders, []) - |> Map.put(:code, "#{new_code}") - |> Map.put(:name, "#{original_event.name} (Copy)") + multi = + Ecto.Multi.new() + |> Ecto.Multi.run(:event, fn _repo, changes -> duplicate_event_attrs(original, changes) end) + |> Ecto.Multi.run(:presentation_file, fn _repo, changes -> + duplicate_presentation_file(original, changes) + end) + |> Ecto.Multi.run(:presentation_state, fn _repo, changes -> + duplicate_presentation_state(original, changes) + end) + |> Ecto.Multi.run(:polls, fn _repo, changes -> duplicate_polls(original, changes) end) + |> Ecto.Multi.run(:forms, fn _repo, changes -> duplicate_forms(original, changes) end) + |> Ecto.Multi.run(:embeds, fn _repo, changes -> duplicate_embeds(original, changes) end) + |> Ecto.Multi.run(:quizzes, fn _repo, changes -> duplicate_quizzes(original, changes) end) - create_event(attrs) - end) - |> Ecto.Multi.run(:new_presentation_file, fn _repo, - %{ - original_event: original_event, - new_event: new_event - } -> - attrs = - Map.from_struct(original_event.presentation_file) - |> Map.drop([:id, :inserted_at, :updated_at, :presentation_state]) - |> Map.put(:event_id, new_event.id) - - Claper.Presentations.create_presentation_file(attrs) - end) - |> Ecto.Multi.run(:new_presentation_state, fn _repo, - %{ - original_event: original_event, - new_presentation_file: - new_presentation_file - } -> - attrs = - Map.from_struct(original_event.presentation_file.presentation_state) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:presentation_file_id, new_presentation_file.id) - |> Map.put(:position, 0) - |> Map.put(:banned, []) - - Claper.Presentations.create_presentation_state(attrs) - end) - |> Ecto.Multi.run(:polls, fn _repo, - %{ - new_presentation_file: new_presentation_file, - original_event: original_event - } -> - {:ok, - Enum.map(original_event.presentation_file.polls, fn poll -> - poll_attrs = - Map.from_struct(poll) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:presentation_file_id, new_presentation_file.id) - |> Map.put( - :poll_opts, - Enum.map(poll.poll_opts, fn opt -> - Map.from_struct(opt) - |> Map.drop([:id, :inserted_at, :updated_at]) - end) - ) - - {:ok, new_poll} = Claper.Polls.create_poll(poll_attrs) - new_poll - end)} - end) - |> Ecto.Multi.run(:forms, fn _repo, - %{ - new_presentation_file: new_presentation_file, - original_event: original_event - } -> - {:ok, - Enum.map(original_event.presentation_file.forms, fn form -> - form_attrs = - Map.from_struct(form) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:presentation_file_id, new_presentation_file.id) - |> Map.put( - :fields, - Enum.map(form.fields, &Map.from_struct(&1)) - ) - - {:ok, new_form} = Claper.Forms.create_form(form_attrs) - new_form - end)} - end) - |> Ecto.Multi.run(:embeds, fn _repo, - %{ - new_presentation_file: new_presentation_file, - original_event: original_event - } -> - {:ok, - Enum.map(original_event.presentation_file.embeds, fn embed -> - embed_attrs = - Map.from_struct(embed) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:presentation_file_id, new_presentation_file.id) - - {:ok, new_embed} = Claper.Embeds.create_embed(embed_attrs) - new_embed - end)} - end) - |> Ecto.Multi.run(:quizzes, fn _repo, - %{ - new_presentation_file: new_presentation_file, - original_event: original_event - } -> - {:ok, - Enum.map(original_event.presentation_file.quizzes, fn quiz -> - quiz_attrs = - Map.from_struct(quiz) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:presentation_file_id, new_presentation_file.id) - |> Map.put( - :quiz_questions, - Enum.map(quiz.quiz_questions, fn question -> - Map.from_struct(question) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put( - :quiz_question_opts, - Enum.map(question.quiz_question_opts, fn opt -> - Map.from_struct(opt) - |> Map.drop([:id, :inserted_at, :updated_at]) - |> Map.put(:response_count, 0) - end) - ) - end) - ) - - {:ok, new_quiz} = Claper.Quizzes.create_quiz(quiz_attrs) - new_quiz - end)} - end) - |> Repo.transaction() do - {:ok, %{new_event: new_event}} -> {:ok, new_event} - {:error, _failed_operation, failed_value, _changes_so_far} -> {:error, failed_value} + case Repo.transaction(multi) do + {:ok, %{event: event}} -> {:ok, event} + {:error, _operation, value, _changes} -> {:error, value} end end + defp duplicate_event_attrs(original, _changes) do + code = + for _ <- 1..5, + into: "", + do: <> + + attrs = + Map.from_struct(original) + |> Map.drop([:id, :inserted_at, :updated_at, :presentation_file, :expired_at]) + |> Map.put(:leaders, []) + |> Map.put(:code, "#{code}") + |> Map.put(:name, "#{original.name} (Copy)") + + create_event(attrs) + end + + defp duplicate_presentation_file(original, changes) do + case get_in(original.presentation_file) do + %Presentations.PresentationFile{} = presentation_file -> + attrs = + Map.from_struct(presentation_file) + |> Map.drop([:id, :inserted_at, :updated_at, :presentation_state]) + |> Map.put(:event_id, changes.event.id) + + Claper.Presentations.create_presentation_file(attrs) + + _ -> + {:ok, nil} + end + end + + defp duplicate_presentation_state(original, changes) do + case get_in(original.presentation_file.presentation_state) do + %Presentations.PresentationState{} = presentation_state -> + attrs = + Map.from_struct(presentation_state) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:presentation_file_id, changes.presentation_file.id) + |> Map.put(:position, 0) + |> Map.put(:banned, []) + + Claper.Presentations.create_presentation_state(attrs) + + _ -> + {:ok, nil} + end + end + + defp duplicate_polls(original, changes) do + case get_in(original.presentation_file.polls) do + polls when is_list(polls) -> + polls = + for poll <- polls do + attrs = + Map.from_struct(poll) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:presentation_file_id, changes.presentation_file.id) + |> Map.put( + :poll_opts, + Enum.map(poll.poll_opts, fn opt -> + Map.from_struct(opt) + |> Map.drop([:id, :inserted_at, :updated_at, :vote_count]) + end) + ) + + {:ok, poll} = Claper.Polls.create_poll(attrs) + poll + end + + {:ok, polls} + + _ -> + {:ok, nil} + end + end + + defp duplicate_forms(original, changes) do + case get_in(original.presentation_file.forms) do + forms when is_list(forms) -> + forms = + for form <- forms do + attrs = + Map.from_struct(form) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:presentation_file_id, changes.presentation_file.id) + |> Map.put( + :fields, + Enum.map(form.fields, &Map.from_struct(&1)) + ) + + {:ok, form} = Claper.Forms.create_form(attrs) + form + end + + {:ok, forms} + + _ -> + {:ok, nil} + end + end + + defp duplicate_embeds(original, changes) do + case get_in(original.presentation_file.embeds) do + embeds when is_list(embeds) -> + embeds = + for embed <- embeds do + attrs = + Map.from_struct(embed) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:presentation_file_id, changes.presentation_file.id) + + {:ok, embed} = Claper.Embeds.create_embed(attrs) + embed + end + + {:ok, embeds} + + _ -> + {:ok, nil} + end + end + + defp duplicate_quizzes(original, changes) do + case get_in(original.presentation_file.quizzes) do + quizzes when is_list(quizzes) -> + quizzes = + for quiz <- quizzes do + attrs = + Map.from_struct(quiz) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:presentation_file_id, changes.presentation_file.id) + |> Map.put(:quiz_questions, Enum.map(quiz.quiz_questions, &map_quiz_question/1)) + + {:ok, quiz} = Claper.Quizzes.create_quiz(attrs) + quiz + end + + {:ok, quizzes} + + _ -> + {:ok, nil} + end + end + + defp map_quiz_question(question) do + Map.from_struct(question) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put( + :quiz_question_opts, + Enum.map(question.quiz_question_opts, &map_quiz_question_opt/1) + ) + end + + defp map_quiz_question_opt(opt) do + Map.from_struct(opt) + |> Map.drop([:id, :inserted_at, :updated_at]) + |> Map.put(:response_count, 0) + end + @doc """ Deletes a event. diff --git a/lib/claper/events/event.ex b/lib/claper/events/event.ex index daf32c5..4ea9994 100644 --- a/lib/claper/events/event.ex +++ b/lib/claper/events/event.ex @@ -45,11 +45,12 @@ defmodule Claper.Events.Event do :code, :started_at, :expired_at, - :audience_peak + :audience_peak, + :user_id ]) |> cast_assoc(:presentation_file) |> cast_assoc(:leaders) - |> validate_required([:code, :name]) + |> validate_required([:name, :code, :started_at]) end def create_changeset(event, attrs) do @@ -57,7 +58,7 @@ defmodule Claper.Events.Event do |> cast(attrs, [:name, :code, :user_id, :started_at, :expired_at]) |> cast_assoc(:presentation_file) |> cast_assoc(:leaders) - |> validate_required([:name, :code, :started_at]) + |> validate_required([:name, :code, :started_at, :user_id]) |> validate_length(:code, min: 5, max: 10) |> validate_length(:name, min: 5, max: 50) |> downcase_code @@ -74,10 +75,10 @@ defmodule Claper.Events.Event do def update_changeset(event, attrs) do event - |> cast(attrs, [:name, :code, :started_at, :expired_at, :audience_peak]) + |> cast(attrs, [:name, :code, :started_at, :expired_at, :audience_peak, :user_id]) |> cast_assoc(:presentation_file) |> cast_assoc(:leaders) - |> validate_required([:code, :started_at]) + |> validate_required([:name, :code, :started_at, :user_id]) |> validate_length(:code, min: 5, max: 10) |> validate_length(:name, min: 5, max: 50) |> downcase_code diff --git a/lib/claper/quizzes/quiz.ex b/lib/claper/quizzes/quiz.ex index 2e46ccb..a0dca68 100644 --- a/lib/claper/quizzes/quiz.ex +++ b/lib/claper/quizzes/quiz.ex @@ -2,6 +2,22 @@ defmodule Claper.Quizzes.Quiz do use Ecto.Schema import Ecto.Changeset + @type t :: %__MODULE__{ + id: integer(), + title: String.t(), + position: integer() | nil, + enabled: boolean(), + show_results: boolean(), + allow_anonymous: boolean(), + lti_line_item_url: String.t() | nil, + lti_resource: Lti13.Resources.Resource.t() | nil, + quiz_responses: [Claper.Quizzes.QuizResponse.t()] | nil, + quiz_questions: [Claper.Quizzes.QuizQuestion.t()] | nil, + presentation_file: Claper.Presentations.PresentationFile.t(), + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + schema "quizzes" do field :title, :string field :position, :integer, default: 0 diff --git a/lib/claper/quizzes/quiz_question.ex b/lib/claper/quizzes/quiz_question.ex index 5162aaf..dac101e 100644 --- a/lib/claper/quizzes/quiz_question.ex +++ b/lib/claper/quizzes/quiz_question.ex @@ -4,6 +4,16 @@ defmodule Claper.Quizzes.QuizQuestion do use Gettext, backend: ClaperWeb.Gettext + @type t :: %__MODULE__{ + id: integer(), + content: String.t(), + type: String.t(), + quiz: Claper.Quizzes.Quiz.t() | nil, + quiz_question_opts: [Claper.Quizzes.QuizQuestionOpt.t()] | nil, + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + schema "quiz_questions" do field :content, :string field :type, :string, default: "qcm" diff --git a/lib/claper/quizzes/quiz_question_opt.ex b/lib/claper/quizzes/quiz_question_opt.ex index 263fcf4..f36c477 100644 --- a/lib/claper/quizzes/quiz_question_opt.ex +++ b/lib/claper/quizzes/quiz_question_opt.ex @@ -2,6 +2,17 @@ defmodule Claper.Quizzes.QuizQuestionOpt do use Ecto.Schema import Ecto.Changeset + @type t :: %__MODULE__{ + id: integer(), + content: String.t(), + is_correct: boolean(), + response_count: integer(), + percentage: float() | nil, + quiz_question: Claper.Quizzes.QuizQuestion.t() | nil, + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + schema "quiz_question_opts" do field :content, :string field :is_correct, :boolean, default: false diff --git a/lib/claper/quizzes/quiz_response.ex b/lib/claper/quizzes/quiz_response.ex index 9055e21..e8ad88f 100644 --- a/lib/claper/quizzes/quiz_response.ex +++ b/lib/claper/quizzes/quiz_response.ex @@ -2,6 +2,17 @@ defmodule Claper.Quizzes.QuizResponse do use Ecto.Schema import Ecto.Changeset + @type t :: %__MODULE__{ + id: integer(), + attendee_identifier: String.t() | nil, + quiz: Claper.Quizzes.Quiz.t() | nil, + quiz_question: Claper.Quizzes.QuizQuestion.t() | nil, + quiz_question_opt: Claper.Quizzes.QuizQuestionOpt.t() | nil, + user: Claper.Accounts.User.t() | nil, + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + schema "quiz_responses" do field :attendee_identifier, :string diff --git a/lib/claper/release.ex b/lib/claper/release.ex index 4401231..d230ce6 100644 --- a/lib/claper/release.ex +++ b/lib/claper/release.ex @@ -21,7 +21,8 @@ defmodule Claper.Release do for repo <- repos() do {:ok, _, _} = Ecto.Migrator.with_repo(repo, fn _repo -> - Code.eval_file("priv/repo/seeds.exs") + seed_script = Application.app_dir(@app, "priv/repo/seeds.exs") + Code.eval_file(seed_script) end) end end diff --git a/lib/claper_web/controllers/stat_controller.ex b/lib/claper_web/controllers/stat_controller.ex index 95e0be1..8b4f4bf 100644 --- a/lib/claper_web/controllers/stat_controller.ex +++ b/lib/claper_web/controllers/stat_controller.ex @@ -197,7 +197,7 @@ defmodule ClaperWeb.StatController do # Private functions defp authorize_event_access(user, event) do - if Events.leaded_by?(user.email, event) || event.user_id == user.id do + if Events.led_by?(user.email, event) || event.user_id == user.id do :ok else :unauthorized diff --git a/lib/claper_web/helpers/csv_exporter.ex b/lib/claper_web/helpers/csv_exporter.ex new file mode 100644 index 0000000..3a819ce --- /dev/null +++ b/lib/claper_web/helpers/csv_exporter.ex @@ -0,0 +1,169 @@ +defmodule ClaperWeb.Helpers.CSVExporter do + @moduledoc """ + Helper module for exporting data to CSV format. + + This module provides functions to convert collections of data + into CSV format for download in the admin panel. + """ + + @doc """ + Converts a list of records to CSV format. + + ## Parameters + - records: List of records/maps to convert + - headers: List of column headers + - fields: List of fields to include in the CSV + + ## Returns + - CSV formatted string + """ + def to_csv(records, headers, fields) do + records + |> build_rows(fields) + |> add_headers(headers) + |> CSV.encode() + |> Enum.to_list() + |> Enum.join("") + end + + @doc """ + Generates a timestamped filename for CSV exports. + + ## Parameters + - prefix: Prefix for the filename (e.g., "users", "events") + + ## Returns + - String filename with timestamp + """ + def generate_filename(prefix) do + date = DateTime.utc_now() |> Calendar.strftime("%Y%m%d_%H%M%S") + "#{prefix}_export_#{date}.csv" + end + + # Private helper functions + + defp build_rows(records, fields) do + Enum.map(records, fn record -> + Enum.map(fields, fn field -> + format_field_value(Map.get(record, field)) + end) + end) + end + + defp add_headers(rows, headers) do + [headers | rows] + end + + defp format_field_value(value) when is_boolean(value) do + if value, do: "Yes", else: "No" + end + + defp format_field_value(%DateTime{} = dt) do + Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S") + end + + defp format_field_value(%NaiveDateTime{} = dt) do + Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S") + end + + defp format_field_value(nil), do: "" + + defp format_field_value(value), do: to_string(value) + + @doc """ + Exports a list of users to CSV format. + + ## Parameters + - users: List of User structs to export + + ## Returns + - CSV formatted string + """ + def export_users_to_csv(users) do + headers = ["Email", "Name", "Role", "Created At"] + + # Transform users to include role name + users_with_role = + Enum.map(users, fn user -> + role_name = if user.role, do: user.role.name, else: "" + + %{ + email: user.email, + # Users don't have a name field currently + name: "", + role: role_name, + inserted_at: user.inserted_at + } + end) + + fields = [:email, :name, :role, :inserted_at] + + to_csv(users_with_role, headers, fields) + end + + @doc """ + Exports a list of events to CSV format. + + ## Parameters + - events: List of Event structs to export + + ## Returns + - CSV formatted string + """ + def export_events_to_csv(events) do + headers = [ + "Name", + "Description", + "Start Date", + "End Date", + "Status" + ] + + # Transform events to include description and status + events_transformed = + Enum.map(events, fn event -> + status = + cond do + event.expired_at && + NaiveDateTime.compare(event.expired_at, NaiveDateTime.utc_now()) == :lt -> + "completed" + + event.started_at && + NaiveDateTime.compare(event.started_at, NaiveDateTime.utc_now()) == :gt -> + "scheduled" + + true -> + "active" + end + + %{ + name: event.name, + # Events don't have a description field currently + description: "", + start_date: event.started_at, + end_date: event.expired_at, + status: status + } + end) + + fields = [:name, :description, :start_date, :end_date, :status] + + to_csv(events_transformed, headers, fields) + end + + @doc """ + Exports a list of OIDC providers to CSV format. + + ## Parameters + - providers: List of Provider structs to export + + ## Returns + - CSV formatted string + """ + def export_oidc_providers_to_csv(providers) do + headers = ["Name", "Issuer", "Client ID", "Active"] + fields = [:name, :issuer, :client_id, :active] + + to_csv(providers, headers, fields) + end +end diff --git a/lib/claper_web/live/admin_live/dashboard_live.ex b/lib/claper_web/live/admin_live/dashboard_live.ex new file mode 100644 index 0000000..af6e07d --- /dev/null +++ b/lib/claper_web/live/admin_live/dashboard_live.ex @@ -0,0 +1,109 @@ +defmodule ClaperWeb.AdminLive.DashboardLive do + use ClaperWeb, :live_view + + import Ecto.Query, warn: false + alias Claper.Admin + alias Claper.Events.Event + alias Claper.Repo + + @impl true + def mount(_params, session, socket) do + if connected?(socket) do + # Set up periodic updates every 30 seconds + :timer.send_interval(30_000, self(), :update_charts) + end + + with %{"locale" => locale} <- session do + Gettext.put_locale(ClaperWeb.Gettext, locale) + end + + socket = + socket + |> assign(:page_title, gettext("Dashboard")) + |> assign(:selected_period, :day) + |> assign(:days_back, 30) + |> load_dashboard_data() + + {:ok, socket} + end + + @impl true + def handle_params(_params, _url, socket) do + {:noreply, socket} + end + + @impl true + def handle_event("change_period", %{"period" => period}, socket) do + period_atom = String.to_atom(period) + + days_back = + case period_atom do + :day -> 30 + # 12 weeks + :week -> 84 + # 12 months + :month -> 365 + _ -> 30 + end + + socket = + socket + |> assign(:selected_period, period_atom) + |> assign(:days_back, days_back) + |> load_chart_data() + + {:noreply, socket} + end + + @impl true + def handle_event("refresh_data", _params, socket) do + socket = load_dashboard_data(socket) + {:noreply, socket} + end + + @impl true + def handle_info(:update_charts, socket) do + socket = load_chart_data(socket) + {:noreply, socket} + end + + defp load_dashboard_data(socket) do + stats = Admin.get_dashboard_stats() + growth_metrics = Admin.get_growth_metrics() + activity_stats = Admin.get_activity_stats() + + # Get recent events for the dashboard + recent_events = + Event + |> order_by([e], desc: e.started_at) + |> limit(5) + |> preload(:user) + |> Repo.all() + + # Transform stats to match template expectations + transformed_stats = %{ + total_users: stats.users_count, + total_events: stats.events_count, + active_events: stats.upcoming_events + } + + socket + |> assign(:stats, transformed_stats) + |> assign(:growth_metrics, growth_metrics) + |> assign(:activity_stats, activity_stats) + |> assign(:recent_events, recent_events) + |> load_chart_data() + end + + defp load_chart_data(socket) do + period = socket.assigns.selected_period + days_back = socket.assigns.days_back + + users_chart_data = Admin.get_users_over_time(period, days_back) + events_chart_data = Admin.get_events_over_time(period, days_back) + + socket + |> assign(:users_chart_data, users_chart_data) + |> assign(:events_chart_data, events_chart_data) + end +end diff --git a/lib/claper_web/live/admin_live/dashboard_live.html.heex b/lib/claper_web/live/admin_live/dashboard_live.html.heex new file mode 100644 index 0000000..ad22e66 --- /dev/null +++ b/lib/claper_web/live/admin_live/dashboard_live.html.heex @@ -0,0 +1,212 @@ + +
+

{gettext("Dashboard")}

+
+ + +
+ +
+
+
{gettext("Total Users")}
+
{@stats.total_users}
+
+ 0 do + "text-success" + else + "text-error" + end + }> + <%= if @growth_metrics.users_growth >= 0 do %> + ā†—ļøŽ + <% else %> + ā†˜ļøŽ + <% end %> + {@growth_metrics.users_growth}% + + {gettext("vs last month")} +
+
+ +
+
{gettext("Total Events")}
+
{@stats.total_events}
+
+ 0 do + "text-success" + else + "text-error" + end + }> + <%= if @growth_metrics.events_growth >= 0 do %> + ā†—ļøŽ + + <% else %> + ā†˜ļøŽ + <% end %> + {@growth_metrics.events_growth}% + + {gettext("vs last month")} +
+
+ +
+
{gettext("Active Events")}
+
{@stats.active_events}
+
{gettext("Currently running")}
+
+
+ + +
+
+
+

{gettext("User Growth")}

+

{gettext("Last 30 days")}

+
+ + +
+
+
+ +
+
+

{gettext("Event Creation")}

+

{gettext("Last 30 days")}

+
+ + +
+
+
+
+ + +
+
+
+

{gettext("Recent Events")}

+ +
+ + + + + + + + + + + + + + <%= for event <- @recent_events do %> + <% now = NaiveDateTime.utc_now() %> + <% {status_text, status_class} = + cond do + is_nil(event.expired_at) == false -> + {gettext("Completed"), "badge-ghost"} + + NaiveDateTime.compare(event.started_at, now) == :gt -> + {gettext("Scheduled"), "badge-info"} + + true -> + {gettext("Active"), "badge-success"} + end %> + + + + + + + + + + <% end %> + <%= if Enum.empty?(@recent_events) do %> + + + + <% end %> + +
{gettext("Name")}{gettext("Code")}{gettext("Owner")}{gettext("Start Date")}{gettext("Audience Peak")}{gettext("Status")}
+
{event.name}
+
+ {event.code} + {event.user.email}{Calendar.strftime(event.started_at, "%b %d, %Y")} +
{event.audience_peak}
+
+
+ {status_text} +
+
+
+ <.link + navigate={~p"/admin/events/#{event}"} + class="btn btn-link btn-xs" + title={gettext("View event")} + > + + + + + + <.link + navigate={~p"/admin/events/#{event}/edit"} + class="btn btn-link btn-xs text-primary" + title={gettext("Edit event")} + > + + + + +
+
+
+ {gettext("No events found")} +
+
+
+
+
+
+
diff --git a/lib/claper_web/live/admin_live/event_live.ex b/lib/claper_web/live/admin_live/event_live.ex new file mode 100644 index 0000000..cc65890 --- /dev/null +++ b/lib/claper_web/live/admin_live/event_live.ex @@ -0,0 +1,175 @@ +defmodule ClaperWeb.AdminLive.EventLive do + use ClaperWeb, :live_view + + alias Claper.Admin + alias Claper.Events.Event + alias ClaperWeb.Helpers.CSVExporter + + @impl true + def mount(_params, session, socket) do + with %{"locale" => locale} <- session do + Gettext.put_locale(ClaperWeb.Gettext, locale) + end + + {:ok, + socket + |> assign(:page_title, gettext("Events")) + |> assign(:events, list_events()) + |> assign(:search, "") + |> assign(:current_sort, %{field: :na, order: :asc})} + end + + @impl true + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Events") + |> assign(:event, nil) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, gettext("New event")) + |> assign(:event, %Event{}) + end + + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Edit event")) + |> assign(:event, Claper.Events.get_event!(id, [:user])) + end + + defp apply_action(socket, :show, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Event details")) + |> assign(:event, Claper.Events.get_event!(id, [:user])) + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + event = Claper.Events.get_event!(id) + {:ok, _} = Claper.Events.delete_event(event) + + {:noreply, + socket + |> put_flash(:info, gettext("Event deleted successfully")) + |> assign(:events, list_events())} + end + + @impl true + def handle_event("search", %{"search" => search}, socket) do + events = search_events(search) + {:noreply, socket |> assign(:search, search) |> assign(:events, events)} + end + + @impl true + def handle_event("sort", %{"field" => field}, socket) do + field = String.to_existing_atom(field) + current_sort = socket.assigns.current_sort + + direction = + if current_sort.field == field && current_sort.order == :asc, do: :desc, else: :asc + + events = sort_events(socket.assigns.events, field, direction) + current_sort = %{field: field, order: direction} + + {:noreply, + socket + |> assign(:events, events) + |> assign(:current_sort, current_sort)} + end + + @impl true + def handle_info({:export_csv_requested, _params}, socket) do + filename = CSVExporter.generate_filename("events") + csv_content = CSVExporter.export_events_to_csv(socket.assigns.events) + + {:noreply, + socket + |> put_flash(:info, gettext("Events exported successfully")) + |> push_event("download_csv", %{filename: filename, content: csv_content})} + end + + @impl true + def handle_info({:table_action, action, event, _event_id}, socket) do + case action do + :view -> + {:noreply, push_navigate(socket, to: ~p"/admin/events/#{event}")} + + :edit -> + {:noreply, push_navigate(socket, to: ~p"/admin/events/#{event}/edit")} + + :delete -> + {:ok, _} = Claper.Events.delete_event(event) + + {:noreply, + socket + |> put_flash(:info, gettext("Event deleted successfully")) + |> assign(:events, list_events())} + end + end + + defp list_events do + Admin.list_all_events() + end + + defp search_events(search) when search == "", do: list_events() + + defp search_events(search) do + Admin.list_all_events(%{"search" => search}) + end + + defp sort_events(events, field, order) do + Enum.sort_by(events, &Map.get(&1, field), order) + end + + def sort_indicator(assigns) do + ~H""" + <%= if @current_sort.field == @field do %> + <%= if @current_sort.order == :asc do %> + + + + <% else %> + + + + <% end %> + <% else %> + + + + <% end %> + """ + end +end diff --git a/lib/claper_web/live/admin_live/event_live.html.heex b/lib/claper_web/live/admin_live/event_live.html.heex new file mode 100644 index 0000000..079752f --- /dev/null +++ b/lib/claper_web/live/admin_live/event_live.html.heex @@ -0,0 +1,367 @@ +<%= case @live_action do %> + <% :index -> %> +
+
+
+

{gettext("Events")}

+
+ <%!-- <.link navigate={~p"/admin/events/new"} class="btn btn-primary btn-sm"> + + + + {gettext("New event")} + --%> +
+
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ + + + + + + + + + + + + + <%= if Enum.empty?(@events) do %> + + + + <% else %> + <%= for event <- @events do %> + + + + + + + + + + <% end %> + <% end %> + +
+ + + + + {gettext("Owner")} + + + + + + + + {gettext("Actions")} +
+ {gettext("No events found")} +
+ {event.name} + + {event.code} + + {if event.user, do: event.user.email, else: gettext("No owner")} + + {if event.started_at, + do: Calendar.strftime(event.started_at, "%Y-%m-%d %H:%M"), + else: gettext("Not started")} + + {if event.expired_at, + do: Calendar.strftime(event.expired_at, "%Y-%m-%d %H:%M"), + else: gettext("Not expired")} + +
+ {event.audience_peak || 0} +
+
+
+ <.link + navigate={~p"/admin/events/#{event}"} + class="btn btn-link btn-sm" + title={gettext("View event")} + > + + + + + + <.link + navigate={~p"/admin/events/#{event}/edit"} + class="btn btn-link btn-sm text-primary" + title={gettext("Edit event")} + > + + + + + + + + + +
+
+
+
+
+
+
+
+ <% :show -> %> +
+
+
+

{gettext("Event details")}

+
+ <.link navigate={~p"/admin/events"} class="btn btn-outline btn-sm"> + {gettext("Back to events")} + + <.link navigate={~p"/admin/events/#{@event}/edit"} class="btn btn-primary btn-sm"> + {gettext("Edit")} + +
+
+
+ +
+
+
+

{@event.name}

+

#{@event.code}

+
+
+
+
{gettext("Name")}
+
{@event.name}
+
+
+
{gettext("Owner")}
+
+ {if @event.user, do: @event.user.email, else: gettext("No owner")} +
+
+
+
{gettext("Started At")}
+
+ {if @event.started_at, + do: Calendar.strftime(@event.started_at, "%Y-%m-%d %H:%M"), + else: gettext("Not started")} +
+
+
+
{gettext("Expired At")}
+
+ {if @event.expired_at, + do: Calendar.strftime(@event.expired_at, "%Y-%m-%d %H:%M"), + else: gettext("Not expired")} +
+
+
+
{gettext("Audience Peak")}
+
+ {@event.audience_peak || 0} {gettext("attendees")} +
+
+
+
{gettext("Created At")}
+
+ {Calendar.strftime(@event.inserted_at, "%Y-%m-%d %H:%M")} +
+
+
+
{gettext("Last Updated")}
+
+ {Calendar.strftime(@event.updated_at, "%Y-%m-%d %H:%M")} +
+
+
+
+
+
+
+ <% :new -> %> +
+
+
+

{gettext("New event")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.EventLive.FormComponent} + id="event-form" + title={gettext("New event")} + action={:new} + event={@event} + navigate={~p"/admin/events"} + /> +
+
+
+
+ <% :edit -> %> +
+
+
+

{gettext("Edit event")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.EventLive.FormComponent} + id={"event-form-#{@event.id}"} + title={gettext("Edit event")} + action={:edit} + event={@event} + navigate={~p"/admin/events"} + /> +
+
+
+
+<% end %> diff --git a/lib/claper_web/live/admin_live/event_live/form_component.ex b/lib/claper_web/live/admin_live/event_live/form_component.ex new file mode 100644 index 0000000..bcbf1eb --- /dev/null +++ b/lib/claper_web/live/admin_live/event_live/form_component.ex @@ -0,0 +1,160 @@ +defmodule ClaperWeb.AdminLive.EventLive.FormComponent do + use ClaperWeb, :live_component + + alias Claper.Events + alias Claper.Accounts + + @impl true + def render(assigns) do + ~H""" +
+ <.form for={@form} id="event-form" phx-target={@myself} phx-change="validate" phx-submit="save"> +
+ <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="event-name" + form={@form} + field={:name} + type="text" + label={gettext("Name")} + placeholder={gettext("Enter event name")} + required={true} + width_class="sm:col-span-6" + description={gettext("A unique name for this event")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="event-code" + form={@form} + field={:code} + type="text" + label={gettext("Code")} + placeholder={gettext("Enter event code")} + required={true} + width_class="sm:col-span-3" + field_class="uppercase" + description={gettext("A unique code for participants to join this event")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="event-started-at" + form={@form} + field={:started_at} + type="datetime" + label={gettext("Started At")} + required={true} + width_class="sm:col-span-3" + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="event-expired-at" + form={@form} + field={:expired_at} + type="datetime" + label={gettext("Expired At")} + required={false} + width_class="sm:col-span-3" + description={gettext("When this event expires (optional)")} + /> + + <.live_component + module={ClaperWeb.AdminLive.SearchableSelectComponent} + id="event-user-id" + form={@form} + field={:user_id} + label={gettext("Assigned User")} + options={@user_options} + placeholder={gettext("Search for a user...")} + required={true} + width_class="sm:col-span-6" + description={gettext("The user who owns this event (required)")} + /> +
+ +
+
+ + +
+
+ +
+ """ + end + + @impl true + def update(%{event: event} = assigns, socket) do + changeset = Events.change_event(event) + + user_options = + Accounts.list_users() + |> Enum.map(&{"#{&1.email}", &1.id}) + + {:ok, + socket + |> assign(assigns) + |> assign(:user_options, user_options) + |> assign_form(changeset)} + end + + @impl true + def handle_event("validate", %{"event" => event_params}, socket) do + changeset = + socket.assigns.event + |> Events.change_event(event_params) + |> Map.put(:action, :validate) + + {:noreply, assign_form(socket, changeset)} + end + + def handle_event("save", %{"event" => event_params}, socket) do + save_event(socket, socket.assigns.action, event_params) + end + + def handle_event("cancel", _params, socket) do + {:noreply, push_navigate(socket, to: socket.assigns.navigate)} + end + + defp save_event(socket, :edit, event_params) do + case Events.update_event(socket.assigns.event, event_params) do + {:ok, event} -> + notify_parent({:saved, event}) + + {:noreply, + socket + |> put_flash(:info, gettext("Event updated successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp save_event(socket, :new, event_params) do + case Events.create_event(event_params) do + {:ok, event} -> + notify_parent({:saved, event}) + + {:noreply, + socket + |> put_flash(:info, gettext("Event created successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp assign_form(socket, %Ecto.Changeset{} = changeset) do + assign(socket, :form, to_form(changeset)) + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/lib/claper_web/live/admin_live/form_field_component.ex b/lib/claper_web/live/admin_live/form_field_component.ex new file mode 100644 index 0000000..9f1c44b --- /dev/null +++ b/lib/claper_web/live/admin_live/form_field_component.ex @@ -0,0 +1,184 @@ +defmodule ClaperWeb.AdminLive.FormFieldComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+ + <%= case @type do %> + <% "text" -> %> + {text_input( + @form, + @field, + [ + class: "input w-full " <> @field_class, + placeholder: @placeholder, + required: @required + ] ++ @extra_attrs + )} + <% "email" -> %> + {email_input( + @form, + @field, + [ + class: "input w-full", + placeholder: @placeholder, + required: @required + ] ++ @extra_attrs + )} + <% "password" -> %> +
+ {password_input( + @form, + @field, + [ + class: "input w-full pr-10", + placeholder: @placeholder, + required: @required, + id: "password-field-#{@field}" + ] ++ @extra_attrs + )} + +
+ <% "textarea" -> %> + {textarea( + @form, + @field, + [ + class: "input w-full", + placeholder: @placeholder, + required: @required, + rows: @rows + ] ++ @extra_attrs + )} + <% "select" -> %> + {select( + @form, + @field, + @select_options, + [ + class: "select w-full", + prompt: @prompt || gettext("Select an option"), + required: @required + ] ++ @extra_attrs + )} + <% "checkbox" -> %> +
+ +
+ <% "date" -> %> + {date_input( + @form, + @field, + [ + class: "input input-bordered w-full", + required: @required + ] ++ @extra_attrs + )} + <% "datetime" -> %> + {datetime_local_input( + @form, + @field, + [ + class: "input input-bordered w-full", + required: @required + ] ++ @extra_attrs + )} + <% "file" -> %> +
+ + + {if @selected_file, do: @selected_file, else: gettext("No file chosen")} + +
+ <% _ -> %> + {text_input( + @form, + @field, + [ + class: "input input-bordered w-full", + placeholder: @placeholder, + required: @required + ] ++ @extra_attrs + )} + <% end %> + + +
+
+ """ + end + + @impl true + def mount(socket) do + {:ok, assign(socket, selected_file: nil)} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:placeholder, fn -> "" end) + |> assign_new(:required, fn -> false end) + |> assign_new(:description, fn -> nil end) + |> assign_new(:width_class, fn -> nil end) + |> assign_new(:field_class, fn -> "" end) + |> assign_new(:checkbox_label, fn -> nil end) + |> assign_new(:prompt, fn -> nil end) + |> assign_new(:select_options, fn -> [] end) + |> assign_new(:rows, fn -> 3 end) + |> assign_new(:extra_attrs, fn -> [] end) + + {:ok, socket} + end + + @impl true + def handle_event("file_selected", %{"_target" => [_field_name]}, socket) do + {:noreply, assign(socket, selected_file: gettext("File selected"))} + end + + defp toggle_password_visibility(field_id) do + %Phoenix.LiveView.JS{} + |> Phoenix.LiveView.JS.dispatch("toggle-password", to: "##{field_id}") + end +end diff --git a/lib/claper_web/live/admin_live/modal_component.ex b/lib/claper_web/live/admin_live/modal_component.ex new file mode 100644 index 0000000..2fc3003 --- /dev/null +++ b/lib/claper_web/live/admin_live/modal_component.ex @@ -0,0 +1,196 @@ +defmodule ClaperWeb.AdminLive.ModalComponent do + use ClaperWeb, :live_component + alias Phoenix.LiveView.JS + + @impl true + def render(assigns) do + ~H""" + + """ + end + + @impl true + def mount(socket) do + {:ok, assign(socket, show: false)} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:show, fn -> false end) + |> assign_new(:icon, fn -> nil end) + |> assign_new(:icon_bg_class, fn -> "bg-red-100" end) + |> assign_new(:icon_text_class, fn -> "text-red-600" end) + |> assign_new(:description, fn -> nil end) + |> assign_new(:content, fn -> [] end) + |> assign_new(:confirm_action, fn -> nil end) + |> assign_new(:confirm_class, fn -> "bg-red-600 hover:bg-red-700 focus:ring-red-500" end) + |> assign_new(:cancel_action, fn -> "Cancel" end) + |> assign_new(:custom_actions, fn -> [] end) + |> assign_new(:size_class, fn -> "sm:max-w-lg sm:w-full" end) + + {:ok, socket} + end + + @impl true + def handle_event("hide", _params, socket) do + send(self(), {:modal_cancelled, socket.assigns.id}) + {:noreply, assign(socket, show: false)} + end + + def handle_event("confirm", _params, socket) do + send(self(), {:modal_confirmed, socket.assigns.id}) + {:noreply, assign(socket, show: false)} + end + + # Public API for controlling the modal + def show_modal(js \\ %JS{}, modal_id) do + js + |> JS.show(to: "##{modal_id}") + |> JS.add_class("animate-fade-in", to: "##{modal_id}") + end + + def hide_modal(js \\ %JS{}, modal_id) do + js + |> JS.add_class("animate-fade-out", to: "##{modal_id}") + |> JS.hide(to: "##{modal_id}", transition: "animate-fade-out", time: 200) + end + + # Preset configurations for common modal types + def delete_modal_config(title, description) do + %{ + icon: "fa-exclamation-triangle", + icon_bg_class: "bg-red-100", + icon_text_class: "text-red-600", + title: title, + description: description, + confirm_action: "Delete", + confirm_class: "bg-red-600 hover:bg-red-700 focus:ring-red-500", + cancel_action: "Cancel" + } + end + + def warning_modal_config(title, description) do + %{ + icon: "fa-exclamation-triangle", + icon_bg_class: "bg-yellow-100", + icon_text_class: "text-yellow-600", + title: title, + description: description, + confirm_action: "Continue", + confirm_class: "bg-yellow-600 hover:bg-yellow-700 focus:ring-yellow-500", + cancel_action: "Cancel" + } + end + + def info_modal_config(title, description) do + %{ + icon: "fa-info-circle", + icon_bg_class: "bg-blue-100", + icon_text_class: "text-blue-600", + title: title, + description: description, + confirm_action: "OK", + confirm_class: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500", + cancel_action: nil + } + end +end diff --git a/lib/claper_web/live/admin_live/oidc_provider_live.ex b/lib/claper_web/live/admin_live/oidc_provider_live.ex new file mode 100644 index 0000000..71d9b8d --- /dev/null +++ b/lib/claper_web/live/admin_live/oidc_provider_live.ex @@ -0,0 +1,155 @@ +defmodule ClaperWeb.AdminLive.OidcProviderLive do + use ClaperWeb, :live_view + + alias Claper.Accounts.Oidc + alias Claper.Accounts.Oidc.Provider + + @impl true + def mount(_params, session, socket) do + with %{"locale" => locale} <- session do + Gettext.put_locale(ClaperWeb.Gettext, locale) + end + + {:ok, + socket + |> assign(:page_title, gettext("OIDC Providers")) + |> assign(:providers, list_providers()) + |> assign(:search, "") + |> assign(:current_sort, %{field: :na, order: :asc})} + end + + @impl true + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "OIDC Providers") + |> assign(:provider, nil) + end + + defp apply_action(socket, :show, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Provider details")) + |> assign(:provider, Oidc.get_provider!(id)) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, gettext("New provider")) + |> assign(:provider, %Provider{}) + end + + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Edit provider")) + |> assign(:provider, Oidc.get_provider!(id)) + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + provider = Oidc.get_provider!(id) + {:ok, _} = Oidc.delete_provider(provider) + + {:noreply, + socket + |> put_flash(:info, gettext("Provider deleted successfully")) + |> assign(:providers, list_providers())} + end + + @impl true + def handle_event("search", %{"search" => search}, socket) do + providers = search_providers(search) + {:noreply, socket |> assign(:search, search) |> assign(:providers, providers)} + end + + @impl true + def handle_event("sort", %{"field" => field}, socket) do + %{current_sort: %{field: current_field, order: current_order}} = socket.assigns + + {field, order} = + if current_field == String.to_existing_atom(field) do + {current_field, if(current_order == :asc, do: :desc, else: :asc)} + else + {String.to_existing_atom(field), :asc} + end + + providers = sort_providers(socket.assigns.providers, field, order) + + {:noreply, + socket + |> assign(:providers, providers) + |> assign(:current_sort, %{field: field, order: order})} + end + + @impl true + def handle_info( + {ClaperWeb.AdminLive.OidcProviderLive.FormComponent, {:saved, _provider}}, + socket + ) do + {:noreply, assign(socket, :providers, list_providers())} + end + + defp list_providers do + Oidc.list_providers() + end + + defp search_providers(search) when search == "", do: list_providers() + + defp search_providers(search) do + search_term = "%#{search}%" + Oidc.search_providers(search_term) + end + + defp sort_providers(providers, field, order) do + Enum.sort_by(providers, &Map.get(&1, field), order) + end + + def sort_indicator(assigns) do + ~H""" + <%= if @current_sort.field == @field do %> + <%= if @current_sort.order == :asc do %> + + + + <% else %> + + + + <% end %> + <% else %> + + + + <% end %> + """ + end +end diff --git a/lib/claper_web/live/admin_live/oidc_provider_live.html.heex b/lib/claper_web/live/admin_live/oidc_provider_live.html.heex new file mode 100644 index 0000000..6369326 --- /dev/null +++ b/lib/claper_web/live/admin_live/oidc_provider_live.html.heex @@ -0,0 +1,358 @@ +<%= case @live_action do %> + <% :index -> %> +
+
+
+

{gettext("OIDC Providers")}

+
+ <.link navigate={~p"/admin/oidc_providers/new"} class="btn btn-primary btn-sm"> + + + + {gettext("New provider")} + +
+
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ + + + + + + + + + + + <%= if Enum.empty?(@providers) do %> + + + + <% else %> + <%= for provider <- @providers do %> + + + + + + + + <% end %> + <% end %> + +
+ + + + + + + + + {gettext("Actions")} +
+ {gettext("No providers found")} +
+ {provider.name} + + {provider.issuer} + + <%= if provider.active do %> + + {gettext("Active")} + + <% else %> + + {gettext("Inactive")} + + <% end %> + + {Calendar.strftime(provider.inserted_at, "%Y-%m-%d %H:%M")} + +
+ <.link + navigate={~p"/admin/oidc_providers/#{provider}"} + class="btn btn-link btn-sm" + title={gettext("View provider")} + > + + + + + + <.link + navigate={~p"/admin/oidc_providers/#{provider}/edit"} + class="btn btn-link btn-sm text-primary" + title={gettext("Edit provider")} + > + + + + + + + + + +
+
+
+
+
+
+
+
+ <% :show -> %> +
+
+
+

{gettext("OIDC Provider details")}

+
+ <.link navigate={~p"/admin/oidc_providers"} class="btn btn-outline btn-sm"> + {gettext("Back to providers")} + + <.link + navigate={~p"/admin/oidc_providers/#{@provider}/edit"} + class="btn btn-primary btn-sm" + > + {gettext("Edit")} + +
+
+
+ +
+
+
+

{@provider.name}

+

{gettext("OIDC Provider")}

+
+
+
+
{gettext("Name")}
+
{@provider.name}
+
+
+
{gettext("Issuer")}
+
+ {@provider.issuer} +
+
+
+
{gettext("Client ID")}
+
+ {@provider.client_id} +
+
+
+
{gettext("Response Type")}
+
+ {@provider.response_type} +
+
+
+
{gettext("Scope")}
+
+ {@provider.scope} +
+
+
+
{gettext("Status")}
+
+ <%= if @provider.active do %> + + {gettext("Active")} + + <% else %> + + {gettext("Inactive")} + + <% end %> +
+
+
+
{gettext("Created At")}
+
+ {Calendar.strftime(@provider.inserted_at, "%Y-%m-%d %H:%M")} +
+
+
+
{gettext("Last Updated")}
+
+ {Calendar.strftime(@provider.updated_at, "%Y-%m-%d %H:%M")} +
+
+
+
+
+
+
+ <% :new -> %> +
+
+
+

{gettext("New provider")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.OidcProviderLive.FormComponent} + id="provider-form" + title={gettext("New provider")} + action={:new} + provider={@provider} + navigate={~p"/admin/oidc_providers"} + /> +
+
+
+
+ <% :edit -> %> +
+
+
+

{gettext("Edit OIDC Provider")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.OidcProviderLive.FormComponent} + id={"provider-form-#{@provider.id}"} + title={gettext("Edit provider")} + action={:edit} + provider={@provider} + navigate={~p"/admin/oidc_providers"} + /> +
+
+
+
+<% end %> diff --git a/lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex b/lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex new file mode 100644 index 0000000..f9b6f63 --- /dev/null +++ b/lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex @@ -0,0 +1,222 @@ +defmodule ClaperWeb.AdminLive.OidcProviderLive.FormComponent do + use ClaperWeb, :live_component + + alias Claper.Accounts.Oidc + + @impl true + def render(assigns) do + ~H""" +
+ <.form + for={@form} + id="provider-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > +
+ <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="name-field" + form={@form} + field={:name} + type="text" + label={gettext("Name")} + placeholder={gettext("Enter provider name")} + required={true} + width_class="sm:col-span-6" + description={gettext("A unique name to identify this OIDC provider")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="issuer-field" + form={@form} + field={:issuer} + type="text" + label={gettext("Issuer URL")} + placeholder={gettext("https://example.com")} + required={true} + width_class="sm:col-span-6" + description={gettext("The OIDC issuer URL (must start with http:// or https://)")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="client_id-field" + form={@form} + field={:client_id} + type="text" + label={gettext("Client ID")} + placeholder={gettext("Enter client ID")} + required={true} + width_class="sm:col-span-3" + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="client_secret-field" + form={@form} + field={:client_secret} + type="text" + label={gettext("Client Secret")} + placeholder={gettext("Enter client secret")} + required={true} + width_class="sm:col-span-3" + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="redirect_uri-field" + form={@form} + field={:redirect_uri} + type="text" + label={gettext("Redirect URI")} + placeholder={gettext("https://yourapp.com/auth/callback")} + required={true} + width_class="sm:col-span-6" + description={ + gettext("The callback URL for your application (must start with http:// or https://)") + } + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="scope-field" + form={@form} + field={:scope} + type="text" + label={gettext("Scope")} + placeholder={gettext("openid email profile")} + width_class="sm:col-span-3" + description={gettext("OIDC scopes to request (defaults to 'openid email profile')")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="response_type-field" + form={@form} + field={:response_type} + type="select" + label={gettext("Response Type")} + select_options={[ + {gettext("Authorization Code"), "code"}, + {gettext("Implicit"), "token"}, + {gettext("Hybrid"), "code token"} + ]} + width_class="sm:col-span-3" + description={gettext("OAuth 2.0 response type (defaults to 'code')")} + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="response_mode-field" + form={@form} + field={:response_mode} + type="select" + label={gettext("Response Mode")} + select_options={[ + {gettext("Query"), "query"}, + {gettext("Fragment"), "fragment"}, + {gettext("Form Post"), "form_post"} + ]} + width_class="sm:col-span-3" + description={ + gettext("How the authorization response should be returned (defaults to 'query')") + } + /> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="active-field" + form={@form} + field={:active} + type="checkbox" + label={gettext("Active")} + checkbox_label={gettext("Enable this OIDC provider")} + width_class="sm:col-span-3" + description={ + gettext("Whether this provider is currently active and available for authentication") + } + /> +
+ +
+
+ + +
+
+ +
+ """ + end + + @impl true + def update(%{provider: provider} = assigns, socket) do + changeset = Oidc.change_provider(provider) + + {:ok, + socket + |> assign(assigns) + |> assign_form(changeset)} + end + + @impl true + def handle_event("validate", %{"provider" => provider_params}, socket) do + changeset = + socket.assigns.provider + |> Oidc.change_provider(provider_params) + |> Map.put(:action, :validate) + + {:noreply, assign_form(socket, changeset)} + end + + def handle_event("save", %{"provider" => provider_params}, socket) do + save_provider(socket, socket.assigns.action, provider_params) + end + + def handle_event("cancel", _params, socket) do + {:noreply, push_navigate(socket, to: socket.assigns.navigate)} + end + + defp save_provider(socket, :edit, provider_params) do + case Oidc.update_provider(socket.assigns.provider, provider_params) do + {:ok, provider} -> + notify_parent({:saved, provider}) + + {:noreply, + socket + |> put_flash(:info, gettext("Provider updated successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp save_provider(socket, :new, provider_params) do + case Oidc.create_provider(provider_params) do + {:ok, provider} -> + notify_parent({:saved, provider}) + + {:noreply, + socket + |> put_flash(:info, gettext("Provider created successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp assign_form(socket, %Ecto.Changeset{} = changeset) do + assign(socket, :form, to_form(changeset)) + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/lib/claper_web/live/admin_live/search_filter_component.ex b/lib/claper_web/live/admin_live/search_filter_component.ex new file mode 100644 index 0000000..9380f58 --- /dev/null +++ b/lib/claper_web/live/admin_live/search_filter_component.ex @@ -0,0 +1,175 @@ +defmodule ClaperWeb.AdminLive.SearchFilterComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+
+
+
+
+ +
+ +
+ + <%= if @filters && length(@filters) > 0 do %> +
+ <%= for filter <- @filters do %> + + <% end %> +
+ <% end %> + + + + <%= if @show_clear and (@search_value || has_active_filters?(@filter_values)) do %> + + <% end %> +
+
+ +
+ <%= if @export_csv_enabled do %> + + <% end %> + + <%= if @new_path do %> + <.link + navigate={@new_path} + class="ml-3 relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + > + + {@new_label || "New"} + + <% end %> + + <%= if @custom_actions do %> + {render_slot(@custom_actions)} + <% end %> +
+
+
+ """ + end + + @impl true + def mount(socket) do + {:ok, assign(socket, search_value: "", filter_values: %{})} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:search_placeholder, fn -> "Search..." end) + |> assign_new(:filters, fn -> [] end) + |> assign_new(:filter_values, fn -> %{} end) + |> assign_new(:search_value, fn -> "" end) + |> assign_new(:show_clear, fn -> true end) + |> assign_new(:export_csv_enabled, fn -> false end) + |> assign_new(:new_path, fn -> nil end) + |> assign_new(:new_label, fn -> "New" end) + |> assign_new(:custom_actions, fn -> [] end) + + {:ok, socket} + end + + @impl true + def handle_event("search", %{"search" => search_value}, socket) do + send( + self(), + {:search_filter_changed, %{search: search_value, filters: socket.assigns.filter_values}} + ) + + {:noreply, assign(socket, search_value: search_value)} + end + + def handle_event("search_change", %{"search" => search_value}, socket) do + send( + self(), + {:search_filter_changed, %{search: search_value, filters: socket.assigns.filter_values}} + ) + + {:noreply, assign(socket, search_value: search_value)} + end + + def handle_event("filter_change", %{"filter" => filter_name, "value" => filter_value}, socket) do + filter_values = Map.put(socket.assigns.filter_values, filter_name, filter_value) + + send( + self(), + {:search_filter_changed, %{search: socket.assigns.search_value, filters: filter_values}} + ) + + {:noreply, assign(socket, filter_values: filter_values)} + end + + def handle_event("clear_all", _params, socket) do + send(self(), {:search_filter_changed, %{search: "", filters: %{}}}) + {:noreply, assign(socket, search_value: "", filter_values: %{})} + end + + def handle_event("export_csv", _params, socket) do + send( + self(), + {:export_csv_requested, + %{search: socket.assigns.search_value, filters: socket.assigns.filter_values}} + ) + + {:noreply, socket} + end + + defp has_active_filters?(filter_values) do + Enum.any?(filter_values, fn {_key, value} -> value != nil and value != "" end) + end +end diff --git a/lib/claper_web/live/admin_live/searchable_select_component.ex b/lib/claper_web/live/admin_live/searchable_select_component.ex new file mode 100644 index 0000000..7cfc2c2 --- /dev/null +++ b/lib/claper_web/live/admin_live/searchable_select_component.ex @@ -0,0 +1,170 @@ +defmodule ClaperWeb.AdminLive.SearchableSelectComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+ + + + + +
+
+ """ + end + + @impl true + def mount(socket) do + {:ok, + socket + |> assign(:show_dropdown, false) + |> assign(:search_term, "") + |> assign(:filtered_options, []) + |> assign(:selected_value, nil) + |> assign(:display_value, "")} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:placeholder, fn -> gettext("Select...") end) + |> assign_new(:required, fn -> false end) + |> assign_new(:description, fn -> nil end) + |> assign_new(:width_class, fn -> nil end) + |> assign_new(:options, fn -> [] end) + |> update_filtered_options() + |> update_display_value() + + {:ok, socket} + end + + @impl true + def handle_event("search", %{"value" => search_term}, socket) do + socket = + socket + |> assign(:search_term, search_term) + |> assign(:show_dropdown, true) + |> update_filtered_options() + + {:noreply, socket} + end + + def handle_event("select_option", %{"value" => value, "label" => label}, socket) do + socket = + socket + |> assign(:selected_value, value) + |> assign(:display_value, label) + |> assign(:show_dropdown, false) + |> assign(:search_term, label) + + {:noreply, socket} + end + + def handle_event("open_dropdown", _params, socket) do + socket = + socket + |> assign(:show_dropdown, true) + |> update_filtered_options() + + {:noreply, socket} + end + + def handle_event("close_dropdown", _params, socket) do + {:noreply, assign(socket, :show_dropdown, false)} + end + + defp update_filtered_options(socket) do + search_term = String.downcase(socket.assigns[:search_term] || "") + + filtered = + if search_term == "" do + socket.assigns.options + else + Enum.filter(socket.assigns.options, fn {label, _value} -> + String.contains?(String.downcase(label), search_term) + end) + end + + assign(socket, :filtered_options, filtered) + end + + defp update_display_value(socket) do + current_value = get_field_value(socket.assigns.form, socket.assigns.field) + display_value = find_display_value(current_value, socket.assigns.options) + + socket + |> assign(:selected_value, current_value) + |> assign(:display_value, display_value) + |> assign(:search_term, display_value) + end + + defp find_display_value(nil, _options), do: "" + + defp find_display_value(current_value, options) do + case Enum.find(options, fn {_label, value} -> + to_string(value) == to_string(current_value) + end) do + {label, _value} -> label + nil -> "" + end + end + + defp get_field_value(form, field) do + Map.get(form.data, field) || Map.get(form.params, to_string(field)) + end +end diff --git a/lib/claper_web/live/admin_live/table_actions_component.ex b/lib/claper_web/live/admin_live/table_actions_component.ex new file mode 100644 index 0000000..4ccd225 --- /dev/null +++ b/lib/claper_web/live/admin_live/table_actions_component.ex @@ -0,0 +1,242 @@ +defmodule ClaperWeb.AdminLive.TableActionsComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+ <%= if @view_enabled do %> + + <% end %> + + <%= if @edit_enabled do %> + + <% end %> + + <%= if @delete_enabled do %> + + <% end %> + + <%= if @duplicate_enabled do %> + + <% end %> + + <%= if @archive_enabled do %> + + <% end %> + + <%= if @toggle_enabled do %> + + <% end %> + + <%= if @dropdown_actions && length(@dropdown_actions) > 0 do %> +
+ + + <%= if @dropdown_open do %> +
+
+ <%= for action <- @dropdown_actions do %> + + <% end %> +
+
+ <% end %> +
+ <% end %> + + <%= if @custom_actions do %> + {render_slot(@custom_actions)} + <% end %> +
+ """ + end + + @impl true + def mount(socket) do + {:ok, assign(socket, dropdown_open: false)} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:view_enabled, fn -> false end) + |> assign_new(:edit_enabled, fn -> true end) + |> assign_new(:delete_enabled, fn -> true end) + |> assign_new(:duplicate_enabled, fn -> false end) + |> assign_new(:archive_enabled, fn -> false end) + |> assign_new(:toggle_enabled, fn -> false end) + |> assign_new(:item_archived, fn -> false end) + |> assign_new(:item_active, fn -> true end) + |> assign_new(:delete_confirm_message, fn -> nil end) + |> assign_new(:toggle_active_title, fn -> nil end) + |> assign_new(:toggle_inactive_title, fn -> nil end) + |> assign_new(:dropdown_actions, fn -> [] end) + |> assign_new(:custom_actions, fn -> [] end) + |> assign_new(:dropdown_open, fn -> false end) + + {:ok, socket} + end + + @impl true + def handle_event("view_item", _params, socket) do + send(self(), {:table_action, :view, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("edit_item", _params, socket) do + send(self(), {:table_action, :edit, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("delete_item", _params, socket) do + send(self(), {:table_action, :delete, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("duplicate_item", _params, socket) do + send(self(), {:table_action, :duplicate, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("archive_item", _params, socket) do + action = if socket.assigns.item_archived, do: :unarchive, else: :archive + send(self(), {:table_action, action, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("toggle_item", _params, socket) do + action = if socket.assigns.item_active, do: :deactivate, else: :activate + send(self(), {:table_action, action, socket.assigns.item, socket.assigns.item_id}) + {:noreply, socket} + end + + def handle_event("toggle_dropdown", _params, socket) do + {:noreply, assign(socket, dropdown_open: !socket.assigns.dropdown_open)} + end + + def handle_event("close_dropdown", _params, socket) do + {:noreply, assign(socket, dropdown_open: false)} + end + + def handle_event("dropdown_action", %{"action" => action_key}, socket) do + action = Enum.find(socket.assigns.dropdown_actions, &(&1.key == action_key)) + + if action do + send( + self(), + {:table_action, String.to_atom(action_key), socket.assigns.item, socket.assigns.item_id} + ) + end + + {:noreply, assign(socket, dropdown_open: false)} + end +end diff --git a/lib/claper_web/live/admin_live/table_component.ex b/lib/claper_web/live/admin_live/table_component.ex new file mode 100644 index 0000000..cc4e20b --- /dev/null +++ b/lib/claper_web/live/admin_live/table_component.ex @@ -0,0 +1,282 @@ +defmodule ClaperWeb.AdminLive.TableComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+ + + + <%= for {header, _index} <- Enum.with_index(@headers) do %> + + <% end %> + + + + <%= if length(@rows) > 0 do %> + <%= for {row, row_index} <- Enum.with_index(@rows) do %> + + <%= for {cell_content, _cell_index} <- Enum.with_index(get_row_cells(row, @headers, @row_func)) do %> + + <% end %> + + <% end %> + <% else %> + + + + <% end %> + +
+
+ {if is_binary(header), do: header, else: header.label} + <%= if @sortable && header.sortable do %> + <%= case @sort_config do %> + <% %{field: field, direction: :asc} when field == header.field -> %> + + <% %{field: field, direction: :desc} when field == header.field -> %> + + <% _ -> %> + + <% end %> + <% end %> +
+
+ <%= case cell_content do %> + <% {:safe, content} -> %> + {raw(content)} + <% content when is_binary(content) -> %> + {content} + <% content -> %> + {to_string(content)} + <% end %> +
+
+ <%= if @empty_icon do %> + + <% end %> +

+ {@empty_title || "No items found"} +

+

+ {@empty_message || "There are no items to display."} +

+ <%= if @empty_action do %> +
+ {render_slot(@empty_action)} +
+ <% end %> +
+
+
+ + <%= if @pagination do %> +
+
+ <%= if @pagination.page_number > 1 do %> + + <% else %> + + Previous + + <% end %> + + <%= if @pagination.page_number < @pagination.total_pages do %> + + <% else %> + + Next + + <% end %> +
+ + +
+ <% end %> +
+ """ + end + + @impl true + def mount(socket) do + {:ok, assign(socket, sort_config: %{field: nil, direction: :asc})} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:sortable, fn -> false end) + |> assign_new(:sort_config, fn -> %{field: nil, direction: :asc} end) + |> assign_new(:row_click_enabled, fn -> false end) + |> assign_new(:empty_title, fn -> nil end) + |> assign_new(:empty_message, fn -> nil end) + |> assign_new(:empty_icon, fn -> nil end) + |> assign_new(:empty_action, fn -> [] end) + |> assign_new(:pagination, fn -> nil end) + |> assign_new(:row_func, fn -> nil end) + + {:ok, socket} + end + + @impl true + def handle_event("sort", %{"field" => field}, socket) do + current_sort = socket.assigns.sort_config + + new_direction = + if current_sort.field == field and current_sort.direction == :asc do + :desc + else + :asc + end + + sort_config = %{field: field, direction: new_direction} + + send(self(), {:table_sort_changed, sort_config}) + {:noreply, assign(socket, sort_config: sort_config)} + end + + def handle_event("paginate", %{"page" => page}, socket) do + page_number = String.to_integer(page) + send(self(), {:table_page_changed, page_number}) + {:noreply, socket} + end + + def handle_event("row_clicked", %{"row-index" => row_index}, socket) do + index = String.to_integer(row_index) + row = Enum.at(socket.assigns.rows, index) + send(self(), {:table_row_clicked, row, index}) + {:noreply, socket} + end + + defp get_row_cells(row, headers, nil) do + # Default behavior: assume row is a list/tuple matching header count + case row do + row when is_list(row) -> row + row when is_tuple(row) -> Tuple.to_list(row) + _ -> List.duplicate("", length(headers)) + end + end + + defp get_row_cells(row, _headers, row_func) when is_function(row_func) do + row_func.(row) + end + + defp get_page_range(pagination) do + start_page = max(1, pagination.page_number - 2) + end_page = min(pagination.total_pages, pagination.page_number + 2) + start_page..end_page + end +end diff --git a/lib/claper_web/live/admin_live/user_live.ex b/lib/claper_web/live/admin_live/user_live.ex new file mode 100644 index 0000000..df6266c --- /dev/null +++ b/lib/claper_web/live/admin_live/user_live.ex @@ -0,0 +1,192 @@ +defmodule ClaperWeb.AdminLive.UserLive do + use ClaperWeb, :live_view + + alias Claper.Admin + alias Claper.Accounts + alias Claper.Accounts.User + alias ClaperWeb.Helpers.CSVExporter + + @impl true + def mount(_params, session, socket) do + with %{"locale" => locale} <- session do + Gettext.put_locale(ClaperWeb.Gettext, locale) + end + + {:ok, + socket + |> assign(:page_title, gettext("Users")) + |> assign(:users, list_users()) + |> assign(:search, "") + |> assign(:current_sort, %{field: :na, order: :asc})} + end + + @impl true + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Users") + |> assign(:user, nil) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, gettext("New user")) + |> assign(:user, %User{}) + end + + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Edit user")) + |> assign(:user, Accounts.get_user!(id) |> Claper.Repo.preload(:role)) + end + + defp apply_action(socket, :show, %{"id" => id}) do + socket + |> assign(:page_title, gettext("User details")) + |> assign(:user, Accounts.get_user!(id) |> Claper.Repo.preload(:role)) + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + user = Accounts.get_user!(id) + {:ok, _} = Accounts.delete_user(user) + + {:noreply, + socket + |> put_flash(:info, gettext("User deleted successfully")) + |> assign(:users, list_users())} + end + + @impl true + def handle_event("sort", %{"field" => field}, socket) do + field_atom = String.to_existing_atom(field) + current_sort = socket.assigns.current_sort + + new_direction = + if current_sort.field == field_atom do + if current_sort.order == :asc, do: :desc, else: :asc + else + :asc + end + + users = sort_users(socket.assigns.users, field_atom, new_direction) + current_sort = %{field: field_atom, order: new_direction} + + {:noreply, + socket + |> assign(:users, users) + |> assign(:current_sort, current_sort)} + end + + @impl true + def handle_info({:search_filter_changed, %{search: search, filters: _filters}}, socket) do + users = search_users(search) + {:noreply, socket |> assign(:search, search) |> assign(:users, users)} + end + + @impl true + def handle_info({:export_csv_requested, _params}, socket) do + filename = CSVExporter.generate_filename("users") + csv_content = CSVExporter.export_users_to_csv(socket.assigns.users) + + {:noreply, + socket + |> put_flash(:info, gettext("Users exported successfully")) + |> push_event("download_csv", %{filename: filename, content: csv_content})} + end + + @impl true + def handle_info({:table_sort_changed, sort_config}, socket) do + %{field: field, direction: direction} = sort_config + users = sort_users(socket.assigns.users, field, direction) + current_sort = %{field: field, order: direction} + + {:noreply, + socket + |> assign(:users, users) + |> assign(:current_sort, current_sort)} + end + + @impl true + def handle_info({:table_action, action, user, _user_id}, socket) do + case action do + :view -> + {:noreply, push_navigate(socket, to: ~p"/admin/users/#{user}")} + + :edit -> + {:noreply, push_navigate(socket, to: ~p"/admin/users/#{user}/edit")} + + :delete -> + {:ok, _} = Accounts.delete_user(user) + + {:noreply, + socket + |> put_flash(:info, gettext("User deleted successfully")) + |> assign(:users, list_users())} + end + end + + def list_users do + Admin.list_all_users() + end + + defp search_users(search) when search == "", do: list_users() + + defp search_users(search) do + Admin.list_all_users(%{"search" => search}) + end + + defp sort_users(users, field, order) do + Enum.sort_by(users, &Map.get(&1, field), order) + end + + def sort_indicator(assigns) do + ~H""" + <%= if @current_sort.field == @field do %> + <%= if @current_sort.order == :asc do %> + + + + <% else %> + + + + <% end %> + <% else %> + + + + <% end %> + """ + end +end diff --git a/lib/claper_web/live/admin_live/user_live.html.heex b/lib/claper_web/live/admin_live/user_live.html.heex new file mode 100644 index 0000000..283b05c --- /dev/null +++ b/lib/claper_web/live/admin_live/user_live.html.heex @@ -0,0 +1,348 @@ +<%= case @live_action do %> + <% :index -> %> +
+
+
+

{gettext("Users")}

+
+ <%!-- <.link navigate={~p"/admin/users/new"} class="btn btn-primary btn-sm"> + + + + {gettext("New user")} + --%> +
+
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ + + + + + + + + + + + <%= if Enum.empty?(@users) do %> + + + + <% else %> + <%= for user <- @users do %> + + + + + + + + <% end %> + <% end %> + +
+ + + + + + + + + {gettext("Actions")} +
+ {gettext("No users found")} +
+ {user.email} + + {if user.role, do: user.role.name, else: gettext("No role")} + + <%= if user.confirmed_at do %> + + {gettext("Confirmed")} + + <% else %> + + {gettext("Unconfirmed")} + + <% end %> + + {Calendar.strftime(user.inserted_at, "%Y-%m-%d %H:%M")} + +
+ <.link + navigate={~p"/admin/users/#{user}"} + class="btn btn-link btn-sm" + title={gettext("View user")} + > + + + + + + <.link + navigate={~p"/admin/users/#{user}/edit"} + class="btn btn-link btn-sm text-primary" + title={gettext("Edit user")} + > + + + + + + + + + +
+
+
+
+
+
+
+
+ <% :show -> %> +
+
+
+

{gettext("User details")}

+
+ <.link navigate={~p"/admin/users"} class="btn btn-outline btn-sm"> + {gettext("Back to users")} + + <.link navigate={~p"/admin/users/#{@user}/edit"} class="btn btn-primary btn-sm"> + {gettext("Edit")} + +
+
+
+ +
+
+
+

{@user.email}

+

{gettext("User Account")}

+
+
+
+
{gettext("Email")}
+
{@user.email}
+
+
+
{gettext("Role")}
+
+ {if @user.role, do: @user.role.name, else: gettext("No role")} +
+
+
+
{gettext("Status")}
+
+ <%= if @user.confirmed_at do %> + + {gettext("Confirmed")} + + <% else %> + + {gettext("Unconfirmed")} + + <% end %> +
+
+
+
{gettext("Created At")}
+
+ {Calendar.strftime(@user.inserted_at, "%Y-%m-%d %H:%M")} +
+
+
+
{gettext("Last Updated")}
+
+ {Calendar.strftime(@user.updated_at, "%Y-%m-%d %H:%M")} +
+
+ <%= if @user.confirmed_at do %> +
+
{gettext("Confirmed At")}
+
+ {Calendar.strftime(@user.confirmed_at, "%Y-%m-%d %H:%M")} +
+
+ <% end %> +
+
+
+
+
+ <% :new -> %> +
+
+
+

{gettext("New user")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.UserLive.FormComponent} + id="user-form" + title={gettext("New user")} + action={:new} + user={@user} + navigate={~p"/admin/users"} + /> +
+
+
+
+ <% :edit -> %> +
+
+
+

{gettext("Edit user")}

+
+
+ +
+
+
+ <.live_component + module={ClaperWeb.AdminLive.UserLive.FormComponent} + id={"user-form-#{@user.id}"} + title={gettext("Edit user")} + action={:edit} + user={@user} + navigate={~p"/admin/users"} + /> +
+
+
+
+<% end %> diff --git a/lib/claper_web/live/admin_live/user_live/form_component.ex b/lib/claper_web/live/admin_live/user_live/form_component.ex new file mode 100644 index 0000000..ae9d08e --- /dev/null +++ b/lib/claper_web/live/admin_live/user_live/form_component.ex @@ -0,0 +1,196 @@ +defmodule ClaperWeb.AdminLive.UserLive.FormComponent do + use ClaperWeb, :live_component + + alias Claper.Accounts + + @impl true + def render(assigns) do + ~H""" +
+ <.form for={@form} id="user-form" phx-target={@myself} phx-change="validate" phx-submit="save"> +
+ <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="user-email" + form={@form} + field={:email} + type="email" + label={gettext("Email")} + placeholder={gettext("Enter user email")} + required={true} + width_class="sm:col-span-6" + description={gettext("User's email address (must be unique)")} + /> + + <%= if @action == :new do %> + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="user-password" + form={@form} + field={:password} + type="password" + label={gettext("Password")} + placeholder={gettext("Enter password")} + required={true} + width_class="sm:col-span-3" + description={gettext("Initial password for the user")} + /> + <% end %> + + <.live_component + module={ClaperWeb.AdminLive.FormFieldComponent} + id="user-role-id" + form={@form} + field={:role_id} + type="select" + label={gettext("Role")} + select_options={@role_options} + required={true} + width_class="sm:col-span-3" + description={gettext("User's access level")} + /> + +
+
+ +
+
+
+ +
+
+ + +
+
+ +
+ """ + end + + @impl true + def update(%{user: user} = assigns, socket) do + # For edit action, ensure we have the current role_id in the changeset + # attrs = + # case assigns.action do + # :edit -> %{"role_id" => user.role_id} + # :new -> %{} + # end + + changeset = Accounts.change_user(user) + + role_options = + Accounts.list_roles() + |> Enum.map(&{String.capitalize(&1.name), &1.id}) + + # Determine if confirmed checkbox should be checked + confirmed_checked = + case assigns.action do + :edit -> !is_nil(user.confirmed_at) + :new -> false + end + + {:ok, + socket + |> assign(assigns) + |> assign(:role_options, role_options) + |> assign(:confirmed_checked, confirmed_checked) + |> assign_form(changeset)} + end + + @impl true + def handle_event("validate", %{"user" => user_params}, socket) do + # Update the confirmed_checked state based on form params + confirmed_checked = Map.get(user_params, "confirmed") == "true" + + # Convert confirmed checkbox to confirmed_at datetime + user_params = maybe_convert_confirmed_field(user_params) + + changeset = + socket.assigns.user + |> Accounts.change_user(user_params) + |> Map.put(:action, :validate) + + {:noreply, + socket + |> assign(:confirmed_checked, confirmed_checked) + |> assign_form(changeset)} + end + + def handle_event("save", %{"user" => user_params}, socket) do + # Convert confirmed checkbox to confirmed_at datetime + user_params = maybe_convert_confirmed_field(user_params) + save_user(socket, socket.assigns.action, user_params) + end + + def handle_event("cancel", _params, socket) do + {:noreply, push_navigate(socket, to: socket.assigns.navigate)} + end + + defp save_user(socket, :edit, user_params) do + case Accounts.update_user(socket.assigns.user, user_params) do + {:ok, user} -> + notify_parent({:saved, user}) + + {:noreply, + socket + |> put_flash(:info, gettext("User updated successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp save_user(socket, :new, user_params) do + case Accounts.create_user(user_params) do + {:ok, user} -> + notify_parent({:saved, user}) + + {:noreply, + socket + |> put_flash(:info, gettext("User created successfully")) + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign_form(socket, changeset)} + end + end + + defp assign_form(socket, %Ecto.Changeset{} = changeset) do + form = to_form(changeset, as: :user) + assign(socket, :form, form) + end + + defp maybe_convert_confirmed_field(user_params) do + case Map.get(user_params, "confirmed") do + "true" -> + user_params + |> Map.delete("confirmed") + |> Map.put("confirmed_at", NaiveDateTime.utc_now()) + + "false" -> + user_params + |> Map.delete("confirmed") + |> Map.put("confirmed_at", nil) + + _ -> + user_params + end + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/lib/claper_web/live/event_live/manage.ex b/lib/claper_web/live/event_live/manage.ex index b9ed708..b6dd0f7 100644 --- a/lib/claper_web/live/event_live/manage.ex +++ b/lib/claper_web/live/event_live/manage.ex @@ -74,7 +74,7 @@ defmodule ClaperWeb.EventLive.Manage do end defp leader?(%{assigns: %{current_user: current_user}} = _socket, event) do - Claper.Events.leaded_by?(current_user.email, event) || event.user.id == current_user.id + Claper.Events.led_by?(current_user.email, event) || event.user.id == current_user.id end defp leader?(_socket, _event), do: false diff --git a/lib/claper_web/live/event_live/presenter.ex b/lib/claper_web/live/event_live/presenter.ex index 93bc610..dbfb96b 100644 --- a/lib/claper_web/live/event_live/presenter.ex +++ b/lib/claper_web/live/event_live/presenter.ex @@ -70,7 +70,7 @@ defmodule ClaperWeb.EventLive.Presenter do end defp leader?(%{assigns: %{current_user: current_user}} = _socket, event) do - Claper.Events.leaded_by?(current_user.email, event) || event.user.id == current_user.id + Claper.Events.led_by?(current_user.email, event) || event.user.id == current_user.id end defp leader?(_socket, _event), do: false diff --git a/lib/claper_web/live/event_live/show.ex b/lib/claper_web/live/event_live/show.ex index 26b5b38..e36bd95 100644 --- a/lib/claper_web/live/event_live/show.ex +++ b/lib/claper_web/live/event_live/show.ex @@ -109,7 +109,7 @@ defmodule ClaperWeb.EventLive.Show do defp check_leader(%{assigns: %{current_user: current_user} = _assigns} = socket, event) when is_map(current_user) do is_leader = - current_user.id == event.user_id || Claper.Events.leaded_by?(current_user.email, event) + current_user.id == event.user_id || Claper.Events.led_by?(current_user.email, event) socket |> assign(:is_leader, is_leader) end diff --git a/lib/claper_web/plugs/admin_required_plug.ex b/lib/claper_web/plugs/admin_required_plug.ex new file mode 100644 index 0000000..c338bdb --- /dev/null +++ b/lib/claper_web/plugs/admin_required_plug.ex @@ -0,0 +1,33 @@ +defmodule ClaperWeb.Plugs.AdminRequiredPlug do + @moduledoc """ + Plug to ensure that the current user has admin role. + + This plug should be used after the authentication plug to ensure + that only admin users can access certain routes. + """ + + import Plug.Conn + import Phoenix.Controller + + use Phoenix.VerifiedRoutes, + endpoint: ClaperWeb.Endpoint, + router: ClaperWeb.Router, + statics: ClaperWeb.static_paths() + + alias Claper.Accounts + + def init(opts), do: opts + + def call(conn, _opts) do + user = conn.assigns[:current_user] + + if user && Accounts.user_has_role?(user, "admin") do + conn + else + conn + |> put_flash(:error, "You must be an admin to access this page.") + |> redirect(to: ~p"/events") + |> halt() + end + end +end diff --git a/lib/claper_web/router.ex b/lib/claper_web/router.ex index 7d06ba0..bd049f3 100644 --- a/lib/claper_web/router.ex +++ b/lib/claper_web/router.ex @@ -14,6 +14,10 @@ defmodule ClaperWeb.Router do plug(ClaperWeb.Plugs.Locale) end + pipeline :admin_required do + plug(ClaperWeb.Plugs.AdminRequiredPlug) + end + pipeline :lti do plug(:accepts, ["html", "json"]) plug(:put_root_layout, html: {ClaperWeb.LayoutView, :root}) @@ -165,4 +169,28 @@ defmodule ClaperWeb.Router do delete("/users/log_out", UserSessionController, :delete) end + + # Admin panel routes - LiveView implementation + live_session :admin, root_layout: {ClaperWeb.LayoutView, :admin} do + scope "/admin", ClaperWeb.AdminLive do + pipe_through [:browser, :require_authenticated_user, :admin_required] + + live "/", DashboardLive, :index + + live "/users", UserLive, :index + live "/users/new", UserLive, :new + live "/users/:id/edit", UserLive, :edit + live "/users/:id", UserLive, :show + + live "/events", EventLive, :index + live "/events/new", EventLive, :new + live "/events/:id/edit", EventLive, :edit + live "/events/:id", EventLive, :show + + live "/oidc_providers", OidcProviderLive, :index + live "/oidc_providers/new", OidcProviderLive, :new + live "/oidc_providers/:id/edit", OidcProviderLive, :edit + live "/oidc_providers/:id", OidcProviderLive, :show + end + end end diff --git a/lib/claper_web/templates/layout/_user_menu.html.heex b/lib/claper_web/templates/layout/_user_menu.html.heex index 7053d56..70c9a71 100644 --- a/lib/claper_web/templates/layout/_user_menu.html.heex +++ b/lib/claper_web/templates/layout/_user_menu.html.heex @@ -5,6 +5,13 @@ > {gettext("My account")} + + {gettext("Admin")} + + + + + + + {csrf_meta_tag()} + <.live_title suffix=" Ā· Claper Admin"> + {assigns[:page_title] || "Admin Dashboard"} + + + + + + + +
+ + +
+ + + + +
+ +
+ <%= if Phoenix.Flash.get(@flash, :info) do %> +
+ + + + + {Phoenix.Flash.get(@flash, :info)} + +
+ <% end %> + + <%= if Phoenix.Flash.get(@flash, :success) do %> +
+ + + + {Phoenix.Flash.get(@flash, :success)} + +
+ <% end %> + + <%= if Phoenix.Flash.get(@flash, :warning) do %> +
+ + + + {Phoenix.Flash.get(@flash, :warning)} + +
+ <% end %> + + <%= if Phoenix.Flash.get(@flash, :error) do %> +
+ + + + {Phoenix.Flash.get(@flash, :error)} + +
+ <% end %> +
+
+ {@inner_content} +
+
+
+ + +
+ + +
+
+ + + + + diff --git a/lib/claper_web/validators/admin_form_validator.ex b/lib/claper_web/validators/admin_form_validator.ex new file mode 100644 index 0000000..d4f407c --- /dev/null +++ b/lib/claper_web/validators/admin_form_validator.ex @@ -0,0 +1,122 @@ +defmodule ClaperWeb.Validators.AdminFormValidator do + @moduledoc """ + Provides validation functions for admin panel forms. + + This module contains helper functions to validate input data + for admin forms before processing, providing consistent validation + across the admin panel. + """ + + @doc """ + Validates OIDC provider data. + + Returns {:ok, validated_params} or {:error, errors} + """ + def validate_oidc_provider(params) do + errors = [] + + errors = + if String.trim(params["name"]) == "" do + [{:name, "Name cannot be blank"} | errors] + else + errors + end + + errors = + if valid_url?(params["issuer"]) do + errors + else + [{:issuer, "Issuer must be a valid URL"} | errors] + end + + errors = + if String.trim(params["client_id"]) == "" do + [{:client_id, "Client ID cannot be blank"} | errors] + else + errors + end + + errors = + if String.trim(params["client_secret"]) == "" do + [{:client_secret, "Client Secret cannot be blank"} | errors] + else + errors + end + + if Enum.empty?(errors) do + {:ok, params} + else + {:error, errors} + end + end + + @doc """ + Validates event data. + + Returns {:ok, validated_params} or {:error, errors} + """ + def validate_event(params) do + errors = [] + + errors = + if String.trim(params["name"]) == "" do + [{:name, "Name cannot be blank"} | errors] + else + errors + end + + errors = + if String.trim(params["code"]) == "" do + [{:code, "Code cannot be blank"} | errors] + else + errors + end + + if Enum.empty?(errors) do + {:ok, params} + else + {:error, errors} + end + end + + @doc """ + Validates user data. + + Returns {:ok, validated_params} or {:error, errors} + """ + def validate_user(params) do + errors = [] + + errors = + if String.trim(params["email"]) == "" do + [{:email, "Email cannot be blank"} | errors] + else + if valid_email?(params["email"]) do + errors + else + [{:email, "Email is not valid"} | errors] + end + end + + if Enum.empty?(errors) do + {:ok, params} + else + {:error, errors} + end + end + + # Private helper functions + + defp valid_url?(nil), do: false + + defp valid_url?(url) do + uri = URI.parse(url) + uri.scheme != nil && uri.host != nil && uri.host =~ "." + end + + defp valid_email?(nil), do: false + + defp valid_email?(email) do + Regex.match?(~r/^[^\s]+@[^\s]+\.[^\s]+$/, email) + end +end diff --git a/lib/claper_web/views/admin/shared_view.ex b/lib/claper_web/views/admin/shared_view.ex new file mode 100644 index 0000000..d656617 --- /dev/null +++ b/lib/claper_web/views/admin/shared_view.ex @@ -0,0 +1,3 @@ +defmodule ClaperWeb.Admin.SharedView do + use ClaperWeb, :view +end diff --git a/lib/claper_web/views/layout_view.ex b/lib/claper_web/views/layout_view.ex index 915811a..ef80821 100644 --- a/lib/claper_web/views/layout_view.ex +++ b/lib/claper_web/views/layout_view.ex @@ -24,6 +24,17 @@ defmodule ClaperWeb.LayoutView do end end + def get_section_path(conn) do + section = Enum.at(conn.path_info, 1) + + case section do + "users" -> ~p"/admin/users" + "events" -> ~p"/admin/events" + "oidc_providers" -> ~p"/admin/oidc_providers" + _ -> ~p"/admin" + end + end + def active_link(%Plug.Conn{} = conn, text, opts) do class = [opts[:class], active_class(conn, opts[:to])] diff --git a/mix.exs b/mix.exs index 884a540..c90adab 100644 --- a/mix.exs +++ b/mix.exs @@ -136,6 +136,7 @@ defmodule Claper.MixProject do "assets.deploy": [ "cmd --cd assets npm install", "tailwind default --minify", + "tailwind admin --minify", "esbuild default --minify", "sass default --no-source-map --style=compressed", "phx.digest" diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 872b256..e731b58 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -8,14 +8,17 @@ msgstr "" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "Einstellungen" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -43,6 +46,9 @@ msgstr "Überprüfen Sie, ob alle Felder korrekt ausgefüllt sind." msgid "Change" msgstr "Ƅndern" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -103,9 +109,12 @@ msgstr "Seien Sie der Erste, der reagiert!" msgid "Join" msgstr "Teilnehmen" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "Dashboard" @@ -175,6 +184,9 @@ msgstr "Zurück zur Startseite" msgid "Created successfully" msgstr "Erfolgreich erstellt" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -198,7 +210,7 @@ msgstr "Erstellen" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -357,7 +369,7 @@ msgid "Add poll to know opinion of your public." msgstr "Fügen Sie eine Umfrage hinzu, um die Meinung Ihres Publikums zu erfahren." #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "Umfrage" @@ -396,7 +408,7 @@ msgstr "Benutzer Email-Adresse" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "Wenn Sie Ihre Datei Ƥndern, werden alle damit verbundenen Interaktionselemente wie Umfragen entfernt." -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "Nachrichten von Teilnehmern werden hier erscheinen." @@ -416,7 +428,7 @@ msgstr "Dadurch werden alle zugehƶrigen Antworten und die Umfrage selbst gelƶs msgid "Ask, comment..." msgstr "Fragen, kommentieren..." -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -453,7 +465,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "Wenn Sie Probleme mit der obigen SchaltflƤche haben, kopieren Sie die folgende URL und fügen Sie sie in Ihren Webbrowser ein" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "Interaktion hinzufügen" @@ -611,6 +623,7 @@ msgstr "Oder verwenden Sie den Code:" msgid "Create account" msgstr "Benutzerkonto erstellen" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -669,23 +682,30 @@ msgid "Edit form" msgstr "Formular bearbeiten" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "Formular" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "Abgeschickte Formulare" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "Formulareinsendungen der Teilnehmer werden hier angezeigt." -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "Name" @@ -716,7 +736,7 @@ msgstr "Abschicken" msgid "Text" msgstr "Text" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "Dies kann nicht rückgƤngig gemacht werden. BestƤtigen?" @@ -888,7 +908,7 @@ msgid "Title" msgstr "Titel" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "Webinhalt" @@ -905,12 +925,12 @@ msgstr "Anpinnen" msgid "Pinned" msgstr "Angepinnt" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format, fuzzy msgid "Pinned messages" msgstr "Angepinnte Nachrichten" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "Angepinnte BeitrƤge werden hier angezeigt." @@ -956,7 +976,7 @@ msgstr "Sie wurden eingeladen, ein Ereignis zu verwalten" msgid "Saved" msgstr "Gespeichert" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "Alle Ihre Veranstaltungen und Dateien werden dauerhaft gelƶscht, sind Sie sicher?" @@ -971,17 +991,17 @@ msgstr "Sind Sie sicher, dass Sie diese Veranstaltung beenden mƶchten? Diese Ak msgid "Attendees room" msgstr "Teilnehmerraum" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "Seien Sie vorsichtig, diese Aktionen sind unwiderruflich" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "Gefahrenzone" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format, fuzzy msgid "Delete account" msgstr "Konto lƶschen" @@ -1011,7 +1031,7 @@ msgstr "Zugriff" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "Animationen in PPT/PPTX-Dateien werden nicht unterstützt, weshalb wir empfehlen, Ihre PrƤsentation in PDF zu exportieren, um eine korrekte Anzeige zu gewƤhrleisten." -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format, fuzzy msgid "Attendees interactions" msgstr "Interaktionen der Teilnehmer" @@ -1038,12 +1058,12 @@ msgstr "Moderatoren" msgid "Finish" msgstr "Abschließen" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "Hier finden Sie alle Interaktionen Ihrer Teilnehmer. Sie kƶnnen Nachrichten, angeheftete Nachrichten und eingereichte Formulare verwalten." -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "Identifizieren Sie Benutzer anhand ihrer einzigartigen Avatare." @@ -1068,12 +1088,12 @@ msgstr "WƤhlen Sie Ihre PrƤsentationsdatei aus. Akzeptierte Formate sind PDF, msgid "Time to launch your presentation!" msgstr "Zeit, Ihre PrƤsentation zu starten!" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "Verwenden Sie die zugehƶrigen Tastaturkürzel, um diese Einstellungen schnell umzuschalten." -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "Sie kƶnnen jede Einstellung für die PrƤsentation (Anzeige auf dem Großbildschirm) und im Raum der Teilnehmer steuern." @@ -1206,7 +1226,7 @@ msgstr "Veranstaltung existiert nicht" msgid "Customize your account" msgstr "Passen Sie Ihr Konto an" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "Sprache" @@ -1226,22 +1246,22 @@ msgstr "Ihre Einstellungen wurden aktualisiert." msgid "Question" msgstr "Frage" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "Fragen" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "Fragen werden hier erscheinen." -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "Nach Datum sortieren" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "Nach Beliebtheit sortieren" @@ -1252,6 +1272,7 @@ msgid "Event manager" msgstr "Veranstaltungsmanager" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "Dokumentation" @@ -1333,12 +1354,12 @@ msgstr "Vorschau schließen" msgid "Create your first interaction." msgstr "Erstellen Sie Ihre erste Interaktion." -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format, fuzzy msgid "Disable" msgstr "Deaktivieren" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "Aktivieren" @@ -1543,12 +1564,12 @@ msgstr "Beenden" msgid "More options" msgstr "Weitere Optionen" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "Nein" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "Ja" @@ -1703,7 +1724,7 @@ msgid "Previous" msgstr "Vorherige" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "Quiz" @@ -1837,6 +1858,10 @@ msgstr "Einzigartige Teilnehmer" msgid "Web Content" msgstr "Webinhalt" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1857,7 +1882,7 @@ msgstr "Mit Ihnen geteilt" msgid "You must login to continue" msgstr "Sie müssen sich anmelden, um fortzufahren" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "muss mindestens eine richtige Antwort haben" @@ -1887,6 +1912,741 @@ msgstr "Anmelden" msgid "Total submissions" msgstr "Gesamtanzahl der Einreichungen" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "Ein eindeutiger Code für Teilnehmer, um diesem Event beizutreten" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "Ein eindeutiger Name für dieses Event" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "Ein eindeutiger Name zur Identifikation dieses OIDC-Anbieters" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "Konto ist bestƤtigt und aktiv" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actions" +msgstr "Optionen" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format, fuzzy +msgid "Active Events" +msgstr "Aktiv" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this event?" +msgstr "Sind Sie sicher, dass Sie dieses Event lƶschen mƶchten?" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this provider?" +msgstr "Sind Sie sicher, dass Sie diesen Anbieter lƶschen mƶchten?" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this user?" +msgstr "Sind Sie sicher, dass Sie diesen Benutzer lƶschen mƶchten?" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "Zugewiesener Benutzer" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format, fuzzy +msgid "Audience Peak" +msgstr "Max. Zuschauer" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "Autorisierungscode" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "Abbrechen" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose file" +msgstr "Datei Ƥndern" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "Client-ID" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "Client-Secret" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "Abgeschlossen" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format, fuzzy +msgid "Confirmed" +msgstr "BestƤtigt" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "BestƤtigt am" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create Event" +msgstr "Veranstaltung erstellen" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "Anbieter erstellen" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create User" +msgstr "Erstellen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created" +msgstr "Erstellen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created At" +msgstr "Erstellen" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format, fuzzy +msgid "Currently running" +msgstr "Aktuelles Quiz" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete event" +msgstr "Lƶschen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "Anbieter lƶschen" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete user" +msgstr "Lƶschen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "OIDC-Anbieter bearbeiten" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "Event bearbeiten" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "Anbieter bearbeiten" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "Ƅndern" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "Diesen OIDC-Anbieter aktivieren" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "Client-ID eingeben" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "Client-Secret eingeben" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "Event-Code eingeben" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "Event-Name eingeben" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format, fuzzy +msgid "Enter password" +msgstr "Aktuelles Passwort" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "Anbieter-Name eingeben" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "Benutzer-E-Mail eingeben" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "Event-Erstellung" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "Event erfolgreich erstellt" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "Event erfolgreich gelƶscht" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "Event-Details" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "Event erfolgreich aktualisiert" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format, fuzzy +msgid "Events" +msgstr "Veranstaltung" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "Events erfolgreich exportiert" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "Abgelaufen am" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "Datei ausgewƤhlt" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format, fuzzy +msgid "Form Post" +msgstr "Formulare" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "Fragment" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "Wie die Autorisierungsantwort zurückgegeben werden soll (Standard ist 'query')" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "Hybrid" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "Implizit" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format, fuzzy +msgid "Inactive" +msgstr "Aktiv" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "Anfangspasswort für den Benutzer" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "Aussteller" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "Aussteller-URL" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "Letzte 30 Tage" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "Zuletzt aktualisiert" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "Neues Event" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "Keine Events gefunden" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "Keine Datei ausgewƤhlt" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "Kein Besitzer" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "Keine Rolle" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "Keine Benutzer gefunden" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "Nicht abgelaufen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "Nicht gestartet" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "OAuth 2.0 Antworttyp (Standard ist 'code')" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Provider" +msgstr "Anbieter" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "OIDC-Anbieter-Details" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Providers" +msgstr "Anbieter" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "OIDC-Bereiche, die angefordert werden sollen (Standard ist 'openid email profile')" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "Besitzer" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "Anbieter erfolgreich erstellt" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider updated successfully" +msgstr "Anbieter erfolgreich aktualisiert" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "Query" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "Aktuelle Events" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "Umleitungs-URI" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "Antwortmodus" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "Antworttyp" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "Rolle" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "Speichern..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "Geplant" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "Bereich" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "Events suchen..." + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "Nach einem Benutzer suchen..." + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "Anbieter suchen..." + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "Benutzer suchen..." + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format, fuzzy +msgid "Select an option" +msgstr "WƤhlen Sie eine Option aus" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "AuswƤhlen..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "Startdatum" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "Gestartet am" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Status" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "Die OIDC-Aussteller-URL (muss mit http:// oder https:// beginnen)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "Die Callback-URL für Ihre Anwendung (muss mit http:// oder https:// beginnen)" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "Der Benutzer, der dieses Event besitzt (erforderlich)" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "Events insgesamt" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "Benutzer insgesamt" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "UnbestƤtigt" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "Event aktualisieren" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "Anbieter aktualisieren" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "Benutzer aktualisieren" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "Benutzerkonto" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "Benutzerwachstum" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "Benutzer erfolgreich erstellt" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "Benutzer erfolgreich gelƶscht" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "Benutzer-Details" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format, fuzzy +msgid "User updated successfully" +msgstr "Benutzer erfolgreich aktualisiert" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "Zugriffsebene des Benutzers" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "E-Mail-Adresse des Benutzers (muss eindeutig sein)" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "Benutzer" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "Benutzer erfolgreich exportiert" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "Event anzeigen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format, fuzzy +msgid "View provider" +msgstr "Bericht ansehen" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "Benutzer anzeigen" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "Wann dieses Event ablƤuft (optional)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "Ob dieser Anbieter derzeit aktiv und für die Authentifizierung verfügbar ist" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format, fuzzy +msgid "attendees" +msgstr "Teilnehmer" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "https://example.com" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "https://yourapp.com/auth/callback" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "openid email profile" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "vs. letzter Monat" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to events" +msgstr "Zurück zu Events" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to providers" +msgstr "Zurück zu Anbietern" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to users" +msgstr "Zurück zu Benutzern" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format, fuzzy +msgid "New provider" +msgstr "Neuer Anbieter" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format, fuzzy +msgid "New user" +msgstr "Neuer Benutzer" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format, fuzzy +msgid "No providers found" +msgstr "Keine Anbieter gefunden" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider deleted successfully" +msgstr "Anbieter erfolgreich gelƶscht" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider details" +msgstr "Anbieter" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "Admin" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "Zurück zur App" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format, fuzzy msgid "Confirm email change" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 1d28e05..df98553 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -10,14 +10,17 @@ msgid "" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -45,6 +48,9 @@ msgstr "" msgid "Change" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -105,9 +111,12 @@ msgstr "" msgid "Join" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "" @@ -177,6 +186,9 @@ msgstr "" msgid "Created successfully" msgstr "" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -200,7 +212,7 @@ msgstr "" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -359,7 +371,7 @@ msgid "Add poll to know opinion of your public." msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "" @@ -398,7 +410,7 @@ msgstr "" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "" @@ -418,7 +430,7 @@ msgstr "" msgid "Ask, comment..." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -455,7 +467,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "" @@ -613,6 +625,7 @@ msgstr "" msgid "Create account" msgstr "" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -671,23 +684,30 @@ msgid "Edit form" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "" -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "" @@ -718,7 +738,7 @@ msgstr "" msgid "Text" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "" @@ -890,7 +910,7 @@ msgid "Title" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "" @@ -907,12 +927,12 @@ msgstr "" msgid "Pinned" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format msgid "Pinned messages" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "" @@ -958,7 +978,7 @@ msgstr "" msgid "Saved" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "" @@ -973,17 +993,17 @@ msgstr "" msgid "Attendees room" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format msgid "Delete account" msgstr "" @@ -1013,7 +1033,7 @@ msgstr "" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format msgid "Attendees interactions" msgstr "" @@ -1040,12 +1060,12 @@ msgstr "" msgid "Finish" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "" @@ -1070,12 +1090,12 @@ msgstr "" msgid "Time to launch your presentation!" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "" @@ -1208,7 +1228,7 @@ msgstr "" msgid "Customize your account" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "" @@ -1228,22 +1248,22 @@ msgstr "" msgid "Question" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "" @@ -1254,6 +1274,7 @@ msgid "Event manager" msgstr "" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "" @@ -1335,12 +1356,12 @@ msgstr "" msgid "Create your first interaction." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format msgid "Disable" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "" @@ -1545,12 +1566,12 @@ msgstr "" msgid "More options" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" @@ -1705,7 +1726,7 @@ msgid "Previous" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "" @@ -1839,6 +1860,10 @@ msgstr "" msgid "Web Content" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1859,7 +1884,7 @@ msgstr "" msgid "You must login to continue" msgstr "" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "" @@ -1889,6 +1914,740 @@ msgstr "" msgid "Total submissions" msgstr "" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Active Events" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this event?" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this provider?" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this user?" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format +msgid "Audience Peak" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format +msgid "Choose file" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format +msgid "Confirmed" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Create Event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Create User" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format +msgid "Created" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format +msgid "Created At" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format +msgid "Currently running" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format +msgid "Delete event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format +msgid "Delete user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter password" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format +msgid "Events" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format +msgid "Form Post" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format +msgid "Inactive" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "OIDC Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format +msgid "OIDC Providers" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format +msgid "Provider updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format +msgid "Select an option" +msgstr "" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "User updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format +msgid "View provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format +msgid "attendees" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format +msgid "Back to events" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "Back to providers" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format +msgid "Back to users" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format +msgid "New provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format +msgid "New user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format +msgid "No providers found" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format +msgid "Provider deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format +msgid "Provider details" +msgstr "" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format msgid "Confirm email change" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 4da17fa..ce15514 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -8,14 +8,17 @@ msgstr "" "Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -43,6 +46,9 @@ msgstr "" msgid "Change" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -103,9 +109,12 @@ msgstr "" msgid "Join" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "" @@ -175,6 +184,9 @@ msgstr "" msgid "Created successfully" msgstr "" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -198,7 +210,7 @@ msgstr "" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -357,7 +369,7 @@ msgid "Add poll to know opinion of your public." msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "" @@ -396,7 +408,7 @@ msgstr "" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "" @@ -416,7 +428,7 @@ msgstr "" msgid "Ask, comment..." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -453,7 +465,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "" @@ -611,6 +623,7 @@ msgstr "" msgid "Create account" msgstr "" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -669,23 +682,30 @@ msgid "Edit form" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "" -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "" @@ -716,7 +736,7 @@ msgstr "" msgid "Text" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "" @@ -888,7 +908,7 @@ msgid "Title" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "" @@ -905,12 +925,12 @@ msgstr "" msgid "Pinned" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format msgid "Pinned messages" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "" @@ -956,7 +976,7 @@ msgstr "" msgid "Saved" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "" @@ -971,17 +991,17 @@ msgstr "" msgid "Attendees room" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format, fuzzy msgid "Delete account" msgstr "" @@ -1011,7 +1031,7 @@ msgstr "" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format, fuzzy msgid "Attendees interactions" msgstr "" @@ -1038,12 +1058,12 @@ msgstr "" msgid "Finish" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "" @@ -1068,12 +1088,12 @@ msgstr "" msgid "Time to launch your presentation!" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "" @@ -1206,7 +1226,7 @@ msgstr "" msgid "Customize your account" msgstr "" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "" @@ -1226,22 +1246,22 @@ msgstr "" msgid "Question" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "" @@ -1252,6 +1272,7 @@ msgid "Event manager" msgstr "" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "" @@ -1333,12 +1354,12 @@ msgstr "" msgid "Create your first interaction." msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format, fuzzy msgid "Disable" msgstr "" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "" @@ -1543,12 +1564,12 @@ msgstr "" msgid "More options" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" @@ -1703,7 +1724,7 @@ msgid "Previous" msgstr "" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "" @@ -1837,6 +1858,10 @@ msgstr "" msgid "Web Content" msgstr "" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1857,7 +1882,7 @@ msgstr "" msgid "You must login to continue" msgstr "" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "" @@ -1887,6 +1912,741 @@ msgstr "" msgid "Total submissions" msgstr "" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actions" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format, fuzzy +msgid "Active Events" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this event?" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this provider?" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this user?" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format, fuzzy +msgid "Audience Peak" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose file" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format, fuzzy +msgid "Confirmed" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create Event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create User" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created At" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format, fuzzy +msgid "Currently running" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format, fuzzy +msgid "Enter password" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format, fuzzy +msgid "Events" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format, fuzzy +msgid "Form Post" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format, fuzzy +msgid "Inactive" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Providers" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "" + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format, fuzzy +msgid "Select an option" +msgstr "" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format, fuzzy +msgid "User updated successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format, fuzzy +msgid "View provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format, fuzzy +msgid "attendees" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to events" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to providers" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to users" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format, fuzzy +msgid "New provider" +msgstr "" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format, fuzzy +msgid "New user" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format, fuzzy +msgid "No providers found" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider deleted successfully" +msgstr "" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider details" +msgstr "" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format, fuzzy msgid "Confirm email change" diff --git a/priv/gettext/es/LC_MESSAGES/default.po b/priv/gettext/es/LC_MESSAGES/default.po index b4f52eb..a909f67 100644 --- a/priv/gettext/es/LC_MESSAGES/default.po +++ b/priv/gettext/es/LC_MESSAGES/default.po @@ -8,14 +8,17 @@ msgstr "" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "Configuración" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -43,6 +46,9 @@ msgstr "Uy, comprueba que todos los campos estĆ©n correctamente rellenados." msgid "Change" msgstr "Cambiar" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -103,9 +109,12 @@ msgstr "Ā”SĆ© el primero en reaccionar!" msgid "Join" msgstr "Unirse" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "Panel" @@ -175,6 +184,9 @@ msgstr "Volver a principal" msgid "Created successfully" msgstr "Creado exitosamente" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -198,7 +210,7 @@ msgstr "Crear" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -357,7 +369,7 @@ msgid "Add poll to know opinion of your public." msgstr "AƱadir una votación para conocer la opinión del pĆŗblico." #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "Votación" @@ -396,7 +408,7 @@ msgstr "Dirección email del usuario" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "Hacer cambios en tu fichero borrarĆ” todos los elementos de interacción asociados, incluyendo votaciones" -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "Los mensajes de los asistentes aparecerĆ”n aquĆ­." @@ -416,7 +428,7 @@ msgstr "Esto borrarĆ” todas las respuestas asociadas y la propia votación, Āæes msgid "Ask, comment..." msgstr "Pregunta, deja comentarios..." -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -453,7 +465,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "Si tienes problemas con el botón superior, copia y pega la URL de debajo en tu navegador" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "AƱadir interacción" @@ -485,7 +497,7 @@ msgstr ", pulsa en el enlace proporcionado para conectar (Ā”comprueba tu bandeja #: lib/claper_web/templates/user_notifier/welcome.html.heex:29 #, elixir-autogen, elixir-format msgid "Export your current presentation to PDF from your favorite slide presentation software (PowerPoint, etc)" -msgstr "" +msgstr "Exporta tu presentación actual a PDF desde tu software de presentación favorito (PowerPoint, etc)" #: lib/claper_web/templates/user_notifier/welcome.html.heex:50 #, elixir-autogen, elixir-format @@ -580,7 +592,7 @@ msgstr "Nivel de participación" #: lib/claper_web/live/event_live/event_card_component.ex:282 #, elixir-autogen, elixir-format msgid "Error when processing the file" -msgstr "" +msgstr "Error al procesar el archivo" #: lib/claper_web/live/event_live/join.html.heex:24 #: lib/claper_web/live/event_live/join.html.heex:47 @@ -611,6 +623,7 @@ msgstr "O usa el código:" msgid "Create account" msgstr "Crear cuenta" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -669,23 +682,30 @@ msgid "Edit form" msgstr "Editar formulario" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "Formulario" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "EnvĆ­os de formulario" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "Los envĆ­os de formulario de los asistentes aparecerĆ”n aquĆ­." -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "Nombre" @@ -716,7 +736,7 @@ msgstr "Enviar" msgid "Text" msgstr "Texto" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "Esto no se puede deshacer, ĀæestĆ”s seguro/a?" @@ -888,7 +908,7 @@ msgid "Title" msgstr "TĆ­tulo" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "Contenido" @@ -905,12 +925,12 @@ msgstr "Anclar" msgid "Pinned" msgstr "Anclado" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format msgid "Pinned messages" msgstr "Mensajes anclados" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "Los mensajes anclados aparecerĆ”n aquĆ­." @@ -956,7 +976,7 @@ msgstr "Has sido invitado/a a gestionar un evento" msgid "Saved" msgstr "Guardado" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "Todos tus eventos y ficheros serĆ”n borrados para siempre, ĀæestĆ”s seguro/a?" @@ -971,17 +991,17 @@ msgstr "ĀæEstĆ”s seguro de que quieres finalizar este evento? Esta acción no pu msgid "Attendees room" msgstr "Sala de asistentes" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "Ten cuidado, estas acciones son irreversibles" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "Zona de peligro" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format, fuzzy msgid "Delete account" msgstr "Borrar cuenta" @@ -1011,7 +1031,7 @@ msgstr "Código de acceso" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "No se soportan las animaciones de los ficheros PPT/PPTX, por lo que te recomendamos exportar tus presentaciones a formato PDF para tener la seguridad de que se mostrarĆ”n correctamente." -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format, fuzzy msgid "Attendees interactions" msgstr "Interacciones de asistentes" @@ -1038,12 +1058,12 @@ msgstr "Colaboradores" msgid "Finish" msgstr "Finalizar" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "AquĆ­ encontrarĆ”s todas las interacciones de tus asistentes. Puedes gestionar mensajes, mensajes anclados y formularios enviados." -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "Identificar usuarios por sus avatares Ćŗnicos." @@ -1068,12 +1088,12 @@ msgstr "Selecciona tu fichero de presentación. Los formatos aceptados son PDF, msgid "Time to launch your presentation!" msgstr "Ā”Es el momento de lanzar tu presentación!" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "Usa los atajos de teclado asociados para conmutar estos ajustes." -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "Puedes controlar cada ajuste para la presentación (lo que se muestra en la pantalla grande) y en la sala de asistentes." @@ -1206,7 +1226,7 @@ msgstr "El evento no existe" msgid "Customize your account" msgstr "Personaliza tu cuenta" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "Idioma" @@ -1226,22 +1246,22 @@ msgstr "Tus preferencias han sido actualizadas." msgid "Question" msgstr "Pregunta" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "Preguntas" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "Las preguntas aparecerĆ”n aquĆ­." -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "Ordenar por fecha" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "Ordenar por popularidad" @@ -1252,6 +1272,7 @@ msgid "Event manager" msgstr "Gestor de evento" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "Documentación" @@ -1333,12 +1354,12 @@ msgstr "Cerrar vista previa" msgid "Create your first interaction." msgstr "Crea tu primera interacción." -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format, fuzzy msgid "Disable" msgstr "Desactivar" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "Activar" @@ -1543,12 +1564,12 @@ msgstr "Finalizar" msgid "More options" msgstr "MĆ”s opciones" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "No" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "SĆ­" @@ -1703,7 +1724,7 @@ msgid "Previous" msgstr "Anterior" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "Cuestionario" @@ -1837,6 +1858,10 @@ msgstr "Asistentes Ćŗnicos" msgid "Web Content" msgstr "Contenido web" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1857,7 +1882,7 @@ msgstr "Compartido contigo" msgid "You must login to continue" msgstr "Debes iniciar sesión para continuar" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "debe tener al menos una respuesta correcta" @@ -1887,6 +1912,741 @@ msgstr "Iniciar sesión" msgid "Total submissions" msgstr "Total de envĆ­os" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "Un código Ćŗnico para que los participantes se unan a este evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "Un nombre Ćŗnico para este evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "Un nombre Ćŗnico para identificar este proveedor OIDC" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "La cuenta estĆ” confirmada y activa" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actions" +msgstr "Opciones" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format, fuzzy +msgid "Active Events" +msgstr "Activo" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this event?" +msgstr "ĀæEstĆ”s seguro de que quieres desvincular esta cuenta?" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this provider?" +msgstr "ĀæEstĆ”s seguro de que quieres desvincular esta cuenta?" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this user?" +msgstr "ĀæEstĆ”s seguro de que quieres desvincular esta cuenta?" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "Usuario Asignado" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format, fuzzy +msgid "Audience Peak" +msgstr "Pico de audiencia" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "Código de Autorización" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "Cancelar" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose file" +msgstr "Cambiar fichero" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "ID de Cliente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "Secreto de Cliente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "Completado" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format, fuzzy +msgid "Confirmed" +msgstr "Confirmar email" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "Confirmado el" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create Event" +msgstr "Crear evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "Crear Proveedor" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create User" +msgstr "Crear" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created" +msgstr "Crear" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created At" +msgstr "Crear" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format, fuzzy +msgid "Currently running" +msgstr "Cuestionario actual" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete event" +msgstr "Borrar" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "Eliminar proveedor" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete user" +msgstr "Borrar" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "Editar Proveedor OIDC" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "Editar evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "Editar proveedor" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "Editar" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "Activar este proveedor OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "Ingresa el ID de cliente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "Ingresa el secreto de cliente" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "Ingresa el código del evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "Ingresa el nombre del evento" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format, fuzzy +msgid "Enter password" +msgstr "ContraseƱa actual" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "Ingresa el nombre del proveedor" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "Ingresa el email del usuario" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "Creación de Eventos" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "Evento creado exitosamente" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "Evento eliminado exitosamente" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "Detalles del evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "Evento actualizado exitosamente" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format, fuzzy +msgid "Events" +msgstr "Evento" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "Eventos exportados exitosamente" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "Expira el" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "Archivo seleccionado" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format, fuzzy +msgid "Form Post" +msgstr "Formularios" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "Fragmento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "Cómo debe devolverse la respuesta de autorización (por defecto 'query')" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "HĆ­brido" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "ImplĆ­cito" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format, fuzzy +msgid "Inactive" +msgstr "Activo" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "ContraseƱa inicial para el usuario" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "Emisor" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "URL del Emisor" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "Últimos 30 dĆ­as" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "Última Actualización" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "Nuevo evento" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "No se encontraron eventos" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "NingĆŗn archivo seleccionado" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "Sin propietario" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "Sin rol" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "No se encontraron usuarios" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "No expirado" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "No iniciado" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "Tipo de respuesta OAuth 2.0 (por defecto 'code')" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Provider" +msgstr "Proveedor" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "Detalles del Proveedor OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Providers" +msgstr "Proveedor" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "Alcances OIDC a solicitar (por defecto 'openid email profile')" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "Propietario" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "Proveedor creado exitosamente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider updated successfully" +msgstr "ContraseƱa actualizada con Ć©xito." + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "Consulta" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "Eventos Recientes" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "URI de Redirección" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "Modo de Respuesta" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "Tipo de Respuesta" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "Rol" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "Guardando..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "Programado" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "Alcance" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "Buscar eventos..." + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "Buscar un usuario..." + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "Buscar proveedores..." + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "Buscar usuarios..." + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format, fuzzy +msgid "Select an option" +msgstr "Seleccione una opción" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "Seleccionar..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "Fecha de Inicio" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "Iniciado el" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Estado" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "La URL del emisor OIDC (debe comenzar con http:// o https://)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "La URL de callback para tu aplicación (debe comenzar con http:// o https://)" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "El usuario propietario de este evento (requerido)" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "Total de Eventos" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "Total de Usuarios" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "Sin confirmar" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "Actualizar Evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "Actualizar Proveedor" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "Actualizar Usuario" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "Cuenta de Usuario" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "Crecimiento de Usuarios" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "Usuario creado exitosamente" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "Usuario eliminado exitosamente" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "Detalles del usuario" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format, fuzzy +msgid "User updated successfully" +msgstr "ContraseƱa actualizada con Ć©xito." + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "Nivel de acceso del usuario" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "Dirección de email del usuario (debe ser Ćŗnica)" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "Usuarios" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "Usuarios exportados exitosamente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "Ver evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format, fuzzy +msgid "View provider" +msgstr "Informe de visualización" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "Ver usuario" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "CuĆ”ndo expira este evento (opcional)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "Si este proveedor estĆ” activo y disponible para autenticación" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format, fuzzy +msgid "attendees" +msgstr "asistente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "https://example.com" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "https://yourapp.com/auth/callback" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "openid email profile" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "vs mes anterior" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to events" +msgstr "Volver a eventos" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to providers" +msgstr "Volver a proveedores" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to users" +msgstr "Volver a usuarios" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format, fuzzy +msgid "New provider" +msgstr "Nuevo proveedor" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format, fuzzy +msgid "New user" +msgstr "Nuevo usuario" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format, fuzzy +msgid "No providers found" +msgstr "No se encontraron proveedores" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider deleted successfully" +msgstr "Proveedor eliminado exitosamente" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider details" +msgstr "Proveedor" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "Admin" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "Volver a la aplicación" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format, fuzzy msgid "Confirm email change" diff --git a/priv/gettext/fr/LC_MESSAGES/default.po b/priv/gettext/fr/LC_MESSAGES/default.po index 57dd75d..ffb508c 100644 --- a/priv/gettext/fr/LC_MESSAGES/default.po +++ b/priv/gettext/fr/LC_MESSAGES/default.po @@ -8,14 +8,17 @@ msgstr "" "Language: fr\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "ParamĆØtres" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -43,6 +46,9 @@ msgstr "Oups, vĆ©rifiez que tous les champs sont remplis correctement." msgid "Change" msgstr "Changer" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -103,9 +109,12 @@ msgstr "Soyez le premier Ć  rĆ©agir !" msgid "Join" msgstr "Rejoindre" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "Tableau de bord" @@ -173,8 +182,11 @@ msgstr "Retourner Ć  l'accueil" #: lib/claper_web/live/event_live/event_form_component.ex:248 #, elixir-autogen, elixir-format msgid "Created successfully" -msgstr "Mis Ć  jour avec succĆØs" +msgstr "CrƩƩ avec succĆØs" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -198,7 +210,7 @@ msgstr "CrĆ©er" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -357,7 +369,7 @@ msgid "Add poll to know opinion of your public." msgstr "Ajoutez un sondage pour connaĆ®tre l'opinion de votre public." #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "Sondage" @@ -397,7 +409,7 @@ msgstr "Adresse email" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "La modification de votre fichier supprimera tous les Ć©lĆ©ments d'interaction comme les sondages associĆ©s." -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "Les messages des participants apparaĆ®tront ici." @@ -417,7 +429,7 @@ msgstr "Cela supprimera toutes les rĆ©ponses associĆ©es et le sondage lui-mĆŖme, msgid "Ask, comment..." msgstr "Questionnez, commentez..." -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -454,7 +466,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "Si vous rencontrez des difficultĆ©s avec le bouton ci-dessus, copiez et collez l'URL ci-dessous dans votre navigateur web" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format, fuzzy msgid "Add interaction" msgstr "Ajouter une interaction" @@ -614,6 +626,7 @@ msgstr "Ou utilisez le code:" msgid "Create account" msgstr "CrĆ©er un compte" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -673,23 +686,30 @@ msgid "Edit form" msgstr "Modifier" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "Formulaire" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "Soumissions de formulaire" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "Les formulaires soumis par les participants apparaĆ®tront ici." -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "Nom" @@ -720,7 +740,7 @@ msgstr "Soumettre" msgid "Text" msgstr "Texte" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "Cela ne peut pas ĆŖtre annulĆ©, confirmez-vous ?" @@ -892,7 +912,7 @@ msgid "Title" msgstr "Titre" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "Contenu web" @@ -909,12 +929,12 @@ msgstr "Ɖpingler" msgid "Pinned" msgstr "ƉpinglĆ©" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format, fuzzy msgid "Pinned messages" msgstr "Messages Ć©pinglĆ©s" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "Les messages Ć©pinglĆ©s apparaĆ®tront ici." @@ -960,7 +980,7 @@ msgstr "Vous avez Ć©tĆ© invitĆ© Ć  gĆ©rer un Ć©vĆ©nement" msgid "Saved" msgstr "EnregistrĆ©" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "Tous vos Ć©vĆ©nements et fichiers seront dĆ©finitivement supprimĆ©s, ĆŖtes-vous sĆ»r ?" @@ -975,17 +995,17 @@ msgstr "Êtes-vous sĆ»r de vouloir terminer cet Ć©vĆ©nement ? Cette action est i msgid "Attendees room" msgstr "Salle des participants" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "Soyez prudent, ces actions sont irrĆ©versibles" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "Zone de danger" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format, fuzzy msgid "Delete account" msgstr "Supprimer le compte" @@ -1015,7 +1035,7 @@ msgstr "Code d'accĆØs" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "Les animations dans les fichiers PPT/PPTX ne sont pas prises en charge, c'est pourquoi nous recommandons d'exporter votre prĆ©sentation en PDF pour garantir un affichage correct." -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format, fuzzy msgid "Attendees interactions" msgstr "Interactions des participants" @@ -1042,12 +1062,12 @@ msgstr "Animateurs" msgid "Finish" msgstr "Terminer" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "Ici, vous trouverez toutes les interactions de vos participants. Vous pouvez gĆ©rer les messages, les messages Ć©pinglĆ©s et les formulaires soumis." -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "Identifiez les utilisateurs par leurs avatars uniques." @@ -1072,12 +1092,12 @@ msgstr "SĆ©lectionnez votre fichier de prĆ©sentation. Les formats acceptĆ©s sont msgid "Time to launch your presentation!" msgstr "Il est temps de lancer votre prĆ©sentation !" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "Utilisez les raccourcis clavier associĆ©s pour basculer rapidement entre ces paramĆØtres." -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "Vous pouvez contrĆ“ler chaque paramĆØtre pour la prĆ©sentation (affichage sur le grand Ć©cran) et dans la salle des participants." @@ -1210,7 +1230,7 @@ msgstr "L'Ć©vĆ©nement n'existe pas" msgid "Customize your account" msgstr "Personnalisez votre compte" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "Langue" @@ -1230,22 +1250,22 @@ msgstr "Vos prĆ©fĆ©rences ont Ć©tĆ© mises Ć  jour." msgid "Question" msgstr "Question" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "Questions" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "Les questions apparaĆ®tront ici." -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "Trier par date" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "Trier par popularitĆ©" @@ -1256,6 +1276,7 @@ msgid "Event manager" msgstr "Gestionnaire d'Ć©vĆ©nement" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "Documentation" @@ -1337,12 +1358,12 @@ msgstr "Fermer l'aperƧu" msgid "Create your first interaction." msgstr "CrĆ©ez votre premiĆØre interaction." -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format, fuzzy msgid "Disable" msgstr "DĆ©sactiver" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "Activer" @@ -1547,12 +1568,12 @@ msgstr "Terminer" msgid "More options" msgstr "Plus d'options" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "Non" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "Oui" @@ -1707,7 +1728,7 @@ msgid "Previous" msgstr "PrĆ©cĆ©dent" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "Quiz" @@ -1841,6 +1862,10 @@ msgstr "Participants uniques" msgid "Web Content" msgstr "Contenu web" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1861,7 +1886,7 @@ msgstr "PartagĆ© avec vous" msgid "You must login to continue" msgstr "Vous devez vous connecter pour continuer" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "doit avoir au moins une bonne rĆ©ponse" @@ -1891,6 +1916,741 @@ msgstr "Se connecter" msgid "Total submissions" msgstr "Total des soumissions" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "Un code unique pour que les participants rejoignent cet Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "Un nom unique pour cet Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "Un nom unique pour identifier ce fournisseur OIDC" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "Le compte est confirmĆ© et actif" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "Actions" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Active Events" +msgstr "ƉvĆ©nements actifs" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this event?" +msgstr "Êtes-vous sĆ»r de vouloir supprimer cet Ć©vĆ©nement ?" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this provider?" +msgstr "Êtes-vous sĆ»r de vouloir supprimer ce fournisseur ?" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete this user?" +msgstr "Êtes-vous sĆ»r de vouloir supprimer cet utilisateur ?" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "Utilisateur assignĆ©" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format +msgid "Audience Peak" +msgstr "Pic d'audience" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "Code d'autorisation" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "Annuler" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format +msgid "Choose file" +msgstr "Choisir un fichier" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "ID Client" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "Secret Client" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "TerminĆ©" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format +msgid "Confirmed" +msgstr "ConfirmĆ©" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "ConfirmĆ© le" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Create Event" +msgstr "CrĆ©er un Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "CrĆ©er un fournisseur" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Create User" +msgstr "CrĆ©er un utilisateur" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format +msgid "Created" +msgstr "CrƩƩ" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format +msgid "Created At" +msgstr "CrƩƩ le" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format +msgid "Currently running" +msgstr "Actuellement en cours" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format +msgid "Delete event" +msgstr "Supprimer l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "Supprimer le fournisseur" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format +msgid "Delete user" +msgstr "Supprimer l'utilisateur" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "Modifier le fournisseur OIDC" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "Modifier l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "Modifier le fournisseur" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit user" +msgstr "Modifier l'utilisateur" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "Activer ce fournisseur OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "Saisir l'ID client" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "Saisir le secret client" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "Saisir le code de l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "Saisir le nom de l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter password" +msgstr "Saisir le mot de passe" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "Saisir le nom du fournisseur" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "Saisir l'email de l'utilisateur" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "CrĆ©ation d'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "ƉvĆ©nement crƩƩ avec succĆØs" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "ƉvĆ©nement supprimĆ© avec succĆØs" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "DĆ©tails de l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "ƉvĆ©nement mis Ć  jour avec succĆØs" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format +msgid "Events" +msgstr "ƉvĆ©nements" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "ƉvĆ©nements exportĆ©s avec succĆØs" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "ExpirĆ© le" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "Fichier sĆ©lectionnĆ©" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format +msgid "Form Post" +msgstr "Form Post" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "Fragment" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "Comment la rĆ©ponse d'autorisation doit ĆŖtre retournĆ©e (par dĆ©faut 'query')" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "Hybride" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "Implicite" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format +msgid "Inactive" +msgstr "Inactif" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "Mot de passe initial pour l'utilisateur" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "Ɖmetteur" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "URL de l'Ć©metteur" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "30 derniers jours" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "DerniĆØre mise Ć  jour" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "Nouvel Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "Aucun Ć©vĆ©nement trouvĆ©" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "Aucun fichier choisi" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "Aucun propriĆ©taire" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "Aucun rĆ“le" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "Aucun utilisateur trouvĆ©" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "Non expirĆ©" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "Non dĆ©marrĆ©" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "Type de rĆ©ponse OAuth 2.0 (par dĆ©faut 'code')" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "OIDC Provider" +msgstr "Fournisseur OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "DĆ©tails du fournisseur OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format +msgid "OIDC Providers" +msgstr "Fournisseurs OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "PortĆ©es OIDC Ć  demander (par dĆ©faut 'openid email profile')" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "PropriĆ©taire" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "Fournisseur crƩƩ avec succĆØs" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format +msgid "Provider updated successfully" +msgstr "Fournisseur mis Ć  jour avec succĆØs" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "Query" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "ƉvĆ©nements rĆ©cents" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "URI de redirection" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "Mode de rĆ©ponse" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "Type de rĆ©ponse" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "RĆ“le" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "Enregistrement..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "PlanifiĆ©" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "PortĆ©e" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "Rechercher des Ć©vĆ©nements..." + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "Rechercher un utilisateur..." + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "Rechercher des fournisseurs..." + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "Rechercher des utilisateurs..." + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format +msgid "Select an option" +msgstr "SĆ©lectionner une option" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "SĆ©lectionner..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "Date de dĆ©but" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "DĆ©marrĆ© le" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Statut" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "L'URL de l'Ć©metteur OIDC (doit commencer par http:// ou https://)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "L'URL de rappel pour votre application (doit commencer par http:// ou https://)" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "L'utilisateur qui possĆØde cet Ć©vĆ©nement (requis)" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "Total des Ć©vĆ©nements" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "Total des utilisateurs" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "Non confirmĆ©" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "Mettre Ć  jour l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "Mettre Ć  jour le fournisseur" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "Mettre Ć  jour l'utilisateur" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "Compte utilisateur" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "Croissance des utilisateurs" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "Utilisateur crƩƩ avec succĆØs" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "Utilisateur supprimĆ© avec succĆØs" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "DĆ©tails de l'utilisateur" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "User updated successfully" +msgstr "Utilisateur mis Ć  jour avec succĆØs" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "Niveau d'accĆØs de l'utilisateur" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "Adresse email de l'utilisateur (doit ĆŖtre unique)" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "Utilisateurs" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "Utilisateurs exportĆ©s avec succĆØs" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "Voir l'Ć©vĆ©nement" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format +msgid "View provider" +msgstr "Voir le fournisseur" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "Voir l'utilisateur" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "Quand cet Ć©vĆ©nement expire (optionnel)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "Si ce fournisseur est actuellement actif et disponible pour l'authentification" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format +msgid "attendees" +msgstr "participants" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "https://exemple.com" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "https://votreapp.com/auth/callback" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "openid email profile" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "vs mois dernier" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format +msgid "Back to events" +msgstr "Retour aux Ć©vĆ©nements" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "Back to providers" +msgstr "Retour aux fournisseurs" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format +msgid "Back to users" +msgstr "Retour aux utilisateurs" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format +msgid "New provider" +msgstr "Nouveau fournisseur" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format +msgid "New user" +msgstr "Nouvel utilisateur" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format +msgid "No providers found" +msgstr "Aucun fournisseur trouvĆ©" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format +msgid "Provider deleted successfully" +msgstr "Fournisseur supprimĆ© avec succĆØs" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format +msgid "Provider details" +msgstr "DĆ©tails du fournisseur" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "Admin" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "Retour Ć  l'app" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format, fuzzy msgid "Confirm email change" diff --git a/priv/gettext/it/LC_MESSAGES/default.po b/priv/gettext/it/LC_MESSAGES/default.po index 7773727..e514b73 100644 --- a/priv/gettext/it/LC_MESSAGES/default.po +++ b/priv/gettext/it/LC_MESSAGES/default.po @@ -9,14 +9,17 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "Impostazioni" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -44,6 +47,9 @@ msgstr "Ops, controlla che tutti i campi siano compilati correttamente." msgid "Change" msgstr "Cambia" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -104,9 +110,12 @@ msgstr "Reagisci per primo !" msgid "Join" msgstr "Partecipa" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "Cruscotto" @@ -176,6 +185,9 @@ msgstr "Ritorna alla home" msgid "Created successfully" msgstr "Creato correttamente" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -199,7 +211,7 @@ msgstr "Crea" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -358,7 +370,7 @@ msgid "Add poll to know opinion of your public." msgstr "Aggiungi un sondaggio per conoscere l'opinione del tuo pubblico." #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "Sondaggio" @@ -397,7 +409,7 @@ msgstr "Indirizzo email utente" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "La modifica del file rimuoverĆ  tutti gli elementi di interazione associati, come i sondaggi." -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "I messaggi delle persone partecipanti appariranno qui." @@ -417,7 +429,7 @@ msgstr "Verranno eliminate tutte le risposte associate e il sondaggio stesso. Se msgid "Ask, comment..." msgstr "Chiedi, commenta..." -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -454,7 +466,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "Se riscontri problemi con il pulsante sopra, copia e incolla l'URL sottostante nel tuo browser web" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "Aggiungi interazione" @@ -612,6 +624,7 @@ msgstr "Oppure usa il codice:" msgid "Create account" msgstr "Crea utenza" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -670,23 +683,30 @@ msgid "Edit form" msgstr "Modifica modulo" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "Modulo" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "Invio di moduli" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "I moduli compilati dai partecipanti appariranno qui." -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "Nome" @@ -717,7 +737,7 @@ msgstr "Invia" msgid "Text" msgstr "Testo" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "Questa operazione non può essere annullata, confermi ?" @@ -889,7 +909,7 @@ msgid "Title" msgstr "Titolo" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "Contenuto web" @@ -906,12 +926,12 @@ msgstr "Appunta" msgid "Pinned" msgstr "Appuntato" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format msgid "Pinned messages" msgstr "Messaggi appuntati" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "I messaggi appuntati verranno visualizzati qui." @@ -957,7 +977,7 @@ msgstr "Sei stato invitato a gestire un evento" msgid "Saved" msgstr "Salvato" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "Tutti i tuoi eventi e file verranno eliminati definitivamente, sei sicuro?" @@ -972,17 +992,17 @@ msgstr "Vuoi davvero terminare questo evento? Questa azione non può essere annu msgid "Attendees room" msgstr "Sala partecipanti" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "Attenzione, queste azioni sono irreversibili" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "Zona pericolosa" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format msgid "Delete account" msgstr "Elimina utenza" @@ -1012,7 +1032,7 @@ msgstr "Codice di accesso" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "Le animazioni nei file PPT/PPTX non sono supportate, motivo per cui consigliamo di esportare la presentazione in PDF per garantirne la corretta visualizzazione." -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format msgid "Attendees interactions" msgstr "Interazioni con i partecipanti" @@ -1039,12 +1059,12 @@ msgstr "Aiutanti" msgid "Finish" msgstr "Terminato" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "Qui troverai tutte le interazioni dei tuoi partecipanti. Puoi gestire messaggi, messaggi appuntati e moduli compilati." -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "Identifica gli utenti tramite i loro avatar unici." @@ -1069,12 +1089,12 @@ msgstr "Seleziona il file della tua presentazione. I formati accettati sono PDF, msgid "Time to launch your presentation!" msgstr "ƈ il momento di lanciare la tua presentazione!" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "Per attivare/disattivare rapidamente queste impostazioni, utilizzare le scorciatoie da tastiera associate." -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "ƈ possibile controllare ogni impostazione della presentazione (visualizzata sul grande schermo) e della sala dei partecipanti." @@ -1207,7 +1227,7 @@ msgstr "L'evento non esiste" msgid "Customize your account" msgstr "Personalizza la tua utenza" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "Lingua" @@ -1227,22 +1247,22 @@ msgstr "Le tue preferenze sono state aggiornate." msgid "Question" msgstr "Domanda" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "Domande" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "Le domande appariranno qui." -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "Ordina per data" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "Ordina per popolaritĆ " @@ -1253,6 +1273,7 @@ msgid "Event manager" msgstr "Responsabile eventi" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "Documentazione" @@ -1334,12 +1355,12 @@ msgstr "Chiudi anteprima" msgid "Create your first interaction." msgstr "Crea la tua prima interazione." -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format msgid "Disable" msgstr "Disabilita" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "Abilita" @@ -1544,12 +1565,12 @@ msgstr "Fine" msgid "More options" msgstr "Altre opzioni" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "No" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "SƬ" @@ -1704,7 +1725,7 @@ msgid "Previous" msgstr "Precedente" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "Quiz" @@ -1838,6 +1859,10 @@ msgstr "Partecipanti unici" msgid "Web Content" msgstr "Contenuto Web" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1858,7 +1883,7 @@ msgstr "Condiviso con te" msgid "You must login to continue" msgstr "Devi accedere per continuare" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "deve avere almeno una risposta corretta" @@ -1888,6 +1913,741 @@ msgstr "Accedi" msgid "Total submissions" msgstr "Risposte totali" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "Un codice unico per far partecipare i partecipanti a questo evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "Un nome unico per questo evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "Un nome unico per identificare questo provider OIDC" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "L'account ĆØ confermato e attivo" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actions" +msgstr "Azioni" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format, fuzzy +msgid "Active Events" +msgstr "Eventi attivi" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this event?" +msgstr "Sei sicuro di voler eliminare questo evento?" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this provider?" +msgstr "Sei sicuro di voler eliminare questo provider?" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this user?" +msgstr "Sei sicuro di voler eliminare questo utente?" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "Utente assegnato" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format, fuzzy +msgid "Audience Peak" +msgstr "Picco di pubblico" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "Codice di autorizzazione" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "Annulla" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose file" +msgstr "Scegli file" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "ID Cliente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "Segreto Cliente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "Completato" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format, fuzzy +msgid "Confirmed" +msgstr "Confermato" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "Confermato il" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create Event" +msgstr "Crea evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "Crea provider" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create User" +msgstr "Crea utente" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created" +msgstr "Creato" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created At" +msgstr "Creato il" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format, fuzzy +msgid "Currently running" +msgstr "Attualmente in corso" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete event" +msgstr "Elimina evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "Elimina provider" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete user" +msgstr "Elimina utente" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "Modifica provider OIDC" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "Modifica evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "Modifica provider" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "Modifica utente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "Abilita questo provider OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "Inserisci ID cliente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "Inserisci segreto cliente" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "Inserisci codice evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "Inserisci nome evento" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format, fuzzy +msgid "Enter password" +msgstr "Inserisci password" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "Inserisci nome provider" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "Inserisci email utente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "Creazione eventi" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "Evento creato con successo" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "Evento eliminato con successo" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "Dettagli evento" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "Evento aggiornato con successo" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format, fuzzy +msgid "Events" +msgstr "Eventi" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "Eventi esportati con successo" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "Scade il" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "File selezionato" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format, fuzzy +msgid "Form Post" +msgstr "Form Post" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "Frammento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "Come dovrebbe essere restituita la risposta di autorizzazione (predefinito: 'query')" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "Ibrido" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "Implicito" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format, fuzzy +msgid "Inactive" +msgstr "Inattivo" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "Password iniziale per l'utente" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "Emittente" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "URL emittente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "Ultimi 30 giorni" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "Ultimo aggiornamento" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "Nuovo evento" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "Nessun evento trovato" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "Nessun file scelto" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "Nessun proprietario" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "Nessun ruolo" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "Nessun utente trovato" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "Non scaduto" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "Non iniziato" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "Tipo di risposta OAuth 2.0 (predefinito: 'code')" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Provider" +msgstr "Provider OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "Dettagli provider OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Providers" +msgstr "Provider OIDC" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "Ambiti OIDC da richiedere (predefinito: 'openid email profile')" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "Proprietario" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "Provider creato con successo" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider updated successfully" +msgstr "Provider aggiornato con successo" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "Query" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "Eventi recenti" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "URI di reindirizzamento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "ModalitĆ  di risposta" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "Tipo di risposta" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "Ruolo" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "Salvataggio in corso..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "Programmato" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "Ambito" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "Cerca eventi..." + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "Cerca un utente..." + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "Cerca provider..." + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "Cerca utenti..." + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format, fuzzy +msgid "Select an option" +msgstr "Seleziona un'opzione" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "Seleziona..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "Data di inizio" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "Iniziato il" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Stato" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "L'URL dell'emittente OIDC (deve iniziare con http:// o https://)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "L'URL di callback per la tua applicazione (deve iniziare con http:// o https://)" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "L'utente proprietario di questo evento (obbligatorio)" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "Eventi totali" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "Utenti totali" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "Non confermato" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "Aggiorna evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "Aggiorna provider" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "Aggiorna utente" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "Account utente" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "Crescita utenti" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "Utente creato con successo" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "Utente eliminato con successo" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "Dettagli utente" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format, fuzzy +msgid "User updated successfully" +msgstr "Utente aggiornato con successo" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "Livello di accesso dell'utente" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "Indirizzo email dell'utente (deve essere unico)" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "Utenti" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "Utenti esportati con successo" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "Visualizza evento" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format, fuzzy +msgid "View provider" +msgstr "Visualizza provider" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "Visualizza utente" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "Quando scade questo evento (opzionale)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "Se questo provider ĆØ attualmente attivo e disponibile per l'autenticazione" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format, fuzzy +msgid "attendees" +msgstr "partecipanti" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "https://example.com" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "https://yourapp.com/auth/callback" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "openid email profile" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "vs mese scorso" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to events" +msgstr "Torna agli eventi" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to providers" +msgstr "Torna ai provider" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to users" +msgstr "Torna agli utenti" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format, fuzzy +msgid "New provider" +msgstr "Nuovo provider" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format, fuzzy +msgid "New user" +msgstr "Nuovo utente" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format, fuzzy +msgid "No providers found" +msgstr "Nessun provider trovato" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider deleted successfully" +msgstr "Provider eliminato con successo" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider details" +msgstr "Dettagli provider" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "Admin" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "Torna all'app" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 msgid "Confirm email change" msgstr "Conferma la modifica dell'email" diff --git a/priv/gettext/nl/LC_MESSAGES/default.po b/priv/gettext/nl/LC_MESSAGES/default.po index 41706c6..99da27b 100644 --- a/priv/gettext/nl/LC_MESSAGES/default.po +++ b/priv/gettext/nl/LC_MESSAGES/default.po @@ -8,14 +8,17 @@ msgstr "" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/claper_web/live/event_live/manage.html.heex:1457 -#: lib/claper_web/live/event_live/manage.html.heex:1463 +#: lib/claper_web/live/event_live/manage.html.heex:1454 +#: lib/claper_web/live/event_live/manage.html.heex:1460 #: lib/claper_web/live/user_settings_live/show.ex:77 #, elixir-autogen, elixir-format msgid "Settings" msgstr "Instellingen" -#: lib/claper_web/live/event_live/manage.ex:841 +#: lib/claper_web/live/admin_live/user_live.html.heex:74 +#: lib/claper_web/live/admin_live/user_live.html.heex:254 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:18 +#: lib/claper_web/live/event_live/manage.ex:825 #: lib/claper_web/live/form_live/form_component.html.heex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:34 #: lib/claper_web/templates/user_registration/new.html.heex:29 @@ -43,6 +46,9 @@ msgstr "Oeps, controleer of alle velden correct zijn ingevuld." msgid "Change" msgstr "Aanpassen" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:83 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:32 #: lib/claper_web/live/event_live/event_form_component.html.heex:309 #, elixir-autogen, elixir-format msgid "Code" @@ -103,9 +109,12 @@ msgstr "Wees de eerste die reageert!" msgid "Join" msgstr "Meedoen" +#: lib/claper_web/live/admin_live/dashboard_live.ex:22 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:3 #: lib/claper_web/live/event_live/index.ex:212 #: lib/claper_web/live/event_live/join.html.heex:31 #: lib/claper_web/live/event_live/join.html.heex:54 +#: lib/claper_web/templates/layout/admin.html.heex:198 #, elixir-autogen, elixir-format msgid "Dashboard" msgstr "Dashboard" @@ -175,6 +184,9 @@ msgstr "Terug" msgid "Created successfully" msgstr "Succesvol aangemaakt" +#: lib/claper_web/live/admin_live/event_live.html.heex:259 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:240 +#: lib/claper_web/live/admin_live/user_live.html.heex:240 #: lib/claper_web/live/event_live/event_card_component.ex:250 #: lib/claper_web/live/event_live/event_card_component.ex:330 #: lib/claper_web/live/event_live/form_component.ex:97 @@ -198,7 +210,7 @@ msgstr "Aanmaken" #: lib/claper_web/live/embed_live/form_component.html.heex:68 #: lib/claper_web/live/event_live/event_card_component.ex:444 #: lib/claper_web/live/event_live/event_form_component.html.heex:67 -#: lib/claper_web/live/event_live/manage.html.heex:1405 +#: lib/claper_web/live/event_live/manage.html.heex:1402 #: lib/claper_web/live/event_live/manageable_post_component.ex:92 #: lib/claper_web/live/event_live/post_component.ex:70 #: lib/claper_web/live/event_live/post_component.ex:142 @@ -357,7 +369,7 @@ msgid "Add poll to know opinion of your public." msgstr "Voeg een peiling toe om achter de mening van het publiek te komen." #: lib/claper_web/live/event_live/manage.html.heex:194 -#: lib/claper_web/live/event_live/manage.html.heex:788 +#: lib/claper_web/live/event_live/manage.html.heex:785 #, elixir-autogen, elixir-format msgid "Poll" msgstr "Peiling" @@ -396,7 +408,7 @@ msgstr "E-mailadres van gebruiker" msgid "Changing your file will remove all interaction elements like polls associated." msgstr "Als je het bestand wijzigt, worden alle bijbehorende interactie-elementen, zoals peilingen, verwijderd." -#: lib/claper_web/live/event_live/manage.html.heex:1221 +#: lib/claper_web/live/event_live/manage.html.heex:1218 #, elixir-autogen, elixir-format msgid "Messages from attendees will appear here." msgstr "Hier verschijnen berichten van deelnemers." @@ -416,7 +428,7 @@ msgstr "Hierdoor worden alle bijbehorende reacties en de peiling verwijderd. Wee msgid "Ask, comment..." msgstr "Vraag, reageer..." -#: lib/claper_web/live/event_live/manage.html.heex:1167 +#: lib/claper_web/live/event_live/manage.html.heex:1164 #: lib/claper_web/live/stat_live/index.html.heex:102 #: lib/claper_web/live/stat_live/index.html.heex:180 #, elixir-autogen, elixir-format @@ -453,7 +465,7 @@ msgid "If you’re having trouble with the button above, copy and paste the URL msgstr "Als je problemen ondervindt met de bovenstaande knop, kopieer en plak dan de onderstaande URL in de webbrowser" #: lib/claper_web/live/event_live/manage.html.heex:758 -#: lib/claper_web/live/event_live/manage.html.heex:1132 +#: lib/claper_web/live/event_live/manage.html.heex:1129 #, elixir-autogen, elixir-format msgid "Add interaction" msgstr "Voeg interactie toe" @@ -611,6 +623,7 @@ msgstr "Of gebruik de code:" msgid "Create account" msgstr "Account aanmaken" +#: lib/claper_web/live/admin_live/user_live/form_component.ex:32 #: lib/claper_web/live/user_settings_live/show.html.heex:161 #: lib/claper_web/templates/user_registration/new.html.heex:37 #: lib/claper_web/templates/user_reset_password/edit.html.heex:34 @@ -669,23 +682,30 @@ msgid "Edit form" msgstr "Formulier bewerken" #: lib/claper_web/live/event_live/manage.html.heex:227 -#: lib/claper_web/live/event_live/manage.html.heex:832 -#: lib/claper_web/live/event_live/manage.html.heex:1417 +#: lib/claper_web/live/event_live/manage.html.heex:829 +#: lib/claper_web/live/event_live/manage.html.heex:1414 #, elixir-autogen, elixir-format msgid "Form" msgstr "Formulier" -#: lib/claper_web/live/event_live/manage.html.heex:1191 +#: lib/claper_web/live/event_live/manage.html.heex:1188 #, elixir-autogen, elixir-format msgid "Form submissions" msgstr "Formulierinzendingen" -#: lib/claper_web/live/event_live/manage.html.heex:1390 +#: lib/claper_web/live/event_live/manage.html.heex:1387 #, elixir-autogen, elixir-format msgid "Form submissions from attendees will appear here." msgstr "Formulierinzendingen van deelnemers worden hier weergegeven." -#: lib/claper_web/live/event_live/manage.ex:840 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:106 +#: lib/claper_web/live/admin_live/event_live.html.heex:74 +#: lib/claper_web/live/admin_live/event_live.html.heex:273 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:19 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:74 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:254 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:24 +#: lib/claper_web/live/event_live/manage.ex:824 #, elixir-autogen, elixir-format msgid "Name" msgstr "Naam" @@ -716,7 +736,7 @@ msgstr "Indienen" msgid "Text" msgstr "Tekst" -#: lib/claper_web/live/event_live/manage.html.heex:1410 +#: lib/claper_web/live/event_live/manage.html.heex:1407 #, elixir-autogen, elixir-format msgid "This cannot be undone, confirm ?" msgstr "Dit kan niet ongedaan worden gemaakt. Bevestigen ?" @@ -888,7 +908,7 @@ msgid "Title" msgstr "Titel" #: lib/claper_web/live/event_live/manage.html.heex:259 -#: lib/claper_web/live/event_live/manage.html.heex:874 +#: lib/claper_web/live/event_live/manage.html.heex:871 #, elixir-autogen, elixir-format msgid "Web content" msgstr "Webinhoud" @@ -905,12 +925,12 @@ msgstr "Vastzetten" msgid "Pinned" msgstr "Vastgezet" -#: lib/claper_web/live/event_live/manage.html.heex:1183 +#: lib/claper_web/live/event_live/manage.html.heex:1180 #, elixir-autogen, elixir-format msgid "Pinned messages" msgstr "Vastgezette berichten" -#: lib/claper_web/live/event_live/manage.html.heex:1344 +#: lib/claper_web/live/event_live/manage.html.heex:1341 #, elixir-autogen, elixir-format msgid "Pinned messages will appear here." msgstr "Hier verschijnen vastgezette berichten." @@ -956,7 +976,7 @@ msgstr "Je bent uitgenodigd om een evenement te beheren" msgid "Saved" msgstr "Opgeslagen" -#: lib/claper_web/live/user_settings_live/show.html.heex:301 +#: lib/claper_web/live/user_settings_live/show.html.heex:293 #, elixir-autogen, elixir-format msgid "All your events and files will be permanently deleted, are you sure?" msgstr "Al jouw evenementen en bestanden worden definitief verwijderd. Weet je dat zeker?" @@ -971,17 +991,17 @@ msgstr "Weet je zeker dat je dit evenement wil stoppen? Deze actie kan niet onge msgid "Attendees room" msgstr "Deelnemers ruimte" -#: lib/claper_web/live/user_settings_live/show.html.heex:291 +#: lib/claper_web/live/user_settings_live/show.html.heex:283 #, elixir-autogen, elixir-format msgid "Be careful, these actions are irreversible" msgstr "Wees voorzichtig, deze acties zijn onomkeerbaar" -#: lib/claper_web/live/user_settings_live/show.html.heex:288 +#: lib/claper_web/live/user_settings_live/show.html.heex:280 #, elixir-autogen, elixir-format msgid "Danger zone" msgstr "Gevarenzone" -#: lib/claper_web/live/user_settings_live/show.html.heex:297 +#: lib/claper_web/live/user_settings_live/show.html.heex:289 #, elixir-autogen, elixir-format, fuzzy msgid "Delete account" msgstr "Account verwijderen" @@ -1011,7 +1031,7 @@ msgstr "Toegangscode" msgid "Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly." msgstr "Animaties in PPT/PPTX-bestanden worden niet ondersteund. Daarom raden wij je aan de presentatie naar PDF te exporteren om er zeker van te zijn dat deze correct wordt weergegeven." -#: lib/claper_web/live/event_live/manage.html.heex:1155 +#: lib/claper_web/live/event_live/manage.html.heex:1152 #, elixir-autogen, elixir-format, fuzzy msgid "Attendees interactions" msgstr "Interacties van deelnemers" @@ -1038,12 +1058,12 @@ msgstr "Facilitators" msgid "Finish" msgstr "Finish" -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms." msgstr "Hier vind je alle interacties van je bezoekers. Je kunt berichten, vastgezette berichten en verzonden formulieren beheren." -#: lib/claper_web/live/event_live/manage.html.heex:1157 +#: lib/claper_web/live/event_live/manage.html.heex:1154 #, elixir-autogen, elixir-format msgid "Identify users by their unique avatars." msgstr "Identificeer gebruikers aan de hand van hun unieke avatars." @@ -1068,12 +1088,12 @@ msgstr "Selecteer het Presentatie. Geaccepteerde formaten zijn PDF, PPT of PPTX. msgid "Time to launch your presentation!" msgstr "Tijd om je presentatie te starten!" -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "Use the associated keyboard shortcuts for quick toggling of these settings." msgstr "Gebruik de bijbehorende sneltoetsen om snel tussen deze instellingen te schakelen." -#: lib/claper_web/live/event_live/manage.html.heex:1459 +#: lib/claper_web/live/event_live/manage.html.heex:1456 #, elixir-autogen, elixir-format msgid "You can control each setting for the presentation (showing on the big screen) and on the attendee's room." msgstr "Je kunt elke instelling voor de presentatie (weergave op het grote scherm) en in de ruimte van de deelnemer beheren." @@ -1206,7 +1226,7 @@ msgstr "Evenement bestaat niet" msgid "Customize your account" msgstr "Pas je account aan" -#: lib/claper_web/live/user_settings_live/show.html.heex:278 +#: lib/claper_web/live/user_settings_live/show.html.heex:270 #, elixir-autogen, elixir-format msgid "Language" msgstr "Taal" @@ -1226,22 +1246,22 @@ msgstr "Je voorkeuren zijn bijgewerkt." msgid "Question" msgstr "Vraag" -#: lib/claper_web/live/event_live/manage.html.heex:1175 +#: lib/claper_web/live/event_live/manage.html.heex:1172 #, elixir-autogen, elixir-format msgid "Questions" msgstr "Vragen" -#: lib/claper_web/live/event_live/manage.html.heex:1262 +#: lib/claper_web/live/event_live/manage.html.heex:1259 #, elixir-autogen, elixir-format msgid "Questions will appear here." msgstr "Vragen zullen hier verschijnen" -#: lib/claper_web/live/event_live/manage.html.heex:1304 +#: lib/claper_web/live/event_live/manage.html.heex:1301 #, elixir-autogen, elixir-format msgid "Sort by date" msgstr "Sorteren op datum" -#: lib/claper_web/live/event_live/manage.html.heex:1283 +#: lib/claper_web/live/event_live/manage.html.heex:1280 #, elixir-autogen, elixir-format msgid "Sort by popularity" msgstr "Sorteer op populariteit" @@ -1252,6 +1272,7 @@ msgid "Event manager" msgstr "Evenementmanager" #: lib/claper_web/templates/layout/_user_menu.html.heex:12 +#: lib/claper_web/templates/layout/admin.html.heex:262 #, elixir-autogen, elixir-format msgid "Documentation" msgstr "Documentatie" @@ -1333,12 +1354,12 @@ msgstr "Voorvertoning sluiten" msgid "Create your first interaction." msgstr "Maak je eerste interactie aan" -#: lib/claper_web/live/event_live/manage.html.heex:1101 +#: lib/claper_web/live/event_live/manage.html.heex:1098 #, elixir-autogen, elixir-format, fuzzy msgid "Disable" msgstr "Uitschakelen" -#: lib/claper_web/live/event_live/manage.html.heex:1118 +#: lib/claper_web/live/event_live/manage.html.heex:1115 #, elixir-autogen, elixir-format msgid "Enable" msgstr "Inschakelen" @@ -1543,12 +1564,12 @@ msgstr "BeĆ«indigen" msgid "More options" msgstr "Meer opties" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "No" msgstr "Nee" -#: lib/claper_web/live/event_live/manage.ex:821 +#: lib/claper_web/live/event_live/manage.ex:805 #, elixir-autogen, elixir-format msgid "Yes" msgstr "Ja" @@ -1703,7 +1724,7 @@ msgid "Previous" msgstr "Vorige" #: lib/claper_web/live/event_live/manage.html.heex:291 -#: lib/claper_web/live/event_live/manage.html.heex:920 +#: lib/claper_web/live/event_live/manage.html.heex:917 #, elixir-autogen, elixir-format msgid "Quiz" msgstr "Quiz" @@ -1837,6 +1858,10 @@ msgstr "Unieke deelnemers" msgid "Web Content" msgstr "Webinhoud" +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:127 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:131 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:286 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:135 #: lib/claper_web/live/event_live/index.html.heex:179 #, elixir-autogen, elixir-format msgid "Active" @@ -1857,7 +1882,7 @@ msgstr "Gedeeld met jou" msgid "You must login to continue" msgstr "Je moet inloggen om door te gaan" -#: lib/claper/quizzes/quiz_question.ex:41 +#: lib/claper/quizzes/quiz_question.ex:51 #, elixir-autogen, elixir-format msgid "must have at least one correct answer" msgstr "moet minimaal een correcte antwoord hebben" @@ -1887,6 +1912,741 @@ msgstr "Inloggen" msgid "Total submissions" msgstr "Totaal aantal inzendingen" +#: lib/claper_web/live/admin_live/event_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "A unique code for participants to join this event" +msgstr "Een unieke code voor deelnemers om aan dit evenement deel te nemen" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:23 +#, elixir-autogen, elixir-format +msgid "A unique name for this event" +msgstr "Een unieke naam voor dit evenement" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:28 +#, elixir-autogen, elixir-format +msgid "A unique name to identify this OIDC provider" +msgstr "Een unieke naam om deze OIDC-aanbieder te identificeren" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Account is confirmed and active" +msgstr "Account is bevestigd en actief" + +#: lib/claper_web/live/admin_live/event_live.html.heex:126 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:108 +#: lib/claper_web/live/admin_live/user_live.html.heex:111 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actions" +msgstr "Opties" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:55 +#, elixir-autogen, elixir-format, fuzzy +msgid "Active Events" +msgstr "Actieve evenementen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:216 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this event?" +msgstr "Weet u zeker dat u dit evenement wilt verwijderen?" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this provider?" +msgstr "Weet u zeker dat u deze aanbieder wilt verwijderen?" + +#: lib/claper_web/live/admin_live/user_live.html.heex:197 +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete this user?" +msgstr "Weet u zeker dat u deze gebruiker wilt verwijderen?" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:68 +#, elixir-autogen, elixir-format +msgid "Assigned User" +msgstr "Toegewezen gebruiker" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:110 +#: lib/claper_web/live/admin_live/event_live.html.heex:119 +#: lib/claper_web/live/admin_live/event_live.html.heex:299 +#, elixir-autogen, elixir-format, fuzzy +msgid "Audience Peak" +msgstr "Publiekspiek" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:103 +#, elixir-autogen, elixir-format +msgid "Authorization Code" +msgstr "Autorisatiecode" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:80 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:147 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:72 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "Annuleren" + +#: lib/claper_web/live/admin_live/form_field_component.ex:112 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose file" +msgstr "Bestand kiezen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:264 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "Client ID" +msgstr "Client-ID" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:62 +#, elixir-autogen, elixir-format +msgid "Client Secret" +msgstr "Client-geheim" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:121 +#, elixir-autogen, elixir-format +msgid "Completed" +msgstr "Voltooid" + +#: lib/claper_web/live/admin_live/user_live.html.heex:134 +#: lib/claper_web/live/admin_live/user_live.html.heex:268 +#, elixir-autogen, elixir-format, fuzzy +msgid "Confirmed" +msgstr "Bevestigd" + +#: lib/claper_web/live/admin_live/user_live.html.heex:291 +#, elixir-autogen, elixir-format +msgid "Confirmed At" +msgstr "Bevestigd op" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create Event" +msgstr "Evenement aanmaken" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Create Provider" +msgstr "Aanbieder aanmaken" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format, fuzzy +msgid "Create User" +msgstr "Gebruiker aanmaken" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:101 +#: lib/claper_web/live/admin_live/user_live.html.heex:104 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created" +msgstr "Aangemaakt" + +#: lib/claper_web/live/admin_live/event_live.html.heex:305 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:296 +#: lib/claper_web/live/admin_live/user_live.html.heex:278 +#, elixir-autogen, elixir-format, fuzzy +msgid "Created At" +msgstr "Aangemaakt op" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:57 +#, elixir-autogen, elixir-format, fuzzy +msgid "Currently running" +msgstr "Momenteel actief" + +#: lib/claper_web/live/admin_live/event_live.html.heex:219 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete event" +msgstr "Evenement verwijderen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:197 +#, elixir-autogen, elixir-format +msgid "Delete provider" +msgstr "Aanbieder verwijderen" + +#: lib/claper_web/live/admin_live/user_live.html.heex:200 +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete user" +msgstr "Gebruiker verwijderen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:339 +#, elixir-autogen, elixir-format +msgid "Edit OIDC Provider" +msgstr "OIDC-aanbieder bewerken" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:176 +#: lib/claper_web/live/admin_live/event_live.ex:41 +#: lib/claper_web/live/admin_live/event_live.html.heex:194 +#: lib/claper_web/live/admin_live/event_live.html.heex:348 +#: lib/claper_web/live/admin_live/event_live.html.heex:358 +#, elixir-autogen, elixir-format +msgid "Edit event" +msgstr "Evenement bewerken" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:48 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:172 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:349 +#, elixir-autogen, elixir-format +msgid "Edit provider" +msgstr "Aanbieder bewerken" + +#: lib/claper_web/live/admin_live/user_live.ex:43 +#: lib/claper_web/live/admin_live/user_live.html.heex:175 +#: lib/claper_web/live/admin_live/user_live.html.heex:329 +#: lib/claper_web/live/admin_live/user_live.html.heex:339 +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "Gebruiker bewerken" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:136 +#, elixir-autogen, elixir-format +msgid "Enable this OIDC provider" +msgstr "Deze OIDC-aanbieder inschakelen" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:51 +#, elixir-autogen, elixir-format +msgid "Enter client ID" +msgstr "Voer client-ID in" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:63 +#, elixir-autogen, elixir-format +msgid "Enter client secret" +msgstr "Voer client-geheim in" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:33 +#, elixir-autogen, elixir-format +msgid "Enter event code" +msgstr "Voer evenementcode in" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:20 +#, elixir-autogen, elixir-format +msgid "Enter event name" +msgstr "Voer evenementnaam in" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:33 +#, elixir-autogen, elixir-format, fuzzy +msgid "Enter password" +msgstr "Voer wachtwoord in" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:25 +#, elixir-autogen, elixir-format +msgid "Enter provider name" +msgstr "Voer aanbieder naam in" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:19 +#, elixir-autogen, elixir-format +msgid "Enter user email" +msgstr "Voer gebruiker e-mail in" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:81 +#, elixir-autogen, elixir-format +msgid "Event Creation" +msgstr "Evenement aanmaken" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:147 +#, elixir-autogen, elixir-format +msgid "Event created successfully" +msgstr "Evenement succesvol aangemaakt" + +#: lib/claper_web/live/admin_live/event_live.ex:58 +#: lib/claper_web/live/admin_live/event_live.ex:110 +#, elixir-autogen, elixir-format +msgid "Event deleted successfully" +msgstr "Evenement succesvol verwijderd" + +#: lib/claper_web/live/admin_live/event_live.ex:47 +#: lib/claper_web/live/admin_live/event_live.html.heex:253 +#, elixir-autogen, elixir-format +msgid "Event details" +msgstr "Evenement details" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:132 +#, elixir-autogen, elixir-format +msgid "Event updated successfully" +msgstr "Evenement succesvol bijgewerkt" + +#: lib/claper_web/live/admin_live/event_live.ex:16 +#: lib/claper_web/live/admin_live/event_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:214 +#, elixir-autogen, elixir-format, fuzzy +msgid "Events" +msgstr "Evenementen" + +#: lib/claper_web/live/admin_live/event_live.ex:75 +#, elixir-autogen, elixir-format +msgid "Events exported successfully" +msgstr "Evenementen succesvol geĆ«xporteerd" + +#: lib/claper_web/live/admin_live/event_live.html.heex:107 +#: lib/claper_web/live/admin_live/event_live.html.heex:291 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:57 +#, elixir-autogen, elixir-format +msgid "Expired At" +msgstr "Verloopt op" + +#: lib/claper_web/live/admin_live/form_field_component.ex:177 +#, elixir-autogen, elixir-format +msgid "File selected" +msgstr "Bestand geselecteerd" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:121 +#, elixir-autogen, elixir-format, fuzzy +msgid "Form Post" +msgstr "Form Post" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:120 +#, elixir-autogen, elixir-format +msgid "Fragment" +msgstr "Fragment" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:125 +#, elixir-autogen, elixir-format +msgid "How the authorization response should be returned (defaults to 'query')" +msgstr "Hoe de autorisatie reactie moet worden geretourneerd (standaard 'query')" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:105 +#, elixir-autogen, elixir-format +msgid "Hybrid" +msgstr "Hybride" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:104 +#, elixir-autogen, elixir-format +msgid "Implicit" +msgstr "Impliciet" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:135 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:290 +#, elixir-autogen, elixir-format, fuzzy +msgid "Inactive" +msgstr "Inactief" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:36 +#, elixir-autogen, elixir-format +msgid "Initial password for the user" +msgstr "Initieel wachtwoord voor de gebruiker" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:83 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:258 +#, elixir-autogen, elixir-format +msgid "Issuer" +msgstr "Uitgever" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:37 +#, elixir-autogen, elixir-format +msgid "Issuer URL" +msgstr "Uitgever URL" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:66 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:82 +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "Laatste 30 dagen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:311 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:302 +#: lib/claper_web/live/admin_live/user_live.html.heex:284 +#, elixir-autogen, elixir-format +msgid "Last Updated" +msgstr "Laatst bijgewerkt" + +#: lib/claper_web/live/admin_live/event_live.ex:35 +#: lib/claper_web/live/admin_live/event_live.html.heex:23 +#: lib/claper_web/live/admin_live/event_live.html.heex:325 +#: lib/claper_web/live/admin_live/event_live.html.heex:335 +#, elixir-autogen, elixir-format +msgid "New event" +msgstr "Nieuw evenement" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:201 +#: lib/claper_web/live/admin_live/event_live.html.heex:134 +#, elixir-autogen, elixir-format +msgid "No events found" +msgstr "Geen evenementen gevonden" + +#: lib/claper_web/live/admin_live/form_field_component.ex:125 +#, elixir-autogen, elixir-format +msgid "No file chosen" +msgstr "Geen bestand gekozen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:147 +#: lib/claper_web/live/admin_live/event_live.html.heex:279 +#, elixir-autogen, elixir-format +msgid "No owner" +msgstr "Geen eigenaar" + +#: lib/claper_web/live/admin_live/user_live.html.heex:129 +#: lib/claper_web/live/admin_live/user_live.html.heex:260 +#, elixir-autogen, elixir-format +msgid "No role" +msgstr "Geen rol" + +#: lib/claper_web/live/admin_live/user_live.html.heex:119 +#, elixir-autogen, elixir-format +msgid "No users found" +msgstr "Geen gebruikers gevonden" + +#: lib/claper_web/live/admin_live/event_live.html.heex:157 +#: lib/claper_web/live/admin_live/event_live.html.heex:295 +#, elixir-autogen, elixir-format +msgid "Not expired" +msgstr "Niet verlopen" + +#: lib/claper_web/live/admin_live/event_live.html.heex:152 +#: lib/claper_web/live/admin_live/event_live.html.heex:287 +#, elixir-autogen, elixir-format +msgid "Not started" +msgstr "Niet gestart" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:108 +#, elixir-autogen, elixir-format +msgid "OAuth 2.0 response type (defaults to 'code')" +msgstr "OAuth 2.0 reactie type (standaard 'code')" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:250 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Provider" +msgstr "OIDC-aanbieder" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:231 +#, elixir-autogen, elixir-format +msgid "OIDC Provider details" +msgstr "OIDC-aanbieder details" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:15 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:246 +#, elixir-autogen, elixir-format, fuzzy +msgid "OIDC Providers" +msgstr "OIDC-aanbieders" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:92 +#, elixir-autogen, elixir-format +msgid "OIDC scopes to request (defaults to 'openid email profile')" +msgstr "OIDC scopes om aan te vragen (standaard 'openid email profile')" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:108 +#: lib/claper_web/live/admin_live/event_live.html.heex:87 +#: lib/claper_web/live/admin_live/event_live.html.heex:277 +#, elixir-autogen, elixir-format +msgid "Owner" +msgstr "Eigenaar" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:209 +#, elixir-autogen, elixir-format +msgid "Provider created successfully" +msgstr "Aanbieder succesvol aangemaakt" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:194 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider updated successfully" +msgstr "Aanbieder succesvol bijgewerkt" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:119 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "Query" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:100 +#, elixir-autogen, elixir-format +msgid "Recent Events" +msgstr "Recente evenementen" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Redirect URI" +msgstr "Redirect URI" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:117 +#, elixir-autogen, elixir-format +msgid "Response Mode" +msgstr "Reactiemodus" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:270 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:101 +#, elixir-autogen, elixir-format +msgid "Response Type" +msgstr "Reactietype" + +#: lib/claper_web/live/admin_live/user_live.html.heex:83 +#: lib/claper_web/live/admin_live/user_live.html.heex:258 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Role" +msgstr "Rol" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:82 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:149 +#: lib/claper_web/live/admin_live/user_live/form_component.ex:74 +#, elixir-autogen, elixir-format +msgid "Saving..." +msgstr "Opslaan..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:124 +#, elixir-autogen, elixir-format +msgid "Scheduled" +msgstr "Gepland" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:276 +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:89 +#, elixir-autogen, elixir-format +msgid "Scope" +msgstr "Scope" + +#: lib/claper_web/live/admin_live/event_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search events..." +msgstr "Zoek evenementen..." + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:70 +#, elixir-autogen, elixir-format +msgid "Search for a user..." +msgstr "Zoek naar een gebruiker..." + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search providers..." +msgstr "Zoek aanbieders..." + +#: lib/claper_web/live/admin_live/user_live.html.heex:55 +#, elixir-autogen, elixir-format +msgid "Search users..." +msgstr "Zoek gebruikers..." + +#: lib/claper_web/live/admin_live/form_field_component.ex:71 +#, elixir-autogen, elixir-format, fuzzy +msgid "Select an option" +msgstr "Selecteer een optie" + +#: lib/claper_web/live/admin_live/searchable_select_component.ex:85 +#, elixir-autogen, elixir-format +msgid "Select..." +msgstr "Selecteer..." + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:109 +#, elixir-autogen, elixir-format +msgid "Start Date" +msgstr "Startdatum" + +#: lib/claper_web/live/admin_live/event_live.html.heex:95 +#: lib/claper_web/live/admin_live/event_live.html.heex:283 +#: lib/claper_web/live/admin_live/event_live/form_component.ex:46 +#, elixir-autogen, elixir-format +msgid "Started At" +msgstr "Gestart op" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:111 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:92 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:282 +#: lib/claper_web/live/admin_live/user_live.html.heex:92 +#: lib/claper_web/live/admin_live/user_live.html.heex:264 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "Status" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:41 +#, elixir-autogen, elixir-format +msgid "The OIDC issuer URL (must start with http:// or https://)" +msgstr "De OIDC-uitgever URL (moet beginnen met http:// of https://)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:79 +#, elixir-autogen, elixir-format +msgid "The callback URL for your application (must start with http:// or https://)" +msgstr "De callback URL voor uw applicatie (moet beginnen met http:// of https://)" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:73 +#, elixir-autogen, elixir-format +msgid "The user who owns this event (required)" +msgstr "De gebruiker die eigenaar is van dit evenement (verplicht)" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:33 +#, elixir-autogen, elixir-format +msgid "Total Events" +msgstr "Totaal evenementen" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Total Users" +msgstr "Totaal gebruikers" + +#: lib/claper_web/live/admin_live/user_live.html.heex:138 +#: lib/claper_web/live/admin_live/user_live.html.heex:272 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "Niet bevestigd" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:83 +#, elixir-autogen, elixir-format +msgid "Update Event" +msgstr "Evenement bijwerken" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:150 +#, elixir-autogen, elixir-format +msgid "Update Provider" +msgstr "Aanbieder bijwerken" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "Update User" +msgstr "Gebruiker bijwerken" + +#: lib/claper_web/live/admin_live/user_live.html.heex:250 +#, elixir-autogen, elixir-format +msgid "User Account" +msgstr "Gebruikersaccount" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:65 +#, elixir-autogen, elixir-format +msgid "User Growth" +msgstr "Gebruikersgroei" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:165 +#, elixir-autogen, elixir-format +msgid "User created successfully" +msgstr "Gebruiker succesvol aangemaakt" + +#: lib/claper_web/live/admin_live/user_live.ex:60 +#: lib/claper_web/live/admin_live/user_live.ex:128 +#, elixir-autogen, elixir-format +msgid "User deleted successfully" +msgstr "Gebruiker succesvol verwijderd" + +#: lib/claper_web/live/admin_live/user_live.ex:49 +#: lib/claper_web/live/admin_live/user_live.html.heex:234 +#, elixir-autogen, elixir-format +msgid "User details" +msgstr "Gebruiker details" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:150 +#, elixir-autogen, elixir-format, fuzzy +msgid "User updated successfully" +msgstr "Gebruiker succesvol bijgewerkt" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:50 +#, elixir-autogen, elixir-format +msgid "User's access level" +msgstr "Toegangsniveau van gebruiker" + +#: lib/claper_web/live/admin_live/user_live/form_component.ex:22 +#, elixir-autogen, elixir-format +msgid "User's email address (must be unique)" +msgstr "E-mailadres van gebruiker (moet uniek zijn)" + +#: lib/claper_web/live/admin_live/user_live.ex:18 +#: lib/claper_web/live/admin_live/user_live.html.heex:6 +#: lib/claper_web/templates/layout/admin.html.heex:230 +#, elixir-autogen, elixir-format +msgid "Users" +msgstr "Gebruikers" + +#: lib/claper_web/live/admin_live/user_live.ex:98 +#, elixir-autogen, elixir-format +msgid "Users exported successfully" +msgstr "Gebruikers succesvol geĆ«xporteerd" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:151 +#: lib/claper_web/live/admin_live/event_live.html.heex:169 +#, elixir-autogen, elixir-format +msgid "View event" +msgstr "Evenement bekijken" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:147 +#, elixir-autogen, elixir-format, fuzzy +msgid "View provider" +msgstr "Aanbieder bekijken" + +#: lib/claper_web/live/admin_live/user_live.html.heex:150 +#, elixir-autogen, elixir-format +msgid "View user" +msgstr "Gebruiker bekijken" + +#: lib/claper_web/live/admin_live/event_live/form_component.ex:60 +#, elixir-autogen, elixir-format +msgid "When this event expires (optional)" +msgstr "Wanneer dit evenement verloopt (optioneel)" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:139 +#, elixir-autogen, elixir-format +msgid "Whether this provider is currently active and available for authentication" +msgstr "Of deze aanbieder momenteel actief is en beschikbaar voor authenticatie" + +#: lib/claper_web/live/admin_live/event_live.html.heex:301 +#, elixir-autogen, elixir-format, fuzzy +msgid "attendees" +msgstr "deelnemers" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:38 +#, elixir-autogen, elixir-format +msgid "https://example.com" +msgstr "https://example.com" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:75 +#, elixir-autogen, elixir-format +msgid "https://yourapp.com/auth/callback" +msgstr "https://yourapp.com/auth/callback" + +#: lib/claper_web/live/admin_live/oidc_provider_live/form_component.ex:90 +#, elixir-autogen, elixir-format +msgid "openid email profile" +msgstr "openid email profile" + +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:28 +#: lib/claper_web/live/admin_live/dashboard_live.html.heex:50 +#, elixir-autogen, elixir-format +msgid "vs last month" +msgstr "vs vorige maand" + +#: lib/claper_web/live/admin_live/event_live.html.heex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to events" +msgstr "Terug naar evenementen" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:234 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to providers" +msgstr "Terug naar aanbieders" + +#: lib/claper_web/live/admin_live/user_live.html.heex:237 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to users" +msgstr "Terug naar gebruikers" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:42 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:23 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:316 +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:326 +#, elixir-autogen, elixir-format, fuzzy +msgid "New provider" +msgstr "Nieuwe aanbieder" + +#: lib/claper_web/live/admin_live/user_live.ex:37 +#: lib/claper_web/live/admin_live/user_live.html.heex:23 +#: lib/claper_web/live/admin_live/user_live.html.heex:306 +#: lib/claper_web/live/admin_live/user_live.html.heex:316 +#, elixir-autogen, elixir-format, fuzzy +msgid "New user" +msgstr "Nieuwe gebruiker" + +#: lib/claper_web/live/admin_live/oidc_provider_live.html.heex:116 +#, elixir-autogen, elixir-format, fuzzy +msgid "No providers found" +msgstr "Geen aanbieders gevonden" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:65 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider deleted successfully" +msgstr "Aanbieder succesvol verwijderd" + +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:36 +#: lib/claper_web/live/admin_live/oidc_provider_live.ex:54 +#, elixir-autogen, elixir-format, fuzzy +msgid "Provider details" +msgstr "Aanbieder details" + +#: lib/claper_web/templates/layout/admin.html.heex:44 +#: lib/claper_web/templates/layout/admin.html.heex:182 +#, elixir-autogen, elixir-format +msgid "Admin" +msgstr "Admin" + +#: lib/claper_web/templates/layout/admin.html.heex:285 +#, elixir-autogen, elixir-format +msgid "Back to app" +msgstr "Terug naar app" + #: lib/claper_web/templates/user_notifier/change.html.heex:17 #, elixir-autogen, elixir-format, fuzzy msgid "Confirm email change" diff --git a/priv/repo/migrations/20250711135508_create_roles.exs b/priv/repo/migrations/20250711135508_create_roles.exs new file mode 100644 index 0000000..c71b1be --- /dev/null +++ b/priv/repo/migrations/20250711135508_create_roles.exs @@ -0,0 +1,14 @@ +defmodule Claper.Repo.Migrations.CreateRoles do + use Ecto.Migration + + def change do + create table(:roles) do + add :name, :string, null: false + add :permissions, :map, default: %{} + + timestamps() + end + + create unique_index(:roles, [:name]) + end +end diff --git a/priv/repo/migrations/20250711135909_add_role_id_to_users.exs b/priv/repo/migrations/20250711135909_add_role_id_to_users.exs new file mode 100644 index 0000000..cdc490f --- /dev/null +++ b/priv/repo/migrations/20250711135909_add_role_id_to_users.exs @@ -0,0 +1,11 @@ +defmodule Claper.Repo.Migrations.AddRoleIdToUsers do + use Ecto.Migration + + def change do + alter table(:users) do + add :role_id, references(:roles, on_delete: :nilify_all), null: true + end + + create index(:users, [:role_id]) + end +end diff --git a/priv/repo/migrations/20250711164053_create_oidc_providers.exs b/priv/repo/migrations/20250711164053_create_oidc_providers.exs new file mode 100644 index 0000000..ac78786 --- /dev/null +++ b/priv/repo/migrations/20250711164053_create_oidc_providers.exs @@ -0,0 +1,22 @@ +defmodule Claper.Repo.Migrations.CreateOidcProviders do + use Ecto.Migration + + def change do + create table(:oidc_providers) do + add :name, :string, null: false + add :issuer, :string, null: false + add :client_id, :string, null: false + add :client_secret, :string, null: false + add :redirect_uri, :string, null: false + add :response_type, :string, default: "code" + add :response_mode, :string + add :scope, :string, default: "openid email profile" + add :active, :boolean, default: true + + timestamps() + end + + create index(:oidc_providers, [:name]) + create unique_index(:oidc_providers, [:issuer]) + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 6f5248c..fb29d07 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -10,6 +10,26 @@ # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. +# Create roles if they don't exist +alias Claper.Accounts.Role +alias Claper.Repo + +# Create admin role if it doesn't exist +if !Repo.get_by(Role, name: "admin") do + %Role{name: "admin", permissions: %{"all" => true}} + |> Repo.insert!() + + IO.puts("Created admin role") +end + +# Create user role if it doesn't exist +if !Repo.get_by(Role, name: "user") do + %Role{name: "user", permissions: %{}} + |> Repo.insert!() + + IO.puts("Created user role") +end + # create a default active lti_1p3 jwk if !Claper.Repo.get_by(Lti13.Jwks.Jwk, id: 1) do %{private_key: private_key} = Lti13.Jwks.Utils.KeyGenerator.generate_key_pair() @@ -22,3 +42,29 @@ if !Claper.Repo.get_by(Lti13.Jwks.Jwk, id: 1) do active: true }) end + +# Create default admin user if no users exist +alias Claper.Accounts +alias Claper.Accounts.User + +if Repo.aggregate(User, :count, :id) == 0 do + admin_role = Repo.get_by(Role, name: "admin") + + if admin_role do + {:ok, admin_user} = + Accounts.register_user(%{ + email: "admin@claper.co", + password: "claper", + confirmed_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + }) + + Accounts.assign_role(admin_user, admin_role) + + IO.puts("Created default admin user:") + IO.puts(" Email: admin@claper.co") + IO.puts(" Password: claper") + IO.puts(" IMPORTANT: Please change this password after first login!") + else + IO.puts("Warning: Admin role not found, skipping default admin user creation") + end +end diff --git a/priv/static/fonts/Montserrat/Montserrat-Italic-VariableFont_wght.ttf b/priv/static/fonts/Montserrat/Montserrat-Italic-VariableFont_wght.ttf new file mode 100644 index 0000000..9f89c9d Binary files /dev/null and b/priv/static/fonts/Montserrat/Montserrat-Italic-VariableFont_wght.ttf differ diff --git a/priv/static/fonts/Montserrat/Montserrat-VariableFont_wght.ttf b/priv/static/fonts/Montserrat/Montserrat-VariableFont_wght.ttf new file mode 100644 index 0000000..df7379c Binary files /dev/null and b/priv/static/fonts/Montserrat/Montserrat-VariableFont_wght.ttf differ diff --git a/test/claper/accounts/role_test.exs b/test/claper/accounts/role_test.exs new file mode 100644 index 0000000..ccda407 --- /dev/null +++ b/test/claper/accounts/role_test.exs @@ -0,0 +1,133 @@ +defmodule Claper.Accounts.RoleTest do + use Claper.DataCase + + alias Claper.Accounts + alias Claper.Accounts.{User, Role} + alias Claper.Repo + + describe "roles" do + setup do + # Ensure admin and user roles exist + {:ok, _admin_role} = Accounts.create_role(%{name: "admin"}) + {:ok, _user_role} = Accounts.create_role(%{name: "user"}) + :ok + end + + test "list_roles/0 returns all roles" do + roles = Accounts.list_roles() + assert length(roles) == 2 + assert Enum.any?(roles, fn r -> r.name == "admin" end) + assert Enum.any?(roles, fn r -> r.name == "user" end) + end + + test "get_role!/1 returns the role with given id" do + role = Repo.get_by(Role, name: "admin") + assert Accounts.get_role!(role.id).name == "admin" + end + + test "get_role_by_name/1 returns the role with given name" do + assert Accounts.get_role_by_name("admin").name == "admin" + assert Accounts.get_role_by_name("user").name == "user" + assert Accounts.get_role_by_name("nonexistent") == nil + end + + test "create_role/1 with valid data creates a role" do + assert {:ok, %Role{} = role} = Accounts.create_role(%{name: "moderator"}) + assert role.name == "moderator" + end + + test "create_role/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Accounts.create_role(%{name: nil}) + end + end + + describe "user role management" do + setup do + # Ensure admin and user roles exist + {:ok, admin_role} = Accounts.create_role(%{name: "admin"}) + {:ok, user_role} = Accounts.create_role(%{name: "user"}) + + # Create a test user + {:ok, user} = Accounts.create_user(%{email: "test@example.com", password: "Password123!"}) + + %{user: user, admin_role: admin_role, user_role: user_role} + end + + test "assign_role/2 assigns a role to a user", %{user: user, admin_role: admin_role} do + assert {:ok, updated_user} = Accounts.assign_role(user, admin_role) + assert updated_user.role_id == admin_role.id + + # Verify through a fresh database query + fresh_user = Repo.get(User, user.id) |> Repo.preload(:role) + assert fresh_user.role.name == "admin" + end + + test "get_user_role/1 returns the role of a user", %{user: user, user_role: user_role} do + # Assign a role first + {:ok, user} = Accounts.assign_role(user, user_role) + + # Test getting the role + role = Accounts.get_user_role(user) + assert role.id == user_role.id + assert role.name == "user" + end + + test "list_users_by_role/1 returns users with a specific role", %{ + user: user, + admin_role: admin_role + } do + # Create another user with a different role + {:ok, user2} = + Accounts.create_user(%{email: "another@example.com", password: "Password123!"}) + + {:ok, _} = Accounts.assign_role(user, admin_role) + + # Get users with admin role + admin_users = Accounts.list_users_by_role("admin") + assert length(admin_users) == 1 + assert hd(admin_users).id == user.id + + # Verify user2 is not in the list + assert user2.id not in Enum.map(admin_users, & &1.id) + end + + test "user_has_role?/2 checks if a user has a specific role", %{ + user: user, + admin_role: admin_role + } do + # Initially user has no role + refute Accounts.user_has_role?(user, "admin") + + # Assign admin role + {:ok, user} = Accounts.assign_role(user, admin_role) + + # Now user should have admin role + assert Accounts.user_has_role?(user, "admin") + refute Accounts.user_has_role?(user, "user") + end + + test "promote_to_admin/1 promotes a user to admin", %{user: user} do + # Initially user should not be admin + refute Accounts.user_has_role?(user, "admin") + + # Promote to admin + {:ok, user} = Accounts.promote_to_admin(user) + + # Verify promotion + assert Accounts.user_has_role?(user, "admin") + end + + test "demote_from_admin/1 demotes a user from admin", %{user: user} do + # First promote to admin + {:ok, user} = Accounts.promote_to_admin(user) + assert Accounts.user_has_role?(user, "admin") + + # Then demote + {:ok, user} = Accounts.demote_from_admin(user) + + # Verify demotion + refute Accounts.user_has_role?(user, "admin") + assert Accounts.user_has_role?(user, "user") + end + end +end diff --git a/test/claper/embeds_test.exs b/test/claper/embeds_test.exs index d6fde8c..c026bf7 100644 --- a/test/claper/embeds_test.exs +++ b/test/claper/embeds_test.exs @@ -75,15 +75,17 @@ defmodule Claper.EmbedsTest do "https://youtu.be/dQw4w9WgXcQ?si=g1A6ZegIXzcrisSw" end - test "create_embed/1 with invalid data creates a youtube embed" do + test "create_embed/1 with invalid youtube URL returns error" do presentation_file = presentation_file_fixture() attrs = %{ title: "some title", - content: "https://youtube.com/dQw4w9WgXcQ?si=g1A6ZegIXzcrisSw", + # Wrong provider URL for youtube + content: "https://vimeo.com/123456", provider: "youtube", presentation_file_id: presentation_file.id, - position: 0 + position: 0, + attendee_visibility: false } assert {:error, %Ecto.Changeset{}} = Embeds.create_embed(attrs) diff --git a/test/claper/events_test.exs b/test/claper/events_test.exs index a8c19f4..4847494 100644 --- a/test/claper/events_test.exs +++ b/test/claper/events_test.exs @@ -2,6 +2,7 @@ defmodule Claper.EventsTest do use Claper.DataCase alias Claper.Events + alias Claper.Events.{Event, ActivityLeader} import Claper.{ EventsFixtures, @@ -12,70 +13,417 @@ defmodule Claper.EventsTest do EmbedsFixtures } - describe "events" do - alias Claper.Events.Event + setup_all do + sandbox_owner_pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Claper.Repo, shared: true) + on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(sandbox_owner_pid) end) - @invalid_attrs %{name: nil, code: nil} + alice = user_fixture(%{email: "alice@example.com"}) + bob = user_fixture(%{email: "bob@example.com"}) + carol = user_fixture(%{email: "carol@example.com"}) - test "list_events/1 returns all events of a user" do - event = event_fixture() - assert Events.list_events(event.user_id) == [event] + now = NaiveDateTime.utc_now() + + alice_active_events = + for _ <- 1..12 do + event_fixture(%{user: alice}) + end + + alice_expired_events = [] + + bob_active_events = [] + + bob_expired_events = + for i <- 1..12 do + event_fixture(%{user: bob, expired_at: NaiveDateTime.add(now, i - 1, :hour)}) + end + + carol_active_events = + for _ <- 1..6 do + event = + event_fixture(%{user: carol}) + + activity_leader_fixture(%{event: event, user: alice}) + event + end + + carol_expired_events = + for i <- 1..6 do + event = + event_fixture(%{user: carol, expired_at: NaiveDateTime.add(now, i - 1, :hour)}) + + activity_leader_fixture(%{event: event, user: bob}) + event + end + + [ + sandbox_owner_pid: sandbox_owner_pid, + alice: alice, + alice_active_events: alice_active_events, + alice_expired_events: alice_expired_events, + alice_events: alice_active_events ++ alice_expired_events, + bob: bob, + bob_active_events: bob_active_events, + bob_expired_events: bob_expired_events, + bob_events: bob_active_events ++ bob_expired_events, + carol: carol, + carol_active_events: carol_active_events, + carol_expired_events: carol_expired_events, + carol_events: carol_active_events ++ carol_expired_events + ] + end + + describe "listing events" do + test "list_events/2 lists all events of a user but not others", context do + assert Events.list_events(context.alice.id) == list(context.alice_events) + assert Events.list_events(context.bob.id) == list(context.bob_events) + assert Events.list_events(context.carol.id) == list(context.carol_events) end - test "list_events/1 doesn't returns events of other users" do - event = event_fixture() + test "paginate_events/3 paginates all events of a user but not others", context do + assert Events.paginate_events(context.alice.id) == + paginate(context.alice_events) - event2 = event_fixture() + params = %{"page" => 2} - assert Events.list_events(event.user_id) == [event] - assert Events.list_events(event.user_id) != [event2] + assert Events.paginate_events(context.alice.id, params) == + paginate(context.alice_events, params) + + assert Events.paginate_events(context.bob.id) == + paginate(context.bob_events) + + params = %{"page" => 2, "page_size" => 12} + + assert Events.paginate_events(context.bob.id, params) == + paginate(context.bob_events, params) end - test "get_event!/2 returns the event with given id" do + test "list_not_expired_events/2 lists all active events for a user but not others", + context do + assert Events.list_not_expired_events(context.alice.id) == + list(context.alice_active_events) + + assert Events.list_not_expired_events(context.bob.id) == + list(context.bob_active_events) + + assert Events.list_not_expired_events(context.carol.id) == + list(context.carol_active_events) + end + + test "paginate_not_expired_events/3 paginates all active events for a user but not others", + context do + assert Events.paginate_not_expired_events(context.alice.id) == + paginate(context.alice_active_events) + + assert Events.paginate_not_expired_events(context.bob.id) == + paginate(context.bob_active_events) + + params = %{"page" => 2, "page_size" => 10} + + assert Events.paginate_not_expired_events(context.carol.id, params) == + paginate(context.carol_active_events, params) + end + + test "list_expired_events/2 lists all expired events for a user but not others", context do + assert Events.list_expired_events(context.alice.id) == + Enum.reverse(context.alice_expired_events) + + assert Events.list_expired_events(context.bob.id) == + Enum.reverse(context.bob_expired_events) + + assert Events.list_expired_events(context.carol.id) == + Enum.reverse(context.carol_expired_events) + end + + test "paginate_expired_events/3 lists all expired events for a user but not others", + context do + assert Events.paginate_expired_events(context.alice.id) == + paginate(context.alice_expired_events) + + assert Events.paginate_expired_events(context.bob.id) == + paginate(context.bob_expired_events) + + params = %{"page" => 2, "page_size" => 10} + + assert Events.paginate_expired_events(context.bob.id, params) == + paginate(context.bob_expired_events, params) + + assert Events.paginate_expired_events(context.carol.id) == + paginate(context.carol_expired_events) + end + + test "list_managed_events_by/2 lists all managed events by user but not others", context do + assert Events.list_managed_events_by(context.alice.email) == + list(context.carol_active_events) + + assert Events.list_managed_events_by(context.bob.email) == + list(context.carol_expired_events) + + assert Events.list_managed_events_by(context.carol.email) == [] + end + + test "paginate_managed_events_by/3 paginates all managed events by user but not others", + context do + assert Events.paginate_managed_events_by(context.alice.email) == + paginate(context.carol_active_events) + + assert Events.paginate_managed_events_by(context.bob.email) == + paginate(context.carol_expired_events) + + assert Events.paginate_managed_events_by(context.carol.email) == {[], 0, 0} + end + end + + describe "counting events" do + test "count_managed_events_by/1 counts all managed events by user", context do + assert Events.count_managed_events_by(context.alice.email) == + Enum.count(context.carol_active_events) + + assert Events.count_managed_events_by(context.bob.email) == + Enum.count(context.carol_expired_events) + + assert Events.count_managed_events_by(context.carol.email) == 0 + end + + test "count_expired_events/1 counts all expired events for user", context do + assert Events.count_expired_events(context.alice.id) == + Enum.count(context.alice_expired_events) + + assert Events.count_expired_events(context.bob.id) == Enum.count(context.bob_expired_events) + + assert Events.count_expired_events(context.carol.id) == + Enum.count(context.carol_expired_events) + end + + test "count_events_month/1 counts all events for user created in the last 30 days", + context do + assert Events.count_events_month(context.alice.id) == + Enum.count(context.alice_active_events) + Enum.count(context.alice_expired_events) + + assert Events.count_events_month(context.bob.id) == + Enum.count(context.bob_active_events) + Enum.count(context.bob_expired_events) + + assert Events.count_events_month(context.carol.id) == + Enum.count(context.carol_active_events) + Enum.count(context.carol_expired_events) + end + end + + describe "getting events" do + test "get_event!/2 gets event by serial ID and UUID" do event = event_fixture() + assert Events.get_event!(event.id) == event + assert Events.get_event!(to_string(event.id)) == event assert Events.get_event!(event.uuid) == event end - test "get_user_event!/3 with invalid user raises exception" do - event = event_fixture() - event2 = event_fixture() + test "get_managed_event!/3 gets event managed by owner and leader, raises if neither", + context do + event = Enum.at(context.carol_active_events, 0) + assert Events.get_managed_event!(context.alice, event.uuid) == event + assert Events.get_managed_event!(context.carol, event.uuid) == event assert_raise Ecto.NoResultsError, fn -> - Events.get_user_event!(event.user_id, event2.uuid) == event + Events.get_managed_event!(context.bob, event.uuid) end end + test "get_user_event!/3 gets event by owner, raises if not", context do + event = Enum.at(context.alice_active_events, 0) + assert Events.get_user_event!(context.alice.id, event.uuid) == event + + assert_raise Ecto.NoResultsError, fn -> + Events.get_user_event!(context.bob.id, event.uuid) + end + end + + test "get_event_with_code!/2 gets non-expired event by code, raises if not found", context do + active_event = Enum.at(context.carol_active_events, 0) + expired_event = Enum.at(context.carol_expired_events, 0) + + assert Events.get_event_with_code!(active_event.code) == active_event + + assert_raise Ecto.NoResultsError, fn -> + Events.get_event_with_code!(expired_event.code) + end + + assert_raise Ecto.NoResultsError, fn -> + Events.get_event_with_code!("ABC123") + end + end + + test "get_event_with_code/2 gets non-expired event by code, returns nil if not found", + context do + active_event = Enum.at(context.carol_active_events, 0) + expired_event = Enum.at(context.carol_expired_events, 0) + + assert Events.get_event_with_code(active_event.code) == active_event + assert Events.get_event_with_code(expired_event.code) == nil + assert Events.get_event_with_code("ABC123") == nil + end + end + + describe "writing events" do test "create_event/1 with valid data creates a event" do user = user_fixture() - valid_attrs = %{ + attrs = %{ name: "some name", code: "12345", user_id: user.id, - started_at: NaiveDateTime.utc_now(), - expired_at: NaiveDateTime.add(NaiveDateTime.utc_now(), 7200, :second) + started_at: + NaiveDateTime.utc_now(:second) + |> NaiveDateTime.truncate(:second), + expired_at: + NaiveDateTime.add(NaiveDateTime.utc_now(), 2, :hour) + |> NaiveDateTime.truncate(:second) } - assert {:ok, %Event{} = event} = Events.create_event(valid_attrs) - assert event.name == "some name" - end + assert {:ok, %Event{} = event} = Events.create_event(attrs) - test "create_event/1 with too short code returns error changeset" do - user = user_fixture() - - valid_attrs = %{ - name: "some name", - code: "code", - user_id: user.id, - started_at: NaiveDateTime.utc_now(), - expired_at: NaiveDateTime.add(NaiveDateTime.utc_now(), 7200, :second) - } - - assert {:error, %Ecto.Changeset{}} = Events.create_event(valid_attrs) + assert event.name == attrs.name + assert event.code == attrs.code + assert event.user_id == attrs.user_id + assert event.started_at == attrs.started_at + assert attrs.expired_at == event.expired_at end test "create_event/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Events.create_event(@invalid_attrs) + user = user_fixture() + + attrs = %{ + name: "some name", + code: "12345", + user_id: user.id, + started_at: + NaiveDateTime.utc_now(:second) + |> NaiveDateTime.truncate(:second), + expired_at: + NaiveDateTime.add(NaiveDateTime.utc_now(), 2, :hour) + |> NaiveDateTime.truncate(:second) + } + + too_short_code = "tiny" + + assert {:error, %Ecto.Changeset{}} = Events.create_event(Map.delete(attrs, :name)) + assert {:error, %Ecto.Changeset{}} = Events.create_event(Map.delete(attrs, :code)) + assert {:error, %Ecto.Changeset{}} = Events.create_event(Map.delete(attrs, :user_id)) + assert {:error, %Ecto.Changeset{}} = Events.create_event(Map.delete(attrs, :started_at)) + + assert {:error, %Ecto.Changeset{}} = + Events.create_event(Map.merge(attrs, %{code: too_short_code})) + end + + test "duplicate_event/2 duplicates an event without presentation association" do + original = event_fixture() + {:ok, duplicate} = Events.duplicate_event(original.user_id, original.uuid) + + assert duplicate.name == "#{original.name} (Copy)" + assert duplicate.id != original.id + assert duplicate.code != original.code + end + + test "duplicate_event/2 duplicates an event with presentation associations" do + original = event_fixture() + presentation_file = presentation_file_fixture(%{event: original}) + presentation_state = presentation_state_fixture(%{presentation_file: presentation_file}) + + poll = + poll_fixture(%{ + presentation_file_id: presentation_file.id, + poll_opts: [ + %{content: "some option 1", vote_count: 1}, + %{content: "some option 2", vote_count: 2} + ] + }) + + poll_fixture(%{presentation_file_id: presentation_file.id}) + + form = form_fixture(%{presentation_file_id: presentation_file.id}) + form_fixture(%{presentation_file_id: presentation_file.id}) + embed = embed_fixture(%{presentation_file_id: presentation_file.id}) + embed_fixture(%{presentation_file_id: presentation_file.id}) + + duplicate = + Events.duplicate_event(original.user_id, original.uuid) + |> then(fn {:ok, duplicate} -> + duplicate + |> Repo.preload( + presentation_file: [:embeds, :forms, :presentation_state, polls: [:poll_opts]] + ) + end) + + # Event + assert duplicate.id != original.id + assert duplicate.uuid != original.uuid + assert duplicate.code != original.code + assert duplicate.name == "#{original.name} (Copy)" + assert duplicate.user_id == original.user_id + + # Presentation file + assert duplicate.presentation_file.id != presentation_file.id + assert duplicate.presentation_file.hash == presentation_file.hash + assert duplicate.presentation_file.length == presentation_file.length + assert duplicate.presentation_file.event_id != presentation_file.event_id + assert duplicate.presentation_file.event_id == duplicate.id + + # Presentation state + duplicate_state = duplicate.presentation_file.presentation_state + assert duplicate_state.id != presentation_state.id + assert duplicate_state.presentation_file_id != presentation_state.presentation_file_id + assert duplicate_state.presentation_file_id == duplicate.presentation_file.id + + # Polls + [duplicate_poll, _] = duplicate.presentation_file.polls + assert duplicate_poll.id != poll.id + assert duplicate_poll.presentation_file_id != poll.presentation_file_id + assert duplicate_poll.presentation_file_id == duplicate.presentation_file.id + assert duplicate_poll.title == poll.title + assert duplicate_poll.position == poll.position + + # Poll options + [o1, o2] = poll.poll_opts + [do1, do2] = duplicate_poll.poll_opts + + assert do1.id != o1.id + assert do1.poll_id != o1.poll_id + assert do1.poll_id == duplicate_poll.id + assert do1.content == o1.content + assert do1.vote_count == 0 + + assert do2.id != o2.id + assert do2.poll_id != o2.poll_id + assert do2.poll_id == duplicate_poll.id + assert do2.content == o2.content + assert do2.vote_count == 0 + + # Forms + [duplicate_form, _] = duplicate.presentation_file.forms + assert duplicate_form.id != form.id + assert duplicate_form.presentation_file_id != form.presentation_file_id + assert duplicate_form.presentation_file_id == duplicate.presentation_file.id + assert duplicate_form.enabled == form.enabled + assert duplicate_form.fields == form.fields + assert duplicate_form.position == form.position + assert duplicate_form.title == form.title + + # Embeds + [duplicate_embed, _] = duplicate.presentation_file.embeds + assert duplicate_embed.id != embed.id + assert duplicate_embed.presentation_file_id != embed.presentation_file_id + assert duplicate_embed.presentation_file_id == duplicate.presentation_file.id + assert(duplicate_embed.attendee_visibility == embed.attendee_visibility) + assert duplicate_embed.content == embed.content + assert duplicate_embed.enabled == embed.enabled + assert duplicate_embed.position == embed.position + assert duplicate_embed.provider == embed.provider + assert duplicate_embed.title == embed.title + end + + test "duplicate_event/2 raises when an invalid user-event is supplied", context do + original = Enum.at(context.alice_active_events, 0) + + assert_raise Ecto.NoResultsError, fn -> + Events.duplicate_event(context.bob.id, original.uuid) + end end test "update_event/2 with valid data updates the event" do @@ -88,10 +436,102 @@ defmodule Claper.EventsTest do test "update_event/2 with invalid data returns error changeset" do event = event_fixture() - assert {:error, %Ecto.Changeset{}} = Events.update_event(event, @invalid_attrs) + + assert {:error, %Ecto.Changeset{}} = Events.update_event(event, %{name: nil}) + assert {:error, %Ecto.Changeset{}} = Events.update_event(event, %{code: nil}) + assert {:error, %Ecto.Changeset{}} = Events.update_event(event, %{code: "tiny"}) + assert {:error, %Ecto.Changeset{}} = Events.update_event(event, %{user_id: nil}) + assert {:error, %Ecto.Changeset{}} = Events.update_event(event, %{started_at: nil}) + assert event == Events.get_event!(event.uuid) end + test "change_event/1 returns a event changeset" do + event = event_fixture() + assert %Ecto.Changeset{} = Events.change_event(event) + end + + test "terminate_event/1 terminates an event and broadcasts it" do + event = event_fixture() + assert event.expired_at == nil + + Phoenix.PubSub.subscribe(Claper.PubSub, "event:#{event.uuid}") + {:ok, event} = Events.terminate_event(event) + + assert NaiveDateTime.diff(NaiveDateTime.utc_now(), event.expired_at) |> abs() < 1 + assert_received {:event_terminated, uuid} + assert uuid == event.uuid + end + + test "delete_event/1 deletes the event" do + event = event_fixture() + + assert {:ok, %Event{}} = Events.delete_event(event) + assert_raise Ecto.NoResultsError, fn -> Events.get_event!(event.uuid) end + end + end + + describe "leading events" do + test "led_by?/2", context do + assert Events.led_by?(context.alice.email, Enum.at(context.carol_active_events, 0)) == true + assert Events.led_by?(context.bob.email, Enum.at(context.carol_active_events, 0)) == false + end + + test "create_activity_leader/1 with valid data creates an activity leader" do + attrs = %{ + email: "dan@example.com" + } + + {:ok, leader = %ActivityLeader{}} = Events.create_activity_leader(attrs) + assert leader.email == attrs.email + + event = event_fixture() + + attrs = %{ + email: "dan@example.com", + event_id: event.id + } + + {:ok, leader = %ActivityLeader{}} = Events.create_activity_leader(attrs) + assert leader.email == attrs.email + assert leader.event_id == attrs.event_id + end + + test "create_activity_leader/1 with invalid data returns error changeset" do + {:error, %Ecto.Changeset{}} = Events.create_activity_leader(%{}) + end + + test "create_activity_leader/1 disallows event owner as leader" do + user = user_fixture() + event = event_fixture(%{user: user}) + + attrs = %{ + email: user.email, + event_id: event.id, + user_email: user.email + } + + {:error, %Ecto.Changeset{}} = Events.create_activity_leader(attrs) + end + + test "get_activity_leader!/1 gets activity leader by ID" do + leader = activity_leader_fixture() + assert Events.get_activity_leader!(leader.id) == leader + end + + test "get_activity_leaders_for_event/1 gets activity leaders for event by ID", context do + event = Enum.at(context.carol_active_events, 0) + [leader] = Events.get_activity_leaders_for_event(event.id) + assert leader.user_id == context.alice.id + end + + test "change_activity_leader/2 returns an activity leader changeset" do + leader = activity_leader_fixture() + assert %Ecto.Changeset{} = Events.change_activity_leader(leader) + end + end + + describe "importing events" do test "import/3 transfer all interactions from an event to another" do user = user_fixture() from_event = event_fixture(%{user: user, name: "from event"}) @@ -121,48 +561,20 @@ defmodule Claper.EventsTest do Events.import(user.id, from_event.uuid, to_event.uuid) end end + end - test "duplicate_event/2 duplicates an event" do - user = user_fixture() - original_event = event_fixture(%{user: user, name: "Original Event"}) - presentation_file = presentation_file_fixture(%{event: original_event}) - presentation_state_fixture(%{presentation_file: presentation_file}) - poll_fixture(%{presentation_file_id: presentation_file.id}) - form_fixture(%{presentation_file_id: presentation_file.id}) - embed_fixture(%{presentation_file_id: presentation_file.id}) + defp list(events), do: Enum.reverse(events) - assert {:ok, duplicated_event} = Events.duplicate_event(user.id, original_event.uuid) + defp paginate(events, params \\ %{}) do + page = Map.get(params, "page", 1) + page_size = Map.get(params, "page_size", 5) + start_index = (page - 1) * page_size + event_count = length(events) - assert duplicated_event.id != original_event.id - assert duplicated_event.user_id == original_event.user_id - assert duplicated_event.name == "Original Event (Copy)" - assert duplicated_event.code != original_event.code - assert duplicated_event.uuid != original_event.uuid - - # Check if the presentation file was duplicated - duplicated_presentation_file = - Claper.Events.get_event!(duplicated_event.uuid, [:presentation_file]).presentation_file - - assert duplicated_presentation_file.id != presentation_file.id - assert duplicated_presentation_file.hash == presentation_file.hash - assert duplicated_presentation_file.length == presentation_file.length - - # Check if polls, forms, and embeds were duplicated - assert length(Claper.Polls.list_polls(duplicated_presentation_file.id)) == 1 - assert length(Claper.Forms.list_forms(duplicated_presentation_file.id)) == 1 - assert length(Claper.Embeds.list_embeds(duplicated_presentation_file.id)) == 1 - end - - test "delete_event/1 deletes the event" do - event = event_fixture() - - assert {:ok, %Event{}} = Events.delete_event(event) - assert_raise Ecto.NoResultsError, fn -> Events.get_event!(event.uuid) end - end - - test "change_event/1 returns a event changeset" do - event = event_fixture() - assert %Ecto.Changeset{} = Events.change_event(event) - end + { + events |> Enum.reverse() |> Enum.slice(start_index, page_size), + event_count, + ceil(event_count / page_size) + } end end diff --git a/test/claper_web/helpers/csv_exporter_test.exs b/test/claper_web/helpers/csv_exporter_test.exs new file mode 100644 index 0000000..25e9191 --- /dev/null +++ b/test/claper_web/helpers/csv_exporter_test.exs @@ -0,0 +1,219 @@ +defmodule ClaperWeb.Helpers.CSVExporterTest do + use Claper.DataCase + + alias ClaperWeb.Helpers.CSVExporter + alias Claper.Accounts.User + alias Claper.Events.Event + alias Claper.Accounts.Oidc.Provider + alias Claper.Accounts.Role + alias Claper.Repo + + describe "export_users_to_csv/1" do + setup do + # Create roles + {:ok, user_role} = Repo.insert(%Role{name: "user"}) + {:ok, admin_role} = Repo.insert(%Role{name: "admin"}) + + # Create users + {:ok, user1} = + Repo.insert(%User{ + email: "user1@example.com", + uuid: Ecto.UUID.generate(), + role_id: user_role.id, + inserted_at: ~N[2023-01-01 10:00:00], + hashed_password: "hashed_password", + is_randomized_password: false + }) + + {:ok, user2} = + Repo.insert(%User{ + email: "admin@example.com", + uuid: Ecto.UUID.generate(), + role_id: admin_role.id, + inserted_at: ~N[2023-01-02 10:00:00], + hashed_password: "hashed_password", + is_randomized_password: false + }) + + %{users: [user1, user2], user_role: user_role, admin_role: admin_role} + end + + test "exports users to CSV format", %{users: users} do + users = Repo.preload(users, :role) + csv = CSVExporter.export_users_to_csv(users) + + # CSV should have a header row and two data rows + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 3 + + # Check header + header = List.first(lines) + assert header =~ "Email" + assert header =~ "Name" + assert header =~ "Role" + assert header =~ "Created At" + + # Check data rows + assert Enum.at(lines, 1) =~ "user1@example.com" + assert Enum.at(lines, 1) =~ "user" + + assert Enum.at(lines, 2) =~ "admin@example.com" + assert Enum.at(lines, 2) =~ "admin" + end + + test "handles empty user list" do + csv = CSVExporter.export_users_to_csv([]) + + # CSV should only have a header row + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 1 + + # Check header + header = List.first(lines) + assert header =~ "Email" + assert header =~ "Name" + assert header =~ "Role" + end + end + + describe "export_events_to_csv/1" do + setup do + # Create events + {:ok, event1} = + Repo.insert(%Event{ + name: "Event One", + uuid: Ecto.UUID.generate(), + code: "event1", + started_at: ~N[2023-01-01 10:00:00], + expired_at: ~N[2023-01-01 12:00:00], + audience_peak: 10, + inserted_at: ~N[2023-01-01 09:00:00] + }) + + {:ok, event2} = + Repo.insert(%Event{ + name: "Event Two", + uuid: Ecto.UUID.generate(), + code: "event2", + started_at: ~N[2023-01-02 10:00:00], + expired_at: ~N[2023-01-02 12:00:00], + audience_peak: 20, + inserted_at: ~N[2023-01-01 09:30:00] + }) + + %{events: [event1, event2]} + end + + test "exports events to CSV format", %{events: events} do + csv = CSVExporter.export_events_to_csv(events) + + # CSV should have a header row and two data rows + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 3 + + # Check header + header = List.first(lines) + assert header =~ "Name" + assert header =~ "Description" + assert header =~ "Start Date" + assert header =~ "End Date" + assert header =~ "Status" + + # Check data rows contain event names + assert Enum.at(lines, 1) =~ "Event One" + assert Enum.at(lines, 2) =~ "Event Two" + end + + test "handles empty event list" do + csv = CSVExporter.export_events_to_csv([]) + + # CSV should only have a header row + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 1 + + # Check header + header = List.first(lines) + assert header =~ "Name" + assert header =~ "Description" + assert header =~ "Start Date" + end + end + + describe "export_oidc_providers_to_csv/1" do + setup do + # Create providers + {:ok, provider1} = + Repo.insert(%Provider{ + name: "Provider One", + issuer: "https://example1.com", + client_id: "client1", + client_secret: "secret1", + redirect_uri: "https://app.example.com/callback1", + scope: "openid email", + active: true, + inserted_at: ~N[2023-01-01 09:00:00] + }) + + {:ok, provider2} = + Repo.insert(%Provider{ + name: "Provider Two", + issuer: "https://example2.com", + client_id: "client2", + client_secret: "secret2", + redirect_uri: "https://app.example.com/callback2", + scope: "openid profile", + active: false, + inserted_at: ~N[2023-01-01 09:30:00] + }) + + %{providers: [provider1, provider2]} + end + + test "exports providers to CSV format", %{providers: providers} do + csv = CSVExporter.export_oidc_providers_to_csv(providers) + + # CSV should have a header row and two data rows + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 3 + + # Check header + header = List.first(lines) + assert header =~ "Name" + assert header =~ "Issuer" + assert header =~ "Client ID" + assert header =~ "Active" + + # Client secret should not be included for security + refute header =~ "Client Secret" + + # Check data rows + assert Enum.at(lines, 1) =~ "Provider One" + assert Enum.at(lines, 1) =~ "https://example1.com" + assert Enum.at(lines, 1) =~ "client1" + assert Enum.at(lines, 1) =~ "Yes" + + assert Enum.at(lines, 2) =~ "Provider Two" + assert Enum.at(lines, 2) =~ "https://example2.com" + assert Enum.at(lines, 2) =~ "client2" + assert Enum.at(lines, 2) =~ "No" + + # Client secrets should not be included in the CSV + refute csv =~ "secret1" + refute csv =~ "secret2" + end + + test "handles empty provider list" do + csv = CSVExporter.export_oidc_providers_to_csv([]) + + # CSV should only have a header row + lines = String.split(csv, "\r\n", trim: true) + assert length(lines) == 1 + + # Check header + header = List.first(lines) + assert header =~ "Name" + assert header =~ "Issuer" + assert header =~ "Client ID" + end + end +end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 433d6c5..dad6a9b 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -28,9 +28,13 @@ defmodule Claper.DataCase do end end - setup tags do - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Claper.Repo, shared: not tags[:async]) - on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) + setup context do + # Don't check out a connection if a setup_all did so already + if context[:sandbox_owner_pid] == nil do + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Claper.Repo, shared: not context[:async]) + on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) + end + :ok end diff --git a/test/support/fixtures/events_fixtures.ex b/test/support/fixtures/events_fixtures.ex index b8deb2d..33a11a1 100644 --- a/test/support/fixtures/events_fixtures.ex +++ b/test/support/fixtures/events_fixtures.ex @@ -9,7 +9,27 @@ defmodule Claper.EventsFixtures do require Claper.UtilFixture @doc """ - Generate a event. + Generate an activity leader. + """ + def activity_leader_fixture(attrs \\ %{}, preload \\ []) do + assoc = %{ + event: attrs[:event] || event_fixture(), + user: attrs[:user] || user_fixture() + } + + {:ok, activity_leader} = + attrs + |> Enum.into(%{ + email: assoc.user.email, + event_id: assoc.event.id + }) + |> Claper.Events.create_activity_leader() + + Claper.UtilFixture.merge_preload(activity_leader, preload, assoc) + end + + @doc """ + Generate an event. """ def event_fixture(attrs \\ %{}, preload \\ []) do assoc = %{user: attrs[:user] || user_fixture()} diff --git a/with_env.sh b/with_env.sh new file mode 100755 index 0000000..5c6fd4a --- /dev/null +++ b/with_env.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -a + +# Default environment file +env_file=".env" + +# Parse command-line arguments +while [[ "$#" -gt 0 ]]; do + case "$1" in + --env) + env_file="$2" + shift 2 # Skip the next argument as it's the value for --env + ;; + *) + break # Exit the loop if an unrecognized option is found + ;; + esac +done + +source "$env_file" +set +a + +# Remaining arguments are treated as the command to run +args=("$@") + +# Check if no command is provided +if [ ${#args[@]} -eq 0 ]; then + echo "Usage: $0 [--env path/to/env] " + echo "Example: $0 mix phx.server" + echo "Example: $0 --env path/to/env mix phx.server" + exit 1 +fi + +# Execute the command +"${args[@]}"