mirror of
https://github.com/ClaperCo/Claper.git
synced 2025-12-14 19:07:52 +01:00
New features for v2 (#83)
This commit is contained in:
44
.env.sample
44
.env.sample
@@ -1,25 +1,39 @@
|
||||
DATABASE_URL=postgres://claper:claper@db:5432/claper
|
||||
SECRET_KEY_BASE=0LZiQBLw4WvqPlz4cz8RsHJlxNiSqM9B48y4ChyJ5v1oA0L/TPIqRjQNdPZN3iEG # Generate with `mix phx.gen.secret`
|
||||
|
||||
# Storage configuration
|
||||
|
||||
PRESENTATION_STORAGE=local
|
||||
PRESENTATION_STORAGE_DIR=/app/uploads
|
||||
MAX_FILE_SIZE_MB=15
|
||||
#MAX_FILE_SIZE_MB=15
|
||||
|
||||
AWS_ACCESS_KEY_ID=xxx
|
||||
AWS_SECRET_ACCESS_KEY=xxx
|
||||
AWS_REGION=eu-west-3
|
||||
AWS_PRES_BUCKET=xxx
|
||||
#AWS_ACCESS_KEY_ID=xxx
|
||||
#AWS_SECRET_ACCESS_KEY=xxx
|
||||
#AWS_REGION=eu-west-3
|
||||
#AWS_PRES_BUCKET=xxx
|
||||
|
||||
SMTP_RELAY=xx.example.com
|
||||
SMTP_USERNAME=johndoe@example.com
|
||||
SMTP_PASSWORD=xxx
|
||||
SMTP_PORT=465
|
||||
SMTP_TLS=if_available
|
||||
# Mail configuration
|
||||
|
||||
MAIL_TRANSPORT=local
|
||||
MAIL_FROM=noreply@claper.co
|
||||
MAIL_FROM_NAME=Claper
|
||||
|
||||
ENABLE_ACCOUNT_CREATION=true
|
||||
ENABLE_MAILBOX_ROUTE=false
|
||||
MAILBOX_USER=admin
|
||||
MAILBOX_PASSWORD=admin
|
||||
#SMTP_RELAY=xx.example.com
|
||||
#SMTP_USERNAME=johndoe@example.com
|
||||
#SMTP_PASSWORD=xxx
|
||||
#SMTP_PORT=465
|
||||
#SMTP_TLS=if_available
|
||||
|
||||
GS_JPG_RESOLUTION=300x300
|
||||
#ENABLE_MAILBOX_ROUTE=false
|
||||
#MAILBOX_USER=admin
|
||||
#MAILBOX_PASSWORD=admin
|
||||
|
||||
# Claper configuration
|
||||
|
||||
#ENABLE_ACCOUNT_CREATION=true
|
||||
#GS_JPG_RESOLUTION=300x300
|
||||
|
||||
# Network configuration
|
||||
|
||||
ENDPOINT_PORT=4000
|
||||
ENDPOINT_HOST=localhost
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
import_deps: [:ecto, :phoenix],
|
||||
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"],
|
||||
import_deps: [:ecto, :ecto_sql, :phoenix],
|
||||
subdirectories: ["priv/*/migrations"],
|
||||
plugins: [Phoenix.LiveView.HTMLFormatter]
|
||||
plugins: [Phoenix.LiveView.HTMLFormatter],
|
||||
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
|
||||
]
|
||||
|
||||
6
.github/workflows/elixir.yml
vendored
6
.github/workflows/elixir.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:9
|
||||
image: postgres:15
|
||||
ports: ['5432:5432']
|
||||
env:
|
||||
POSTGRES_PASSWORD: claper
|
||||
@@ -42,8 +42,8 @@ jobs:
|
||||
- name: Set up Elixir
|
||||
uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f
|
||||
with:
|
||||
elixir-version: '1.13.2'
|
||||
otp-version: '24.1'
|
||||
elixir-version: '1.15.4'
|
||||
otp-version: '26'
|
||||
- name: Restore dependencies cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,3 +1,20 @@
|
||||
## v2.0.0
|
||||
|
||||
- Add dynamic layout in the manager view
|
||||
- Add quick event feature
|
||||
- Add toggle for message reactions in attendees room
|
||||
- Add toggle for polls results in attendees room
|
||||
- Add delete account button in user settings
|
||||
- Add tour guide for new users
|
||||
- Add headers to exported CSV in reports
|
||||
- Add the ability to embed attendees room in an iframe
|
||||
- Change date picker for a more user-friendly one
|
||||
- Upgrade Ecto, Phoenix and LiveView
|
||||
- Fix user avatars in reports
|
||||
- Fix average voters stats
|
||||
- Fix some UI/UX issues
|
||||
- Remove end date for events
|
||||
|
||||
## v1.7.0
|
||||
|
||||
- Add keyboard shortcuts to control settings (#64) (@Dhanus3133)
|
||||
@@ -12,6 +29,7 @@
|
||||
- Security updates
|
||||
|
||||
## v1.6.0
|
||||
|
||||
- Improve QR code readability
|
||||
- Add ARM Docker image
|
||||
- Refactor all runtime configuration
|
||||
@@ -46,25 +64,21 @@
|
||||
- Add MAX_FILE_SIZE_MB environment variable to limit file upload size
|
||||
- Add feature to deactivate messages during a presentation
|
||||
|
||||
|
||||
## v1.3.0
|
||||
|
||||
- Add Form feature to collect data from your public
|
||||
- Improve docs for Docker Compose
|
||||
- Improve Docker Compose file reference
|
||||
|
||||
|
||||
## v1.2.1
|
||||
|
||||
- Fix presenter url (400 error in production)
|
||||
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Added password change form in settings
|
||||
- Added more documentation on deployment in production
|
||||
|
||||
|
||||
## v1.1.1
|
||||
|
||||
_Security updates_
|
||||
@@ -72,7 +86,6 @@ _Security updates_
|
||||
- Added `ENABLE_MAILBOX_ROUTE`, `MAILBOX_USER` and `MAILBOX_PASSWORD` environment variables to enable/disable route to local mailbox (`/dev/mailbox`) and basic auth (optional)
|
||||
- Restricted `/users/register` route if `ENABLE_ACCOUNT_CREATION` is false
|
||||
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Added password authentication
|
||||
@@ -81,7 +94,6 @@ _Security updates_
|
||||
- Added new `ENABLE_ACCOUNT_CREATION` environment variable to enable or disable user registration
|
||||
- Improved french localization
|
||||
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first version of the open-source project. Feel free to contribute!
|
||||
|
||||
@@ -11,4 +11,9 @@ Don't forget to give the project a star! Thanks again!
|
||||
|
||||
## Translations
|
||||
|
||||
You can contribute to the translations by editing or addind PO files in `/priv/gettext/`
|
||||
You can contribute to the translations by editing the files in `/priv/gettext/`
|
||||
Each language has its own directory with the `.po` files. The country code is used as the directory name and following the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) nomenclature, for example, `en` for English, `fr` for French, `de` for German. You can find the list of country codes [here](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
|
||||
|
||||
### Add new language
|
||||
|
||||
To add a new language, you can copy the `en` directory and rename it with the country code of the new language. Then you can edit the `.po` files with the translations.
|
||||
|
||||
@@ -84,7 +84,7 @@ RUN mix release
|
||||
# the compiled release and other runtime necessities
|
||||
FROM ${RUNNER_IMAGE}
|
||||
|
||||
RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales ghostscript \
|
||||
RUN apt-get update -y && apt-get install -y curl libstdc++6 openssl libncurses5 locales ghostscript \
|
||||
&& apt-get install -y libreoffice --no-install-recommends && apt-get clean && rm -f /var/lib/apt/lists/*_*
|
||||
|
||||
# Set the locale
|
||||
|
||||
53
README.md
53
README.md
@@ -1,4 +1,3 @@
|
||||
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
@@ -26,14 +25,12 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
[![Product Name Screen Shot][product-screenshot]](https://claper.co)
|
||||
|
||||
Claper turns your presentations into an interactive, engaging and exciting experience.
|
||||
|
||||
Claper has a two-sided mission:
|
||||
|
||||
- The first one is to help these people presenting an idea or a message by giving them the opportunity to make their presentation unique and to have real-time feedback from their audience.
|
||||
- The second one is to help each participant to take their place, to be an actor in the presentation, in the meeting and to feel important and useful.
|
||||
|
||||
@@ -43,12 +40,12 @@ Supported languages: 🇬🇧 English, 🇫🇷 French, 🇩🇪 German.
|
||||
|
||||
Claper is proudly powered by Phoenix and Elixir.
|
||||
|
||||
* [![Phoenix][Phoenix]][Phoenix-url]
|
||||
* [![Elixir][Elixir]][Elixir-url]
|
||||
* [![Tailwind][Tailwind]][Tailwind-url]
|
||||
|
||||
- [![Phoenix][Phoenix]][Phoenix-url]
|
||||
- [![Elixir][Elixir]][Elixir-url]
|
||||
- [![Tailwind][Tailwind]][Tailwind-url]
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
This is an example of how you may give instructions on setting up your project locally.
|
||||
@@ -57,18 +54,20 @@ To get a local copy up and running follow these simple example steps.
|
||||
### Prerequisites
|
||||
|
||||
To run Claper on your local environment you need to have:
|
||||
* Postgres >= 9
|
||||
* Elixir >= 1.13.2
|
||||
* Erlang >= 24
|
||||
* NPM >= 6.14.17
|
||||
* NodeJS >= 14.19.2
|
||||
* Ghostscript >= 9.5.0 (for PDF support)
|
||||
* Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
- Postgres >= 9
|
||||
- Elixir >= 1.13.2
|
||||
- Erlang >= 24
|
||||
- NPM >= 6.14.17
|
||||
- NodeJS >= 14.19.2
|
||||
- Ghostscript >= 9.5.0 (for PDF support)
|
||||
- Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
You can also use Docker to easily run a Postgres instance:
|
||||
|
||||
```sh
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:9
|
||||
```
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:15
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -105,7 +104,6 @@ Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
If you have configured `MAIL` to `local`, you can access to the mailbox at [`localhost:4000/dev/mailbox`](http://localhost:4000/dev/mailbox).
|
||||
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
A Docker Compose [reference file](https://github.com/ClaperCo/Claper/blob/main/docker-compose.yml) is provided in the repository. You can use it to run Claper with Docker Compose.
|
||||
@@ -116,19 +114,8 @@ cd Claper
|
||||
docker compose up
|
||||
```
|
||||
|
||||
|
||||
### Using Docker Compose for Dev
|
||||
|
||||
To easy check new features, it is possible to directly build the Docker image from the source code and run the container with the [docker-compose-dev.yml](https://github.com/ClaperCo/Claper/blob/main/docker-compose-dev.yml) file.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ClaperCo/Claper.git
|
||||
cd Claper
|
||||
docker compose -f docker-compose-dev.yml up
|
||||
```
|
||||
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
@@ -142,23 +129,23 @@ Don't forget to give the project a star! Thanks again!
|
||||
4. Push to the Branch (`git push origin feature/amazing_feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
|
||||
<!-- LICENSE -->
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the GPLv3 License. See `LICENSE.txt` for more information.
|
||||
|
||||
<!-- CONTACT -->
|
||||
|
||||
## Contact
|
||||
|
||||
[](https://x.com/alxlion_)
|
||||
|
||||
Project Link: [https://github.com/ClaperCo/Claper](https://github.com/ClaperCo/Claper)
|
||||
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/ClaperCo/Claper.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/ClaperCo/Claper/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/ClaperCo/Claper.svg?style=for-the-badge
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import url("flatpickr/dist/flatpickr.min.css");
|
||||
@import url('air-datepicker/air-datepicker.css');
|
||||
@import url("animate.css/animate.min.css");
|
||||
|
||||
@tailwind base;
|
||||
@@ -427,4 +427,21 @@
|
||||
-ms-transform:rotate(-20deg);
|
||||
-o-transform:rotate(-20deg);
|
||||
transform:rotate(-20deg);
|
||||
}
|
||||
|
||||
/* Air datepicker */
|
||||
.air-datepicker-body--day-name {
|
||||
@apply text-primary-600;
|
||||
}
|
||||
|
||||
.air-datepicker-cell.-selected-, .air-datepicker-cell.-selected-.-current- {
|
||||
@apply bg-primary-500 text-white hover:bg-primary-600;
|
||||
}
|
||||
|
||||
.air-datepicker-cell.-current- {
|
||||
@apply text-secondary-500;
|
||||
}
|
||||
|
||||
.animate__slow_slow {
|
||||
--animate-duration: 5s;
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
@import "../node_modules/tiny-slider/src/tiny-slider.scss";
|
||||
|
||||
@import "../node_modules/@sjmc11/tourguidejs/src/scss/tour.scss";
|
||||
|
||||
$particleSize: 20vmin;
|
||||
$animationDuration: 6s;
|
||||
$amount: 20;
|
||||
|
||||
203
assets/js/app.js
203
assets/js/app.js
@@ -1,20 +1,3 @@
|
||||
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
|
||||
// to get started and then uncomment the line below.
|
||||
// import "./user_socket.js"
|
||||
|
||||
// You can include dependencies in two ways.
|
||||
//
|
||||
// The simplest option is to put them in assets/vendor and
|
||||
// import them using relative paths:
|
||||
//
|
||||
// import "./vendor/some-package.js"
|
||||
//
|
||||
// Alternatively, you can `npm install some-package` and import
|
||||
// them using a path starting with the package name:
|
||||
//
|
||||
// import "some-package"
|
||||
//
|
||||
|
||||
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
|
||||
import "phoenix_html"
|
||||
// Establish Phoenix Socket and LiveView configuration.
|
||||
@@ -22,13 +5,18 @@ import {Socket, Presence} from "phoenix"
|
||||
import {LiveSocket} from "phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
import Alpine from 'alpinejs'
|
||||
import flatpickr from "flatpickr"
|
||||
import moment from "moment-timezone"
|
||||
import AirDatepicker from 'air-datepicker'
|
||||
import airdatepickerLocaleEn from 'air-datepicker/locale/en'
|
||||
import airdatepickerLocaleFr from 'air-datepicker/locale/fr'
|
||||
import airdatepickerLocaleDe from 'air-datepicker/locale/de'
|
||||
import 'moment/locale/de'
|
||||
import 'moment/locale/fr'
|
||||
import QRCodeStyling from "qr-code-styling"
|
||||
import { Presenter } from "./presenter"
|
||||
import { Manager } from "./manager"
|
||||
import Split from "split-grid"
|
||||
import { TourGuideClient } from "@sjmc11/tourguidejs/src/Tour"
|
||||
window.moment = moment
|
||||
|
||||
window.moment.locale("en")
|
||||
@@ -36,32 +24,125 @@ window.moment.locale(navigator.language.split('-')[0])
|
||||
window.Alpine = Alpine
|
||||
Alpine.start()
|
||||
|
||||
let airdatepickerLocale = {
|
||||
en: airdatepickerLocaleEn,
|
||||
fr: airdatepickerLocaleFr,
|
||||
de: airdatepickerLocaleDe
|
||||
}
|
||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
let Hooks = {}
|
||||
|
||||
Hooks.EmbeddedBanner = {
|
||||
mounted() {
|
||||
if (window !== window.parent) {
|
||||
this.el.classList.remove("hidden")
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (window !== window.parent) {
|
||||
this.el.classList.remove("hidden")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Hooks.TourGuide = {
|
||||
mounted() {
|
||||
this.tour = new TourGuideClient({
|
||||
nextLabel: this.el.dataset.nextLabel,
|
||||
prevLabel: this.el.dataset.prevLabel,
|
||||
finishLabel: this.el.dataset.finishLabel,
|
||||
completeOnFinish: true,
|
||||
rememberStep: true,
|
||||
})
|
||||
|
||||
if (!this.tour.isFinished(this.el.dataset.group)) {
|
||||
this.tour.start(this.el.dataset.group)
|
||||
}
|
||||
|
||||
this.tour.onBeforeExit(() => {
|
||||
this.tour.finishTour(true, this.el.dataset.group)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.Split = {
|
||||
mounted() {
|
||||
const type = this.el.dataset.type
|
||||
const gutter = this.el.dataset.gutter
|
||||
const columnSlitValue = localStorage.getItem('column-split') || '1fr 10px 1fr'
|
||||
const rowSlitValue = localStorage.getItem('row-split') || '1fr 10px 1fr'
|
||||
|
||||
if (type === "column") {
|
||||
this.columnSplit = Split({
|
||||
columnGutters: [{
|
||||
track: 1,
|
||||
element: this.el.querySelector(gutter)
|
||||
}],
|
||||
onDragEnd: () => {
|
||||
const currentPosition = this.el.style['grid-template-columns']
|
||||
localStorage.setItem('column-split', currentPosition)
|
||||
},
|
||||
})
|
||||
this.el.style['grid-template-columns'] = columnSlitValue
|
||||
} else {
|
||||
this.rowSplit = Split({
|
||||
rowGutters: [{
|
||||
track: 1,
|
||||
element: this.el.querySelector(gutter)
|
||||
}],
|
||||
onDragEnd: () => {
|
||||
const value = this.el.style['grid-template-rows']
|
||||
localStorage.setItem('row-split', value)
|
||||
},
|
||||
})
|
||||
this.el.style['grid-template-rows'] = rowSlitValue
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (this.columnSplit) {
|
||||
const value = localStorage.getItem('column-split') || '1fr 10px 1fr'
|
||||
this.el.style['grid-template-columns'] = value
|
||||
}
|
||||
if (this.rowSplit) {
|
||||
const value = localStorage.getItem('row-split') || '1fr 10px 1fr'
|
||||
this.el.style['grid-template-rows'] = value
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.columnSplit) {
|
||||
this.columnSplit.destroy()
|
||||
}
|
||||
if (this.rowSplit) {
|
||||
this.rowSplit.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.Scroll = {
|
||||
mounted() {
|
||||
if (this.el.dataset.postsNb > 4) window.scrollTo({top: document.querySelector(this.el.dataset.target).scrollHeight, behavior: 'smooth'});
|
||||
this.handleEvent("scroll", () => {
|
||||
let t = document.querySelector(this.el.dataset.target)
|
||||
if (this.el.childElementCount > 4 && (window.scrollY + window.innerHeight >= t.offsetHeight - 100)) {
|
||||
window.scrollTo({top: t.scrollHeight, behavior: 'smooth'});
|
||||
}
|
||||
})
|
||||
},
|
||||
updated() {
|
||||
let t = document.querySelector(this.el.dataset.target)
|
||||
if (this.el.childElementCount > 4 && (window.scrollY + window.innerHeight >= t.offsetHeight - 300)) {
|
||||
window.scrollTo({top: t.scrollHeight, behavior: 'smooth'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.ScrollIntoDiv = {
|
||||
mounted() {
|
||||
let t = document.querySelector(this.el.dataset.target)
|
||||
if (this.el.dataset.postsNb > 4) t.scrollTo({top: t.scrollHeight, behavior: 'smooth'});
|
||||
|
||||
this.handleEvent("scroll", () => {
|
||||
let t = document.querySelector(this.el.dataset.target);
|
||||
if (this.el.childElementCount > 4 && (t.scrollHeight - t.scrollTop < t.clientHeight + 100)) {
|
||||
t.scrollTo({top: t.scrollHeight, behavior: 'smooth'});
|
||||
}
|
||||
})
|
||||
this.scrollElement(true);
|
||||
this.handleEvent("scroll", this.scrollElement.bind(this));
|
||||
},
|
||||
scrollElement(firstScroll) {
|
||||
let t = this.el.parentElement;
|
||||
if (firstScroll === true || (t.scrollHeight - t.scrollTop - t.clientHeight) <= 100) {
|
||||
t.scrollTo({top: t.scrollHeight, behavior: 'smooth'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +155,7 @@ Hooks.NicknamePicker = {
|
||||
|
||||
this.el.addEventListener("click", (e) => this.clicked(e))
|
||||
},
|
||||
destroy() {
|
||||
destroyed() {
|
||||
this.el.removeEventListener("click", (e) => this.clicked(e))
|
||||
},
|
||||
clicked(e) {
|
||||
@@ -91,7 +172,7 @@ Hooks.EmptyNickname = {
|
||||
mounted() {
|
||||
this.el.addEventListener("click", (e) => this.clicked(e))
|
||||
},
|
||||
destroy() {
|
||||
destroyed() {
|
||||
this.el.removeEventListener("click", (e) => this.clicked(e))
|
||||
},
|
||||
clicked(e) {
|
||||
@@ -170,33 +251,21 @@ Hooks.CalendarLocalDate = {
|
||||
}
|
||||
Hooks.Pickr = {
|
||||
mounted() {
|
||||
const getDefaultDate = (dateStart, dateEnd, mode) => {
|
||||
if (mode == "range") {
|
||||
return moment.utc(dateStart).format('Y-MM-DD HH:mm') + " - " + moment.utc(dateEnd).format('Y-MM-DD HH:mm')
|
||||
} else {
|
||||
return moment.utc(dateStart).format('Y-MM-DD HH:mm')
|
||||
}
|
||||
};
|
||||
this.pickr = flatpickr(this.el, {
|
||||
wrap: true,
|
||||
inline: false,
|
||||
enableTime: true,
|
||||
enable: JSON.parse(this.el.dataset.enable),
|
||||
time_24hr: true,
|
||||
formatDate: (date, format, locale) => {
|
||||
return moment(date).utc().format('Y-MM-DD HH:mm');
|
||||
const localTime = this.el.querySelector("input[type=text]")
|
||||
const utcTime = this.el.querySelector("input[type=hidden]")
|
||||
localTime.value = moment.utc(utcTime.value).local().format("DD-MM-YYYY HH:mm")
|
||||
this.pickr = new AirDatepicker(localTime, {
|
||||
dateFormat: "dd-MM-yyyy",
|
||||
timepicker: true,
|
||||
minutesStep: 5,
|
||||
minDate: moment(),
|
||||
timeFormat: "HH:mm",
|
||||
selectedDates: [moment(localTime.value, "DD-MM-YYYY HH:mm").toDate()],
|
||||
onSelect: ({date}) => {
|
||||
const utc = moment(date).utc().format("YYYY-MM-DDTHH:mm:ss")
|
||||
utcTime.value = utc
|
||||
},
|
||||
parseDate: (datestr, format) => {
|
||||
return moment.utc(datestr).local().toDate();
|
||||
},
|
||||
locale: {
|
||||
firstDayOfWeek: 1,
|
||||
rangeSeparator: ' - '
|
||||
},
|
||||
mode: this.el.dataset.mode == "range" ? "range" : "single",
|
||||
minuteIncrement: 1,
|
||||
dateFormat: "Y-m-d H:i",
|
||||
defaultDate: getDefaultDate(this.el.dataset.defaultDateStart, this.el.dataset.defaultDateEnd, this.el.dataset.mode)
|
||||
locale: airdatepickerLocale[navigator.language.split('-')[0]]
|
||||
})
|
||||
},
|
||||
updated() {
|
||||
@@ -298,11 +367,6 @@ Hooks.WelcomeEarly = {
|
||||
})
|
||||
}
|
||||
}
|
||||
Hooks.DefaultValue = {
|
||||
mounted() {
|
||||
this.el.value = moment(this.el.dataset.defaultValue ? this.el.dataset.defaultValue : undefined).utc().format();
|
||||
}
|
||||
}
|
||||
Hooks.ClickFeedback = {
|
||||
clicked(e) {
|
||||
this.el.className = "animate__animated animate__rubberBand animate__faster";
|
||||
@@ -313,7 +377,7 @@ Hooks.ClickFeedback = {
|
||||
mounted() {
|
||||
this.el.addEventListener("click", (e) => this.clicked(e))
|
||||
},
|
||||
destroy() {
|
||||
destroyed() {
|
||||
this.el.removeEventListener("click", (e) => this.clicked(e))
|
||||
}
|
||||
}
|
||||
@@ -370,6 +434,15 @@ Hooks.QRCode = {
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.Dropdown = {
|
||||
mounted() {
|
||||
this.el.addEventListener("click", (e) => {
|
||||
e.preventDefault()
|
||||
this.el.classList.toggle("hidden")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let Uploaders = {}
|
||||
|
||||
Uploaders.S3 = function(entries, onViewError){
|
||||
|
||||
@@ -15,8 +15,9 @@ export class Manager {
|
||||
|
||||
if (el) {
|
||||
setTimeout(() => {
|
||||
document.getElementById("slide-preview-" + data.current_page).scrollIntoView({
|
||||
block: 'center',
|
||||
document.getElementById("slides").scrollTo({
|
||||
top: el.offsetTop - el.scrollHeight,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, data.timeout ? data.timeout : 0)
|
||||
@@ -51,10 +52,13 @@ export class Manager {
|
||||
var el = document.getElementById("slide-preview-" + this.currentPage)
|
||||
|
||||
if (el) {
|
||||
document.getElementById("slide-preview-" + this.currentPage).scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'smooth'
|
||||
});
|
||||
setTimeout(() => {
|
||||
document.getElementById("slides").scrollTo({
|
||||
top: el.offsetTop - el.scrollHeight,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2011
assets/package-lock.json
generated
2011
assets/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,13 @@
|
||||
"alpinejs": "^3.13.1",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"esbuild": "^0.14.54",
|
||||
"flatpickr": "^4.6.13",
|
||||
"postcss": "^8.4.29",
|
||||
"postcss-import": "^15.1.0",
|
||||
"tailwindcss": "^3.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sjmc11/tourguidejs": "^0.0.16",
|
||||
"air-datepicker": "^3.5.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
@@ -19,6 +20,8 @@
|
||||
"phoenix_html": "file:../deps/phoenix_html",
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"split-grid": "^1.0.11",
|
||||
"split.js": "^1.6.5",
|
||||
"tiny-slider": "^2.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,17 @@ port = get_int_from_path_or_env(config_dir, "PORT", "4000")
|
||||
|
||||
secret_key_base = get_var_from_path_or_env(config_dir, "SECRET_KEY_BASE", nil)
|
||||
|
||||
case secret_key_base do
|
||||
nil ->
|
||||
raise "SECRET_KEY_BASE configuration option is required. See https://docs.claper.co/configuration.html#production-docker"
|
||||
if Mix.env() == :prod do
|
||||
case secret_key_base do
|
||||
nil ->
|
||||
raise "SECRET_KEY_BASE configuration option is required. See https://docs.claper.co/configuration.html#production-docker"
|
||||
|
||||
key when byte_size(key) < 32 ->
|
||||
raise "SECRET_KEY_BASE must be at least 32 bytes long. See https://docs.claper.co/configuration.html#production-docker"
|
||||
key when byte_size(key) < 32 ->
|
||||
raise "SECRET_KEY_BASE must be at least 32 bytes long. See https://docs.claper.co/configuration.html#production-docker"
|
||||
|
||||
_ ->
|
||||
nil
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
endpoint_host = get_var_from_path_or_env(config_dir, "ENDPOINT_HOST", "localhost")
|
||||
|
||||
@@ -11,7 +11,7 @@ config :claper, Claper.Repo,
|
||||
database: "claper_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
hostname: "localhost",
|
||||
pool: Ecto.Adapters.SQL.Sandbox,
|
||||
pool_size: 10
|
||||
pool_size: 1
|
||||
|
||||
# We don't run a server during test. If one is required,
|
||||
# you can enable the server option below.
|
||||
@@ -24,7 +24,7 @@ config :claper, ClaperWeb.Endpoint,
|
||||
config :claper, Claper.Mailer, adapter: Swoosh.Adapters.Test
|
||||
|
||||
# Print only warnings and errors during test
|
||||
config :logger, level: :warn
|
||||
config :logger, level: :warning
|
||||
|
||||
# Initialize plugs at runtime for faster test compilation
|
||||
config :phoenix, :plug_init_mode, :runtime
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
version: "3.0"
|
||||
services:
|
||||
db:
|
||||
image: postgres:9
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: claper
|
||||
POSTGRES_USER: claper
|
||||
POSTGRES_DB: claper
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U claper"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
app:
|
||||
build: .
|
||||
user: 0:0
|
||||
ports:
|
||||
- 4000:4000
|
||||
volumes:
|
||||
- uploads:/app/uploads
|
||||
environment:
|
||||
DATABASE_URL: postgres://claper:claper@db:5432/claper
|
||||
SECRET_KEY_BASE: 0LZiQBLw4WvqPlz4cz8RsHJlxNiSqM9B48y4ChyJ5v1oA0L/TPIqRjQNdPZN3iEG
|
||||
MAIL_TRANSPORT: local
|
||||
ENDPOINT_PORT: 4000
|
||||
PRESENTATION_STORAGE: local
|
||||
MAX_FILE_SIZE_MB: 15
|
||||
ENABLE_ACCOUNT_CREATION: true
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
postgres-data:
|
||||
uploads:
|
||||
@@ -1,37 +1,50 @@
|
||||
version: "3.0"
|
||||
services:
|
||||
db:
|
||||
image: postgres:9
|
||||
ports:
|
||||
image: postgres:15
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
- "claper-db:/var/lib/postgresql/data"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- pg_isready
|
||||
- "-q"
|
||||
- "-d"
|
||||
- "claper"
|
||||
- "-U"
|
||||
- "claper"
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
environment:
|
||||
POSTGRES_PASSWORD: claper
|
||||
POSTGRES_USER: claper
|
||||
POSTGRES_DB: claper
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U claper"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks:
|
||||
- claper-net
|
||||
app:
|
||||
image: ghcr.io/claperco/claper:latest
|
||||
image: ghcr.io/claperco/claper:latest # or build: .
|
||||
user: 0:0
|
||||
ports:
|
||||
ports:
|
||||
- 4000:4000
|
||||
volumes:
|
||||
- uploads:/app/uploads
|
||||
environment:
|
||||
DATABASE_URL: postgres://claper:claper@db:5432/claper
|
||||
SECRET_KEY_BASE: 0LZiQBLw4WvqPlz4cz8RsHJlxNiSqM9B48y4ChyJ5v1oA0L/TPIqRjQNdPZN3iEG
|
||||
MAIL_TRANSPORT: local
|
||||
ENDPOINT_PORT: 4000
|
||||
PRESENTATION_STORAGE: local
|
||||
MAX_FILE_SIZE_MB: 15
|
||||
ENABLE_ACCOUNT_CREATION: true
|
||||
- "claper-uploads:/app/uploads"
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:4000 || exit 1
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
timeout: 5s
|
||||
env_file: .env
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
uploads:
|
||||
claper-db:
|
||||
driver: local
|
||||
claper-uploads:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
claper-net:
|
||||
driver: bridge
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
## Prerequisites
|
||||
|
||||
To run Claper on your production environment you need to have:
|
||||
* Postgres >= 9
|
||||
* Elixir >= 1.13.2
|
||||
* Erlang >= 24
|
||||
* NPM >= 6.14.17
|
||||
* NodeJS >= 14.19.2
|
||||
* Ghostscript >= 9.5.0 (for PDF support)
|
||||
* Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
- Postgres >= 9
|
||||
- Elixir >= 1.13.2
|
||||
- Erlang >= 24
|
||||
- NPM >= 6.14.17
|
||||
- NodeJS >= 14.19.2
|
||||
- Ghostscript >= 9.5.0 (for PDF support)
|
||||
- Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
## Steps (without docker)
|
||||
|
||||
@@ -71,43 +71,83 @@ server {
|
||||
Here is a docker-compose example to run Claper behind Traefik.
|
||||
|
||||
```yaml
|
||||
version: "3.0"
|
||||
services:
|
||||
db:
|
||||
image: postgres:9
|
||||
image: postgres:15
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- "claper-db:/var/lib/postgresql/data"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- pg_isready
|
||||
- "-q"
|
||||
- "-d"
|
||||
- "claper"
|
||||
- "-U"
|
||||
- "claper"
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
environment:
|
||||
POSTGRES_PASSWORD: claper
|
||||
POSTGRES_USER: claper
|
||||
POSTGRES_DB: claper
|
||||
networks:
|
||||
- claper-net
|
||||
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
DATABASE_URL: postgres://claper:claper@db:5432/claper
|
||||
SECRET_KEY_BASE: 0LZiQBLw4WvqPlz4cz8RsHJlxNiSqM9B48y4ChyJ5v1oA0L/TPIqRjQNdPZN3iEG
|
||||
MAIL_TRANSPORT: local
|
||||
ENDPOINT_HOST: claper.local
|
||||
ENDPOINT_PORT: 4000
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:4000 || exit 1
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
timeout: 5s
|
||||
volumes:
|
||||
- "claper-uploads:/app/uploads"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.claper.rule=Host(`claper.local`)"
|
||||
- "traefik.http.routers.claper.entrypoints=web"
|
||||
- "traefik.http.routers.app.rule=Host(`app.claper.co`)" # change to your domain
|
||||
- "traefik.http.routers.app.tls.certresolver=myresolver"
|
||||
- "traefik.http.routers.app.entrypoints=web"
|
||||
- "traefik.http.services.app.loadbalancer.server.port=4000"
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- db
|
||||
- traefik
|
||||
networks:
|
||||
- claper-net
|
||||
|
||||
traefik:
|
||||
image: traefik
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
#- "--api.dashboard=true"
|
||||
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.web.address=:443"
|
||||
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.myresolver.acme.email=yourmail@example.com"
|
||||
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
||||
volumes:
|
||||
- "../letsencrypt:/letsencrypt"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "/var/log/traefik:/var/log/traefik/"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
- "443:443"
|
||||
networks:
|
||||
- claper-net
|
||||
|
||||
volumes:
|
||||
claper-db:
|
||||
driver: local
|
||||
claper-uploads:
|
||||
driver: local
|
||||
networks:
|
||||
claper-net:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## Behind Kubernetes
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
## Prerequisites
|
||||
|
||||
To run Claper on your local environment you need to have:
|
||||
* Postgres >= 9
|
||||
* Elixir >= 1.13.2
|
||||
* Erlang >= 24
|
||||
* NPM >= 6.14.17
|
||||
* NodeJS >= 14.19.2
|
||||
* Ghostscript >= 9.5.0 (for PDF support)
|
||||
* Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
- Postgres >= 9
|
||||
- Elixir >= 1.13.2
|
||||
- Erlang >= 24
|
||||
- NPM >= 6.14.17
|
||||
- NodeJS >= 14.19.2
|
||||
- Ghostscript >= 9.5.0 (for PDF support)
|
||||
- Libreoffice >= 6.4 (for PPT/PPTX support)
|
||||
|
||||
You can also use Docker to easily run a Postgres instance:
|
||||
|
||||
```sh
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:9
|
||||
```
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:15
|
||||
```
|
||||
|
||||
1. Clone the repo
|
||||
```sh
|
||||
@@ -45,7 +47,6 @@ Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
If you have configured `MAIL` to `local`, you can access to the mailbox at [`localhost:4000/dev/mailbox`](http://localhost:4000/dev/mailbox).
|
||||
|
||||
|
||||
## Using Docker Compose
|
||||
|
||||
A Docker Compose [reference file](https://github.com/ClaperCo/Claper/blob/main/docker-compose.yml) is provided in the repository. You can use it to run Claper with Docker Compose.
|
||||
@@ -56,17 +57,6 @@ cd Claper
|
||||
docker compose up
|
||||
```
|
||||
|
||||
## Using Docker Compose for Dev
|
||||
|
||||
To easy check new features, it is possible to directly build the Docker image from the source code and run the container with the [docker-compose-dev.yml](https://github.com/ClaperCo/Claper/blob/main/docker-compose-dev.yml) file.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ClaperCo/Claper.git
|
||||
cd Claper
|
||||
docker compose -f docker-compose-dev.yml up
|
||||
```
|
||||
|
||||
|
||||
### ARM architecture
|
||||
|
||||
If you are using an ARM architecture (like Apple M1), the original Docker image won't work. You can build the image yourself by replacing the `BUILDER_IMAGE` argument in the `Dockerfile` with `ARG BUILDER_IMAGE="hexpm/elixir-arm64:1.13.2-erlang-24.2.1-debian-bullseye-20210902-slim"` and then build the image as described above.
|
||||
|
||||
@@ -182,7 +182,7 @@ defmodule Claper.Accounts do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> deliver_magic_link(user, &Routes.user_confirmation_url(conn, :confirm_magic, &1))
|
||||
iex> deliver_magic_link(user, &url(~p"/users/magic/&1"))
|
||||
{:ok, %{to: ..., body: ...}}
|
||||
|
||||
"""
|
||||
@@ -423,4 +423,8 @@ defmodule Claper.Accounts do
|
||||
UserToken.user_magic_and_contexts_query(token.sent_to, ["magic"])
|
||||
)
|
||||
end
|
||||
|
||||
def delete(user) do
|
||||
Repo.delete(user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,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.expired_at])
|
||||
from(e in Event, where: e.user_id == ^user_id, order_by: [desc: e.inserted_at])
|
||||
|> Repo.all()
|
||||
|> Repo.preload(preload)
|
||||
end
|
||||
@@ -140,7 +140,7 @@ defmodule Claper.Events do
|
||||
def get_event_with_code!(code, preload \\ []) do
|
||||
now = NaiveDateTime.utc_now()
|
||||
|
||||
from(e in Event, where: e.code == ^code and e.expired_at > ^now)
|
||||
from(e in Event, where: e.code == ^code and (is_nil(e.expired_at) or e.expired_at > ^now))
|
||||
|> Repo.one!()
|
||||
|> Repo.preload(preload)
|
||||
end
|
||||
@@ -148,7 +148,7 @@ defmodule Claper.Events do
|
||||
def get_event_with_code(code, preload \\ []) do
|
||||
now = DateTime.utc_now()
|
||||
|
||||
from(e in Event, where: e.code == ^code and e.expired_at > ^now)
|
||||
from(e in Event, where: e.code == ^code and (is_nil(e.expired_at) or e.expired_at > ^now))
|
||||
|> Repo.one()
|
||||
|> Repo.preload(preload)
|
||||
end
|
||||
@@ -234,7 +234,7 @@ defmodule Claper.Events do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a event.
|
||||
Updates an event.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -258,6 +258,28 @@ defmodule Claper.Events do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates an event.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> terminate_event(event)
|
||||
{:ok, %Event{}}
|
||||
|
||||
"""
|
||||
def terminate_event(%Event{} = event) do
|
||||
event
|
||||
|> Event.update_changeset(%{expired_at: NaiveDateTime.utc_now()})
|
||||
|> Repo.update()
|
||||
|> case do
|
||||
{:ok, event} ->
|
||||
broadcast({:ok, event, event.uuid}, :event_terminated)
|
||||
|
||||
{:error, changeset} ->
|
||||
{:error, %{changeset | action: :update}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import interactions from another event
|
||||
|
||||
@@ -421,4 +443,16 @@ defmodule Claper.Events do
|
||||
def change_activity_leader(%ActivityLeader{} = activity_leader, attrs \\ %{}) do
|
||||
ActivityLeader.changeset(activity_leader, attrs)
|
||||
end
|
||||
|
||||
defp broadcast({:error, _reason} = error, _event), do: error
|
||||
|
||||
defp broadcast({:ok, e, event_uuid}, event) do
|
||||
Phoenix.PubSub.broadcast(
|
||||
Claper.PubSub,
|
||||
"event:#{event_uuid}",
|
||||
{event, event_uuid}
|
||||
)
|
||||
|
||||
{:ok, e}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,8 +10,6 @@ defmodule Claper.Events.Event do
|
||||
field :started_at, :naive_datetime
|
||||
field :expired_at, :naive_datetime
|
||||
|
||||
field :date_range, :string, virtual: true
|
||||
|
||||
has_many :posts, Claper.Posts.Post
|
||||
|
||||
has_many :leaders, Claper.Events.ActivityLeader, on_replace: :delete
|
||||
@@ -30,21 +28,19 @@ defmodule Claper.Events.Event do
|
||||
:code,
|
||||
:started_at,
|
||||
:expired_at,
|
||||
:date_range,
|
||||
:audience_peak
|
||||
])
|
||||
|> cast_assoc(:presentation_file)
|
||||
|> cast_assoc(:leaders)
|
||||
|> validate_required([:code])
|
||||
|> validate_date_range
|
||||
|> validate_required([:code, :name])
|
||||
end
|
||||
|
||||
def create_changeset(event, attrs) do
|
||||
event
|
||||
|> cast(attrs, [:name, :code, :user_id, :started_at, :expired_at, :date_range])
|
||||
|> cast(attrs, [:name, :code, :user_id, :started_at, :expired_at])
|
||||
|> cast_assoc(:presentation_file)
|
||||
|> cast_assoc(:leaders)
|
||||
|> validate_required([:code, :started_at, :expired_at])
|
||||
|> validate_required([:code, :started_at])
|
||||
|> downcase_code
|
||||
end
|
||||
|
||||
@@ -56,38 +52,12 @@ defmodule Claper.Events.Event do
|
||||
)
|
||||
end
|
||||
|
||||
defp validate_date_range(changeset) do
|
||||
date_range = get_change(changeset, :date_range)
|
||||
|
||||
if date_range != nil do
|
||||
splited = date_range |> String.split(" - ")
|
||||
|
||||
if splited |> Enum.count() == 2 do
|
||||
changeset
|
||||
|> put_change(:started_at, Enum.at(splited, 0))
|
||||
|> put_change(:expired_at, Enum.at(splited, 1))
|
||||
else
|
||||
add_error(changeset, :date_range, "invalid date range")
|
||||
end
|
||||
else
|
||||
start_date = get_change(changeset, :started_at)
|
||||
end_date = get_change(changeset, :expired_at)
|
||||
|
||||
if start_date != nil && end_date != nil do
|
||||
changeset
|
||||
|> put_change(:date_range, "#{start_date} - #{end_date}")
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_changeset(event, attrs) do
|
||||
event
|
||||
|> cast(attrs, [:name, :code, :started_at, :expired_at, :date_range, :audience_peak])
|
||||
|> cast(attrs, [:name, :code, :started_at, :expired_at, :audience_peak])
|
||||
|> cast_assoc(:presentation_file)
|
||||
|> cast_assoc(:leaders)
|
||||
|> validate_required([:code, :started_at, :expired_at])
|
||||
|> validate_required([:code, :started_at])
|
||||
|> downcase_code
|
||||
end
|
||||
|
||||
@@ -105,4 +75,8 @@ defmodule Claper.Events.Event do
|
||||
def started?(event) do
|
||||
NaiveDateTime.compare(NaiveDateTime.utc_now(), event.started_at) == :gt
|
||||
end
|
||||
|
||||
def finished?(event) do
|
||||
event.expired_at && NaiveDateTime.compare(NaiveDateTime.utc_now(), event.expired_at) == :gt
|
||||
end
|
||||
end
|
||||
|
||||
@@ -238,13 +238,14 @@ defmodule Claper.Forms do
|
||||
[%FormSubmit{}, ...]
|
||||
|
||||
"""
|
||||
def list_form_submits(presentation_file_id) do
|
||||
def list_form_submits(presentation_file_id, preload \\ []) do
|
||||
from(fs in FormSubmit,
|
||||
join: f in Form,
|
||||
on: f.id == fs.form_id,
|
||||
where: f.presentation_file_id == ^presentation_file_id
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Repo.preload(preload)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -9,6 +9,8 @@ defmodule Claper.Presentations.PresentationState do
|
||||
field :join_screen_visible, :boolean
|
||||
field :chat_enabled, :boolean
|
||||
field :anonymous_chat_enabled, :boolean
|
||||
field :message_reaction_enabled, :boolean, default: true
|
||||
field :show_poll_results_enabled, :boolean, default: true
|
||||
field :banned, {:array, :string}, default: []
|
||||
field :show_only_pinned, :boolean, default: false
|
||||
|
||||
@@ -29,7 +31,9 @@ defmodule Claper.Presentations.PresentationState do
|
||||
:presentation_file_id,
|
||||
:chat_enabled,
|
||||
:anonymous_chat_enabled,
|
||||
:show_only_pinned
|
||||
:show_only_pinned,
|
||||
:message_reaction_enabled,
|
||||
:show_poll_results_enabled
|
||||
])
|
||||
|> validate_required([])
|
||||
end
|
||||
|
||||
@@ -16,11 +16,14 @@ defmodule Claper.Stats do
|
||||
|
||||
def total_vote_count(presentation_file_id) do
|
||||
from(p in Claper.Polls.Poll,
|
||||
join: o in Claper.Polls.PollOpt,
|
||||
on: o.poll_id == p.id,
|
||||
join: pv in Claper.Polls.PollVote,
|
||||
on: pv.poll_id == p.id,
|
||||
where: p.presentation_file_id == ^presentation_file_id,
|
||||
group_by: o.poll_id,
|
||||
select: sum(o.vote_count)
|
||||
group_by: p.presentation_file_id,
|
||||
select:
|
||||
count(
|
||||
fragment("DISTINCT COALESCE(?, CAST(? AS varchar))", pv.attendee_identifier, pv.user_id)
|
||||
)
|
||||
)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@@ -43,8 +43,6 @@ defmodule Claper.Tasks.Converter do
|
||||
Remove the presentation files directory
|
||||
"""
|
||||
def clear(hash) do
|
||||
IO.puts("Clearing #{hash}...")
|
||||
|
||||
if get_presentation_storage() == "local" do
|
||||
File.rm_rf(
|
||||
Path.join([
|
||||
|
||||
@@ -17,13 +17,16 @@ defmodule ClaperWeb do
|
||||
and import those modules here.
|
||||
"""
|
||||
|
||||
def static_paths, do: ~w(assets fonts .well-known images favicon.ico robots.txt)
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller, namespace: ClaperWeb
|
||||
|
||||
import Plug.Conn
|
||||
import ClaperWeb.Gettext
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
unquote(verified_routes())
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,7 +74,9 @@ defmodule ClaperWeb do
|
||||
|
||||
def view_component do
|
||||
quote do
|
||||
use Phoenix.HTML
|
||||
import Phoenix.HTML
|
||||
import Phoenix.HTML.Form
|
||||
use PhoenixHTMLHelpers
|
||||
use Phoenix.Component
|
||||
import ClaperWeb.ErrorHelpers
|
||||
alias Phoenix.LiveView.JS
|
||||
@@ -89,7 +94,9 @@ defmodule ClaperWeb do
|
||||
defp view_helpers do
|
||||
quote do
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
import Phoenix.HTML
|
||||
import Phoenix.HTML.Form
|
||||
use PhoenixHTMLHelpers
|
||||
|
||||
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
|
||||
import Phoenix.LiveView.Helpers
|
||||
@@ -101,7 +108,17 @@ defmodule ClaperWeb do
|
||||
|
||||
import ClaperWeb.ErrorHelpers
|
||||
import ClaperWeb.Gettext
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
unquote(verified_routes())
|
||||
end
|
||||
end
|
||||
|
||||
def verified_routes do
|
||||
quote do
|
||||
use Phoenix.VerifiedRoutes,
|
||||
endpoint: ClaperWeb.Endpoint,
|
||||
router: ClaperWeb.Router,
|
||||
statics: ClaperWeb.static_paths()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -5,22 +5,22 @@ defmodule ClaperWeb.StatController do
|
||||
|
||||
def export(conn, %{"form_id" => form_id}) do
|
||||
form = Forms.get_form!(form_id, [:form_submits])
|
||||
csv_data = csv_content(form.form_submits |> Enum.map(& &1.response))
|
||||
headers = form.fields |> Enum.map(& &1.name)
|
||||
csv_data = headers |> csv_content(form.form_submits |> Enum.map(& &1.response))
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/csv")
|
||||
|> put_resp_header("content-disposition", "attachment; filename=\"export.csv\"")
|
||||
|> put_resp_header("content-disposition", "attachment; filename=\"#{form.title}.csv\"")
|
||||
|> put_root_layout(false)
|
||||
|> send_resp(200, csv_data)
|
||||
end
|
||||
|
||||
defp csv_content(records) do
|
||||
records
|
||||
|> Enum.map(fn record ->
|
||||
record
|
||||
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
|
||||
|> Map.values()
|
||||
end)
|
||||
defp csv_content(headers, records) do
|
||||
data =
|
||||
records
|
||||
|> Enum.map(&(&1 |> Map.values()))
|
||||
|
||||
([headers] ++ data)
|
||||
|> CSV.encode()
|
||||
|> Enum.to_list()
|
||||
|> to_string()
|
||||
|
||||
@@ -2,12 +2,12 @@ defmodule ClaperWeb.UserAuth do
|
||||
@moduledoc """
|
||||
Plug for user authentication.
|
||||
"""
|
||||
use ClaperWeb, :controller
|
||||
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
|
||||
alias Claper.Accounts
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
# Make the remember me cookie valid for 60 days.
|
||||
# If you want bump or reduce this value, also change
|
||||
@@ -137,13 +137,13 @@ defmodule ClaperWeb.UserAuth do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
# |> redirect(to: Routes.user_registration_path(conn, :confirm))
|
||||
# |> redirect(to: ~p"/users/register/confirm")
|
||||
end
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "You must log in to access this page.")
|
||||
|> maybe_store_return_to()
|
||||
|> redirect(to: Routes.user_session_path(conn, :new))
|
||||
|> redirect(to: ~p"/users/log_in")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule ClaperWeb.UserConfirmationController do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
user,
|
||||
&Routes.user_confirmation_url(conn, :update, &1)
|
||||
&url(~p"/users/confirm/#{&1}")
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule ClaperWeb.UserRegistrationController do
|
||||
# {:ok, _} =
|
||||
# Accounts.deliver_user_confirmation_instructions(
|
||||
# user,
|
||||
# &Routes.user_confirmation_url(conn, :update, &1)
|
||||
# &url(~p"/users/confirm/#{&1}")
|
||||
# )
|
||||
|
||||
conn
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule ClaperWeb.UserResetPasswordController do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_reset_password_instructions(
|
||||
user,
|
||||
&Routes.user_reset_password_url(conn, :edit, &1)
|
||||
&url(~p"/users/reset_password/#{&1}")
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ defmodule ClaperWeb.UserSessionController do
|
||||
end
|
||||
|
||||
# def create(conn, %{"user" => %{"email" => email}} = _user_params) do
|
||||
# Accounts.deliver_magic_link(email, &Routes.user_confirmation_url(conn, :confirm_magic, &1))
|
||||
# Accounts.deliver_magic_link(email, &url(~p"/users/magic/#{&1}"))
|
||||
|
||||
# conn
|
||||
# |> redirect(to: Routes.user_registration_path(conn, :confirm, %{email: email}))
|
||||
# |> redirect(to: ~p"/users/register/confirm?#{[%{email: email}]}")
|
||||
# end
|
||||
def create(conn, %{"user" => user_params}) do
|
||||
%{"email" => email, "password" => password} = user_params
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule ClaperWeb.UserSettingsController do
|
||||
Accounts.deliver_update_email_instructions(
|
||||
applied_user,
|
||||
user.email,
|
||||
&Routes.user_settings_url(conn, :confirm_email, &1)
|
||||
&url(~p"/users/settings/confirm_email/#{&1}")
|
||||
)
|
||||
|
||||
conn
|
||||
@@ -26,7 +26,7 @@ defmodule ClaperWeb.UserSettingsController do
|
||||
:info,
|
||||
"A link to confirm your email change has been sent to the new address."
|
||||
)
|
||||
|> redirect(to: Routes.user_settings_show_path(conn, :show))
|
||||
|> redirect(to: ~p"/users/settings")
|
||||
|
||||
{:error, changeset} ->
|
||||
render(conn, "edit.html", email_changeset: changeset)
|
||||
@@ -38,12 +38,12 @@ defmodule ClaperWeb.UserSettingsController do
|
||||
:ok ->
|
||||
conn
|
||||
|> put_flash(:info, "Email changed successfully.")
|
||||
|> redirect(to: Routes.user_settings_show_path(conn, :show))
|
||||
|> redirect(to: ~p"/users/settings")
|
||||
|
||||
:error ->
|
||||
conn
|
||||
|> put_flash(:error, "Email change link is invalid or it has expired.")
|
||||
|> redirect(to: Routes.user_settings_show_path(conn, :show))
|
||||
|> redirect(to: ~p"/users/settings")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,11 +4,23 @@ defmodule ClaperWeb.Endpoint do
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
@session_options [
|
||||
store: :cookie,
|
||||
key: "_claper_key",
|
||||
signing_salt: "Tg18Y2zU"
|
||||
]
|
||||
@session_options (case Mix.env() do
|
||||
:dev ->
|
||||
[
|
||||
store: :cookie,
|
||||
key: "_claper_key",
|
||||
signing_salt: "Tg18Y2zU",
|
||||
same_site: "None",
|
||||
secure: true
|
||||
]
|
||||
|
||||
_ ->
|
||||
[
|
||||
store: :cookie,
|
||||
key: "_claper_key",
|
||||
signing_salt: "Tg18Y2zU"
|
||||
]
|
||||
end)
|
||||
|
||||
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
||||
|
||||
@@ -20,8 +32,7 @@ defmodule ClaperWeb.Endpoint do
|
||||
at: "/",
|
||||
from: :claper,
|
||||
gzip: false,
|
||||
only:
|
||||
~w(assets fonts .well-known images favicon.ico robots.txt loaderio-eb3b956a176cdd4f54eb8570ce8bbb06.txt)
|
||||
only: ClaperWeb.static_paths()
|
||||
|
||||
plug Plug.Static,
|
||||
at: "/uploads",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
use ClaperWeb, :live_component
|
||||
|
||||
alias Claper.Events.Event
|
||||
|
||||
def render(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
@@ -15,17 +17,19 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
<%= @event.name %>
|
||||
</p>
|
||||
<div class="ml-2 flex-shrink-0 flex">
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.started_at) == :gt and NaiveDateTime.compare(@current_time, @event.expired_at) == :lt do %>
|
||||
<p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
|
||||
<%= gettext("In progress") %>
|
||||
</p>
|
||||
<%= if Event.started?(@event) && !Event.finished?(@event) do %>
|
||||
<div class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-500 text-white items-center gap-x-1">
|
||||
<span class="h-2 w-2 bg-white rounded-full animate__animated animate__flash animate__infinite animate__slow_slow">
|
||||
</span>
|
||||
<%= gettext("Live") %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.started_at) == :lt do %>
|
||||
<%= if !Event.started?(@event) && !Event.finished?(@event) do %>
|
||||
<p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
<%= gettext("Incoming") %>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.expired_at) == :gt do %>
|
||||
<%= if Event.finished?(@event) do %>
|
||||
<p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
|
||||
<%= gettext("Finished") %>
|
||||
</p>
|
||||
@@ -44,25 +48,22 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
class="flex items-center text-sm text-gray-500 space-x-1"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<img src="/images/icons/calendar-clear-outline.svg" class="h-5 w-5" />
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.started_at) == :gt and NaiveDateTime.compare(@current_time, @event.expired_at) == :lt do %>
|
||||
<p>
|
||||
<%= gettext("Finish on") %>
|
||||
<span x-text={"moment.utc('#{@event.expired_at}').local().format('lll')"}></span>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.started_at) == :lt do %>
|
||||
<p>
|
||||
<%= gettext("Starting on") %>
|
||||
<span x-text={"moment.utc('#{@event.started_at}').local().format('lll')"}></span>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.expired_at) == :gt do %>
|
||||
<p>
|
||||
<%= gettext("Finished on") %>
|
||||
<span x-text={"moment.utc('#{@event.expired_at}').local().format('lll')"}></span>
|
||||
</p>
|
||||
<% end %>
|
||||
<img
|
||||
:if={
|
||||
Event.finished?(@event) ||
|
||||
!Event.started?(@event)
|
||||
}
|
||||
src="/images/icons/calendar-clear-outline.svg"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<p :if={!Event.finished?(@event) && !Event.started?(@event)}>
|
||||
<%= gettext("Starting on") %>
|
||||
<span x-text={"moment.utc('#{@event.started_at}').local().format('lll')"}></span>
|
||||
</p>
|
||||
<p :if={Event.finished?(@event)}>
|
||||
<%= gettext("Finished on") %>
|
||||
<span x-text={"moment.utc('#{@event.expired_at}').local().format('lll')"}></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,122 +73,207 @@ defmodule ClaperWeb.EventLive.EventCardComponent do
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.expired_at) == :lt do %>
|
||||
<%= if @event.presentation_file.status == "done" || (@event.presentation_file.status == "fail" && @event.presentation_file.hash) do %>
|
||||
<%= if !Event.finished?(@event) do %>
|
||||
<%= if @event.presentation_file.status != "progress" do %>
|
||||
<div class="mt-2 flex flex-col space-y-2 sm:space-y-0 justify-between sm:flex-row items-center">
|
||||
<div
|
||||
id={"event-infos-0-#{@event.uuid}"}
|
||||
class="text-sm w-full space-y-2 sm:w-auto font-medium text-gray-700 sm:flex sm:justify-center sm:space-x-1 sm:space-y-0 sm:items-center"
|
||||
phx-update="ignore"
|
||||
id={"event-infos-#{@event.uuid}"}
|
||||
class="text-sm w-full sm:w-auto font-medium text-gray-700 flex justify-center space-x-1 sm:space-y-0 items-center relative"
|
||||
>
|
||||
<button
|
||||
phx-click-away={JS.hide(to: "#dropdown-#{@event.uuid}")}
|
||||
phx-click={JS.toggle(to: "#dropdown-#{@event.uuid}")}
|
||||
phx-target={@myself}
|
||||
class="flex w-full lg:w-auto pl-3 pr-4 text-white items-center justify-between py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline hover:bg-primary-600 bg-primary-500"
|
||||
>
|
||||
<span class="mr-2"><%= gettext("Access") %></span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
phx-hook="Dropdown"
|
||||
id={"dropdown-#{@event.uuid}"}
|
||||
class="hidden rounded shadow-lg bg-white border px-2 py-1 absolute -left-1 top-9 w-max"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
class="py-2 px-2 rounded text-gray-600 hover:bg-gray-100 flex items-center gap-x-2"
|
||||
href={~p"/e/#{@event.code}/manage"}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 2.25a.75.75 0 0 0 0 1.5H3v10.5a3 3 0 0 0 3 3h1.21l-1.172 3.513a.75.75 0 0 0 1.424.474l.329-.987h8.418l.33.987a.75.75 0 0 0 1.422-.474l-1.17-3.513H18a3 3 0 0 0 3-3V3.75h.75a.75.75 0 0 0 0-1.5H2.25Zm6.04 16.5.5-1.5h6.42l.5 1.5H8.29Zm7.46-12a.75.75 0 0 0-1.5 0v6a.75.75 0 0 0 1.5 0v-6Zm-3 2.25a.75.75 0 0 0-1.5 0v3.75a.75.75 0 0 0 1.5 0V9Zm-3 2.25a.75.75 0 0 0-1.5 0v1.5a.75.75 0 0 0 1.5 0v-1.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span><%= gettext("Presentation manager") %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
class="py-2 px-2 rounded text-gray-600 hover:bg-gray-100 flex items-center gap-x-2"
|
||||
href={~p"/e/#{@event.code}"}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.25 6.75a3.75 3.75 0 1 1 7.5 0 3.75 3.75 0 0 1-7.5 0ZM15.75 9.75a3 3 0 1 1 6 0 3 3 0 0 1-6 0ZM2.25 9.75a3 3 0 1 1 6 0 3 3 0 0 1-6 0ZM6.31 15.117A6.745 6.745 0 0 1 12 12a6.745 6.745 0 0 1 6.709 7.498.75.75 0 0 1-.372.568A12.696 12.696 0 0 1 12 21.75c-2.305 0-4.47-.612-6.337-1.684a.75.75 0 0 1-.372-.568 6.787 6.787 0 0 1 1.019-4.38Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path d="M5.082 14.254a8.287 8.287 0 0 0-1.308 5.135 9.687 9.687 0 0 1-1.764-.44l-.115-.04a.563.563 0 0 1-.373-.487l-.01-.121a3.75 3.75 0 0 1 3.57-4.047ZM20.226 19.389a8.287 8.287 0 0 0-1.308-5.135 3.75 3.75 0 0 1 3.57 4.047l-.01.121a.563.563 0 0 1-.373.486l-.115.04c-.567.2-1.156.349-1.764.441Z" />
|
||||
</svg>
|
||||
<span><%= gettext("Attendees room") %></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<.link
|
||||
:if={Event.started?(@event)}
|
||||
data-confirm={
|
||||
gettext(
|
||||
"Are you sure you want to terminate this event? This action cannot be undone."
|
||||
)
|
||||
}
|
||||
phx-value-id={@event.uuid}
|
||||
phx-click="terminate"
|
||||
class="flex w-full lg:w-auto pl-3 pr-4 text-white items-center justify-between py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline bg-red-500 hover:bg-red-600 transition"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5 mr-2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<span><%= gettext("Terminate") %></span>
|
||||
</.link>
|
||||
</div>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={~p"/events/#{@event.uuid}/edit"}
|
||||
class="flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-primary-500 text-sm items-center"
|
||||
>
|
||||
<span><%= gettext("Edit") %></span>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div
|
||||
:if={@event.presentation_file.status == "fail" && is_nil(@event.presentation_file.hash)}
|
||||
class="mt-2 flex flex-col space-y-2 sm:space-y-0 justify-between sm:flex-row items-center"
|
||||
>
|
||||
<span class="text-sm text-supporting-red-500">
|
||||
<%= gettext("Error when processing the file") %>
|
||||
</span>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={Routes.event_manage_path(@socket, :show, @event.code)}
|
||||
class="flex w-full lg:w-auto px-6 text-white py-2 justify-center rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline hover:bg-primary-600 bg-primary-500 space-x-2"
|
||||
href={~p"/events/#{@event.uuid}/edit"}
|
||||
class="flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-primary-500 text-sm items-center"
|
||||
>
|
||||
<img src="/images/icons/easel.svg" class="h-5" />
|
||||
<span><%= gettext("Present/Customize") %></span>
|
||||
<span><%= gettext("Edit") %></span>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
href={Routes.event_show_path(@socket, :show, @event.code)}
|
||||
class="flex w-full lg:w-auto px-6 text-primary-500 py-2 justify-center rounded-md tracking-wide focus:outline-none focus:shadow-outline bg-white items-center space-x-2"
|
||||
>
|
||||
<img src="/images/icons/eye.svg" class="h-5" />
|
||||
<span><%= gettext("Join") %></span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={Routes.event_index_path(@socket, :edit, @event.uuid)}
|
||||
class="flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-primary-500 text-sm items-center"
|
||||
>
|
||||
<span><%= gettext("Edit") %></span>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if @event.presentation_file.status == "fail" && is_nil(@event.presentation_file.hash) do %>
|
||||
<div class="mt-2 flex flex-col space-y-2 sm:space-y-0 justify-between sm:flex-row items-center">
|
||||
<span class="text-sm text-supporting-red-500">
|
||||
<%= gettext("Error when processing the file") %>
|
||||
</span>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={Routes.event_index_path(@socket, :edit, @event.uuid)}
|
||||
class="flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-primary-500 text-sm items-center"
|
||||
>
|
||||
<span><%= gettext("Edit") %></span>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if @event.presentation_file.status == "progress" do %>
|
||||
<div class="flex space-x-1 items-center">
|
||||
<img src="/images/loading.gif" class="h-8" />
|
||||
<span class="text-sm text-gray-500"><%= gettext("Processing your file...") %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= if NaiveDateTime.compare(@current_time, @event.expired_at) == :gt do %>
|
||||
<div class="mt-2 flex flex-col space-y-2 sm:space-y-0 justify-between sm:flex-row items-center">
|
||||
<div
|
||||
id={"event-infos-1-#{@event.uuid}"}
|
||||
class="text-sm w-full space-y-2 sm:w-auto font-medium text-gray-700 sm:flex sm:justify-center sm:space-x-1 sm:space-y-0 sm:items-center"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={Routes.stat_index_path(@socket, :index, @event.uuid)}
|
||||
class="flex w-full lg:w-auto px-6 text-white py-2 justify-center rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline hover:bg-primary-600 bg-primary-500 space-x-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
|
||||
<path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
|
||||
</svg>
|
||||
<span><%= gettext("Report") %></span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<%= link(gettext("Delete"),
|
||||
to: "#",
|
||||
phx_click: "delete",
|
||||
phx_value_id: @event.uuid,
|
||||
data: [
|
||||
confirm:
|
||||
gettext(
|
||||
"This will delete all data related to your event, this cannot be undone. Confirm ?"
|
||||
)
|
||||
],
|
||||
class:
|
||||
"flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-red-500 text-sm items-center"
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:if={@event.presentation_file.status == "progress"}
|
||||
class="flex space-x-1 items-center"
|
||||
>
|
||||
<img src="/images/loading.gif" class="h-8" />
|
||||
<span class="text-sm text-gray-500"><%= gettext("Processing your file...") %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div
|
||||
:if={Event.finished?(@event)}
|
||||
class="mt-2 flex flex-col space-y-2 sm:space-y-0 justify-between sm:flex-row items-center"
|
||||
>
|
||||
<div
|
||||
id={"event-infos-1-#{@event.uuid}"}
|
||||
class="text-sm w-full space-y-2 sm:w-auto font-medium text-gray-700 sm:flex sm:justify-center sm:space-x-1 sm:space-y-0 sm:items-center"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<a
|
||||
href={~p"/events/#{@event.uuid}/stats"}
|
||||
class="flex w-full lg:w-auto px-3 text-white py-2 justify-center rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline hover:bg-primary-600 bg-primary-500 space-x-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
|
||||
<path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
|
||||
</svg>
|
||||
<span><%= gettext("View report") %></span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<%= if not @is_leader do %>
|
||||
<%= link(gettext("Delete"),
|
||||
to: "#",
|
||||
phx_click: "delete",
|
||||
phx_value_id: @event.uuid,
|
||||
data: [
|
||||
confirm:
|
||||
gettext(
|
||||
"This will delete all data related to your event, this cannot be undone. Confirm ?"
|
||||
)
|
||||
],
|
||||
class:
|
||||
"flex w-full lg:w-auto rounded-md tracking-wide focus:outline-none focus:shadow-outline text-red-500 text-sm items-center"
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("open", _params, socket) do
|
||||
{:noreply, socket |> assign(:dropdown, true)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
defmodule ClaperWeb.EventLive.EventFormComponent do
|
||||
alias Claper.Presentations.PresentationFile
|
||||
use ClaperWeb, :live_component
|
||||
|
||||
alias Claper.Events
|
||||
@@ -118,7 +119,7 @@ defmodule ClaperWeb.EventLive.EventFormComponent do
|
||||
|
||||
File.cp!(path, dest)
|
||||
|
||||
{:ok, Routes.static_path(socket, "/uploads/#{hash}/#{Path.basename(dest)}")}
|
||||
{:ok, "/uploads/#{hash}/#{Path.basename(dest)}"}
|
||||
end)
|
||||
|
||||
[ext | _] = MIME.extensions(MIME.from_path(dest))
|
||||
@@ -151,10 +152,53 @@ defmodule ClaperWeb.EventLive.EventFormComponent do
|
||||
save_file(socket, event_params, &edit_event/4)
|
||||
end
|
||||
|
||||
defp save_event(socket, :new, event_params) do
|
||||
defp save_event(
|
||||
%{assigns: %{event: %{:presentation_file => %PresentationFile{}}}} = socket,
|
||||
:new,
|
||||
event_params
|
||||
) do
|
||||
save_file(socket, event_params, &create_event/4)
|
||||
end
|
||||
|
||||
defp save_event(
|
||||
%{assigns: %{event: %{:presentation_file => %Ecto.Association.NotLoaded{}}}} = socket,
|
||||
:new,
|
||||
event_params
|
||||
) do
|
||||
create_event(socket, event_params)
|
||||
end
|
||||
|
||||
defp create_event(socket, event_params) do
|
||||
case Events.create_event(
|
||||
event_params
|
||||
|> Map.put("user_id", socket.assigns.current_user.id)
|
||||
|> Map.put("presentation_file", %{
|
||||
"status" => "done",
|
||||
"length" => 0,
|
||||
"presentation_state" => %{}
|
||||
})
|
||||
) do
|
||||
{:ok, event} ->
|
||||
with e <- Events.get_event!(event.uuid, [:leaders]) do
|
||||
Enum.each(e.leaders, fn leader ->
|
||||
Claper.Accounts.LeaderNotifier.deliver_event_invitation(
|
||||
e.name,
|
||||
leader.email,
|
||||
url(~p"/events")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Created successfully"))
|
||||
|> push_redirect(to: socket.assigns.return_to)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
defp create_event(socket, event_params, hash, ext) do
|
||||
case Events.create_event(
|
||||
event_params
|
||||
@@ -176,7 +220,7 @@ defmodule ClaperWeb.EventLive.EventFormComponent do
|
||||
Claper.Accounts.LeaderNotifier.deliver_event_invitation(
|
||||
e.name,
|
||||
leader.email,
|
||||
Routes.event_index_url(socket, :index)
|
||||
url(~p"/events")
|
||||
)
|
||||
end)
|
||||
end
|
||||
@@ -246,7 +290,7 @@ defmodule ClaperWeb.EventLive.EventFormComponent do
|
||||
Claper.Accounts.LeaderNotifier.deliver_event_invitation(
|
||||
e.name,
|
||||
leader.email,
|
||||
Routes.event_index_url(socket, :index)
|
||||
url(~p"/events")
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<div>
|
||||
<div
|
||||
id="wrapper"
|
||||
phx-hook="TourGuide"
|
||||
data-next-label={gettext("Next")}
|
||||
data-prev-label={gettext("Back")}
|
||||
data-finish-label={gettext("Finish")}
|
||||
data-group="create-event"
|
||||
>
|
||||
<div class="border-b border-gray-200 py-4 flex flex-col sm:flex-row sm:items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-2xl font-medium leading-6 text-gray-900 sm:truncate">
|
||||
@@ -6,7 +13,7 @@
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex mt-4 space-x-5 sm:mt-0">
|
||||
<%= if @uploads.presentation_file.entries |> Enum.at(0, %{}) |> Map.get(:progress, 0) >= 100 || Map.has_key?(@event.presentation_file, :id) do %>
|
||||
<%= if (@uploads.presentation_file.entries |> Enum.at(0, %{}) |> Map.get(:progress, 0) == 0 || @uploads.presentation_file.entries |> Enum.at(0, %{}) |> Map.get(:progress, 0) == 100) && @changeset.valid? do %>
|
||||
<button
|
||||
type="submit"
|
||||
form="event-form"
|
||||
@@ -26,7 +33,7 @@
|
||||
end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= if @action == :edit && NaiveDateTime.compare(NaiveDateTime.utc_now(), @event.expired_at) == :lt do %>
|
||||
<%= if @action == :edit && !@event.expired_at do %>
|
||||
<%= link(gettext("Delete"),
|
||||
to: "#",
|
||||
phx_click: "delete",
|
||||
@@ -39,10 +46,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= if Map.get(@event, :presentation_file) == nil || Map.get(@event.presentation_file, :id) == nil do %>
|
||||
<div class="mt-12 mb-3">
|
||||
<%= if Map.get(@event, :presentation_file) == nil || Map.get(@event.presentation_file, :length) == 0 || Map.get(@event.presentation_file, :id) == nil do %>
|
||||
<div
|
||||
class="mb-3 mt-12"
|
||||
data-tg-group="create-event"
|
||||
data-tg-order="1"
|
||||
data-tg-tour={"<p class='mb-3'>#{gettext("Select your presentation file. Accepted formats are PDF, PPT, or PPTX. Ensure the file size does not exceed the maximum limit.")}</p><p class='opacity-50 text-xs'>#{gettext("Animations in PPT/PPTX files are not supported, which is why we recommend exporting your presentation to PDF to ensure it displays correctly.")}</p>"}
|
||||
data-tg-title={"📄 #{gettext("Presentation file (optional)")}"}
|
||||
>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<%= gettext("Select your presentation") %>
|
||||
<%= gettext("Select your presentation (optional)") %>
|
||||
</label>
|
||||
<div class="max-w-lg flex flex-col justify-center items-center px-6 pt-5 pb-6 border-2 bg-white shadow-base border-gray-300 border-dashed rounded-md">
|
||||
<%= if @uploads.presentation_file.entries |> Enum.at(0, %{}) |> Map.get(:progress, 0) < 100 do %>
|
||||
@@ -70,7 +83,7 @@
|
||||
phx-target={@myself}
|
||||
>
|
||||
<span><%= gettext("Upload a file") %></span>
|
||||
<%= live_file_input(@uploads.presentation_file, class: "sr-only") %>
|
||||
<.live_file_input upload={@uploads.presentation_file} class="sr-only" />
|
||||
</form>
|
||||
</label>
|
||||
<p class="pl-1"><%= gettext("or drag and drop") %></p>
|
||||
@@ -157,7 +170,7 @@
|
||||
phx-target={@myself}
|
||||
>
|
||||
<span><%= gettext("Change file") %></span>
|
||||
<%= live_file_input(@uploads.presentation_file, class: "sr-only") %>
|
||||
<.live_file_input upload={@uploads.presentation_file} class="sr-only" />
|
||||
</form>
|
||||
</label>
|
||||
<p class="text-supporting-red-500 text-sm italic text-center hidden">
|
||||
@@ -225,15 +238,20 @@
|
||||
<ClaperWeb.Component.Input.text
|
||||
form={f}
|
||||
key={:name}
|
||||
name={gettext("Name of your presentation")}
|
||||
name={gettext("Name of your event")}
|
||||
autofocus="true"
|
||||
required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<div
|
||||
class="my-3"
|
||||
data-tg-title={"🔑 #{gettext("Access code")}"}
|
||||
data-tg-tour={"<p>#{gettext("This code will be used by your attendees to access the event. You have the option to create a custom code.")}</p>"}
|
||||
data-tg-group="create-event"
|
||||
data-tg-order="2"
|
||||
>
|
||||
<ClaperWeb.Component.Input.code
|
||||
readonly={NaiveDateTime.compare(NaiveDateTime.utc_now(), @event.expired_at) == :gt}
|
||||
form={f}
|
||||
key={:code}
|
||||
name={gettext("Code")}
|
||||
@@ -241,15 +259,20 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<ClaperWeb.Component.Input.date_range
|
||||
readonly={NaiveDateTime.compare(NaiveDateTime.utc_now(), @event.expired_at) == :gt}
|
||||
<div
|
||||
class="my-3"
|
||||
data-tg-title={"🗓️ #{gettext("Event start date")}"}
|
||||
data-tg-tour={"<p class='mb-3'>#{gettext("Select the start date for your event. Future dates are permissible.")}</p><p class='opacity-50 text-xs'>#{gettext("Attendees attempting to access the event prior to this date will be directed to a waiting room.")}</p>"}
|
||||
data-tg-group="create-event"
|
||||
data-tg-order="3"
|
||||
phx-update="ignore"
|
||||
id="date-picker"
|
||||
>
|
||||
<ClaperWeb.Component.Input.date
|
||||
form={f}
|
||||
key={:date_range}
|
||||
name={gettext("When your presentation will be available ?")}
|
||||
key={:started_at}
|
||||
name={gettext("When your event will start?")}
|
||||
required="true"
|
||||
start_date_field={:started_at}
|
||||
end_date_field={:expired_at}
|
||||
from={Date.add(Date.utc_today(), -1)}
|
||||
to={Date.add(Date.utc_today(), 365)}
|
||||
/>
|
||||
@@ -270,7 +293,13 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="mt-20 mb-3">
|
||||
<div
|
||||
class="mt-7 mb-3"
|
||||
data-tg-title={"🧑💻 #{gettext("Facilitators")}"}
|
||||
data-tg-tour={"<p class='mb-3'>#{gettext("If you require assistance in managing your event, you can grant access to others. Simply enter their email addresses; once they register an account with these emails, they will be able to manage the event.")}</p><p class='opacity-50 text-xs'>#{gettext("Note: Facilitators do not have the ability to delete your event.")}</p>"}
|
||||
data-tg-group="create-event"
|
||||
data-tg-order="4"
|
||||
>
|
||||
<span class="text-lg block font-medium text-gray-700">
|
||||
<%= gettext("Facilitators can present and manage interactions") %>
|
||||
</span>
|
||||
@@ -278,7 +307,7 @@
|
||||
type="button"
|
||||
phx-click="add-leader"
|
||||
phx-target={@myself}
|
||||
class="rounded-md bg-primary-500 hover:bg-primary-600 transition flex items-center mt-3 md:w-max text-white py-7 px-3 text-sm max-h-0"
|
||||
class="rounded-md bg-primary-500 hover:bg-primary-600 transition flex items-center mt-3 md:w-max text-white py-5 px-3 text-sm max-h-0"
|
||||
>
|
||||
<svg
|
||||
class="text-white h-6 transform"
|
||||
@@ -292,8 +321,8 @@
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<span><%= gettext("Add facilitator") %></span>
|
||||
</svg>
|
||||
<span><%= gettext("Add facilitator") %></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,16 +12,24 @@ defmodule ClaperWeb.EventLive.Index do
|
||||
Gettext.put_locale(ClaperWeb.Gettext, locale)
|
||||
end
|
||||
|
||||
changeset =
|
||||
Events.change_event(%Event{}, %{
|
||||
started_at: NaiveDateTime.utc_now(),
|
||||
code: Enum.random(1000..9999),
|
||||
leaders: []
|
||||
})
|
||||
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Claper.PubSub, "events:#{socket.assigns.current_user.id}")
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:events, list_events(socket))
|
||||
|> stream(:events, list_events(socket))
|
||||
|> assign(:managed_events, list_managed_events(socket))
|
||||
|> assign(:quick_event_changeset, changeset)
|
||||
|
||||
{:ok, socket, temporary_assigns: [events: []]}
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -33,37 +41,104 @@ defmodule ClaperWeb.EventLive.Index do
|
||||
def handle_info({:presentation_file_process_done, presentation}, socket) do
|
||||
event = Claper.Events.get_event!(presentation.event.uuid, [:presentation_file])
|
||||
|
||||
{:noreply,
|
||||
socket |> update(:events, fn events -> [event | events] end) |> put_flash(:info, nil)}
|
||||
{:noreply, socket |> stream_insert(:events, event) |> put_flash(:info, nil)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
event = Events.get_event!(id, [:presentation_file])
|
||||
def handle_event("validate", %{"event" => event_params}, socket) do
|
||||
changeset =
|
||||
%Event{}
|
||||
|> Claper.Events.change_event(event_params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
{:noreply, socket |> assign(:quick_event_changeset, changeset)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("save", %{"event" => event_params}, socket) do
|
||||
case Claper.Events.create_event(
|
||||
event_params
|
||||
|> Map.put("user_id", socket.assigns.current_user.id)
|
||||
|> Map.put("presentation_file", %{
|
||||
"status" => "done",
|
||||
"length" => 0,
|
||||
"presentation_state" => %{}
|
||||
})
|
||||
|> Map.put("started_at", NaiveDateTime.utc_now())
|
||||
|> Map.put("code", "#{Enum.random(1000..9999)}")
|
||||
) do
|
||||
{:ok, _event} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Quick event created successfully"))
|
||||
|> push_redirect(to: ~p"/events")}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, quick_event_changeset: changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
|
||||
event = Events.get_user_event!(current_user.id, id, [:presentation_file])
|
||||
{:ok, _} = Events.delete_event(event)
|
||||
|
||||
Task.Supervisor.async_nolink(Claper.TaskSupervisor, fn ->
|
||||
Claper.Tasks.Converter.clear(event.presentation_file.hash)
|
||||
end)
|
||||
|
||||
{:noreply, redirect(socket, to: Routes.event_index_path(socket, :index))}
|
||||
{:noreply, redirect(socket, to: ~p"/events")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"checked",
|
||||
%{"key" => "no_file", "value" => value},
|
||||
%{assigns: %{event: event}} = socket
|
||||
) do
|
||||
{:noreply, socket |> assign(:event, %{event | no_file: value})}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("terminate", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
|
||||
event = Events.get_user_event!(current_user.id, id)
|
||||
{:ok, _} = Events.terminate_event(event)
|
||||
{:noreply, redirect(socket, to: ~p"/events")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"toggle-quick-create",
|
||||
_params,
|
||||
%{assigns: %{:live_action => :quick_create}} = socket
|
||||
) do
|
||||
{:noreply, assign(socket, :live_action, :index)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle-quick-create", _params, %{assigns: %{:live_action => :index}} = socket) do
|
||||
{:noreply, assign(socket, :live_action, :quick_create)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
event =
|
||||
Events.get_user_event!(socket.assigns.current_user.id, id, [:presentation_file, :leaders])
|
||||
|
||||
if event.presentation_file.status == "fail" && event.presentation_file.hash do
|
||||
Claper.Presentations.update_presentation_file(event.presentation_file, %{
|
||||
"status" => "done"
|
||||
})
|
||||
if event.expired_at && NaiveDateTime.compare(NaiveDateTime.utc_now(), event.expired_at) == :gt do
|
||||
redirect(socket, to: ~p"/events")
|
||||
else
|
||||
if event.presentation_file.status == "fail" && event.presentation_file.hash do
|
||||
Claper.Presentations.update_presentation_file(event.presentation_file, %{
|
||||
"status" => "done"
|
||||
})
|
||||
end
|
||||
|
||||
{:ok, socket |> assign(:event, event)}
|
||||
|
||||
socket
|
||||
|> assign(:page_title, gettext("Edit"))
|
||||
|> assign(:event, event)
|
||||
end
|
||||
|
||||
{:ok, socket |> assign(:event, event)}
|
||||
|
||||
socket
|
||||
|> assign(:page_title, gettext("Edit"))
|
||||
|> assign(:event, event)
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
@@ -71,7 +146,6 @@ defmodule ClaperWeb.EventLive.Index do
|
||||
|> assign(:page_title, gettext("Create"))
|
||||
|> assign(:event, %Event{
|
||||
started_at: NaiveDateTime.utc_now(),
|
||||
expired_at: NaiveDateTime.utc_now() |> NaiveDateTime.add(3600 * 2, :second),
|
||||
code: Enum.random(1000..9999),
|
||||
leaders: []
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="mx-3 max-w-7xl sm:mx-auto">
|
||||
<div class="mx-3 md:max-w-3xl lg:max-w-5xl md:mx-auto">
|
||||
<%= if @live_action in [:new, :edit] do %>
|
||||
<.live_component
|
||||
module={ClaperWeb.EventLive.EventFormComponent}
|
||||
@@ -6,21 +6,117 @@
|
||||
event={@event}
|
||||
page_title={@page_title}
|
||||
action={@live_action}
|
||||
return_to={Routes.event_index_path(@socket, :index)}
|
||||
return_to={~p"/events"}
|
||||
current_user={@current_user}
|
||||
/>
|
||||
<% else %>
|
||||
<div class="border-b border-gray-200 py-4 flex items-center justify-between">
|
||||
<div
|
||||
id="quick-create-modal"
|
||||
class={"#{if @live_action != :quick_create, do: 'hidden' } fixed z-30 inset-0 overflow-y-auto p-4 sm:p-6 md:p-24
|
||||
transform transition-all duration-150"}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div
|
||||
phx-click="toggle-quick-create"
|
||||
class="fixed inset-0 bg-gray-800 bg-opacity-75 transition-opacity w-full h-full"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</div>
|
||||
<div class="mx-auto max-w-xl transform divide-y divide-gray-100 overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
|
||||
<button phx-click="toggle-quick-create" class="absolute right-0 top-0">
|
||||
<svg
|
||||
class="text-gray-500 h-9 transform rotate-45"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id="modal-content" class="bg-gray-100 pt-7 pb-5 px-3">
|
||||
<.form
|
||||
:let={f}
|
||||
:if={@live_action == :quick_create}
|
||||
for={@quick_event_changeset}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
>
|
||||
<ClaperWeb.Component.Input.text
|
||||
form={f}
|
||||
key={:name}
|
||||
name=""
|
||||
readonly={false}
|
||||
class="h-12"
|
||||
placeholder={gettext("Name of your event")}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
phx_disable_with="Loading..."
|
||||
class="mt-5 w-full lg:w-auto px-6 text-white py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Create") %>
|
||||
</button>
|
||||
</.form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="border-b border-gray-200 py-4 flex items-center justify-between relative"
|
||||
id="events-header"
|
||||
phx-hook="TourGuide"
|
||||
data-group="welcome"
|
||||
data-next-label={gettext("Next")}
|
||||
data-prev-label={gettext("Back")}
|
||||
data-finish-label={gettext("Finish")}
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-2xl font-medium leading-6 text-gray-900 sm:truncate">
|
||||
<%= gettext("My presentations") %>
|
||||
<%= gettext("My events") %>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex mt-0">
|
||||
<a
|
||||
data-phx-link="patch"
|
||||
data-phx-link-state="push"
|
||||
href={Routes.event_index_path(@socket, :new)}
|
||||
<.link
|
||||
data-tg-group="welcome"
|
||||
data-tg-tour={
|
||||
gettext(
|
||||
"If you don't have time and just want interactions without a presentation file, you can create a new event here."
|
||||
)
|
||||
}
|
||||
data-tg-order="2"
|
||||
data-tg-title={"#{gettext("In a hurry ?")} 🏃♂️"}
|
||||
phx-click="toggle-quick-create"
|
||||
class="relative inline-flex items-center px-5 py-2 text-sm rounded-md text-gray-500"
|
||||
>
|
||||
<svg
|
||||
class="-ml-1 mr-1 h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
<%= gettext("Quick event") %>
|
||||
</span>
|
||||
</.link>
|
||||
<.link
|
||||
data-tg-group="welcome"
|
||||
data-tg-order="1"
|
||||
data-tg-tour={gettext("Welcome to Claper! You can create a new event here.")}
|
||||
data-tg-title={"#{gettext("Your first steps with Claper")} 👋"}
|
||||
href={~p"/events/new"}
|
||||
class="relative inline-flex items-center px-5 py-2 text-lg font-medium rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<svg
|
||||
@@ -37,28 +133,27 @@
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
<%= gettext("Create") %>
|
||||
<%= gettext("Create event") %>
|
||||
</span>
|
||||
</a>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 relative">
|
||||
<ul role="event-list" class="divide-y divide-gray-200" phx-update="append" id="events">
|
||||
<div class="mt-2 relative">
|
||||
<ul role="event-list" phx-update="stream" id="events">
|
||||
<% current_time = NaiveDateTime.utc_now() %>
|
||||
<%= for event <- @events do %>
|
||||
<.live_component
|
||||
module={ClaperWeb.EventLive.EventCardComponent}
|
||||
id={"event-#{event.uuid}"}
|
||||
event={event}
|
||||
current_time={current_time}
|
||||
/>
|
||||
<% end %>
|
||||
<.live_component
|
||||
:for={{id, event} <- @streams.events}
|
||||
module={ClaperWeb.EventLive.EventCardComponent}
|
||||
id={id}
|
||||
event={event}
|
||||
current_time={current_time}
|
||||
/>
|
||||
</ul>
|
||||
<%= if Enum.count(@events) == 0 do %>
|
||||
<%= if Enum.count(@streams.events) == 0 do %>
|
||||
<div class="w-full text-2xl text-black opacity-25 text-center">
|
||||
<img src="/images/icons/arrow.svg" class="h-20 float-right mr-16 -mt-5" />
|
||||
<p class="pt-12 clear-both"><%= gettext("Create your first presentation") %></p>
|
||||
<p class="pt-12 clear-both"><%= gettext("Create your first event") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -67,18 +162,13 @@
|
||||
<div class="border-b border-gray-200 py-4 flex items-center justify-between mt-12">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-2xl font-medium leading-6 text-gray-900 sm:truncate">
|
||||
<%= gettext("Invited presentations") %>
|
||||
<%= gettext("Invited events") %>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 relative">
|
||||
<ul
|
||||
role="managed-event-list"
|
||||
class="divide-y divide-gray-200"
|
||||
id="event-cards"
|
||||
phx-update="replace"
|
||||
>
|
||||
<div class="mt-2 relative">
|
||||
<ul role="managed-event-list" id="event-cards" phx-update="replace">
|
||||
<% current_time = NaiveDateTime.utc_now() %>
|
||||
<%= for event <- @managed_events do %>
|
||||
<.live_component
|
||||
|
||||
@@ -28,8 +28,7 @@ defmodule ClaperWeb.EventLive.Join do
|
||||
|
||||
@impl true
|
||||
def handle_event("join", %{"event" => %{"code" => code}}, socket) do
|
||||
{:noreply,
|
||||
socket |> push_redirect(to: Routes.event_show_path(socket, :show, String.downcase(code)))}
|
||||
{:noreply, socket |> push_redirect(to: ~p"/e/#{String.downcase(code)}")}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :join, _params) do
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
background: linear-gradient(-45deg, #2C033A, #21033A, #053138, #053138);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,17 +24,19 @@
|
||||
<%= gettext("About") %>
|
||||
</a>
|
||||
<%= if @current_user do %>
|
||||
<%= live_patch(gettext("Dashboard"),
|
||||
to: Routes.event_index_path(@socket, :index),
|
||||
class:
|
||||
"relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
) %>
|
||||
<.link
|
||||
href={~p"/events"}
|
||||
class="relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Dashboard") %>
|
||||
</.link>
|
||||
<% else %>
|
||||
<%= live_patch(gettext("Login"),
|
||||
to: Routes.user_session_path(@socket, :new),
|
||||
class:
|
||||
"relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
) %>
|
||||
<.link
|
||||
href={~p"/users/log_in"}
|
||||
class="relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Login") %>
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
<button @click="open = true" class="md:hidden">
|
||||
@@ -44,17 +47,19 @@
|
||||
<%= gettext("About") %>
|
||||
</a>
|
||||
<%= if @current_user do %>
|
||||
<%= live_patch(gettext("Dashboard"),
|
||||
to: Routes.event_index_path(@socket, :index),
|
||||
class:
|
||||
"relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
) %>
|
||||
<.link
|
||||
href={~p"/events"}
|
||||
class="relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Dashboard") %>
|
||||
</.link>
|
||||
<% else %>
|
||||
<%= live_patch(gettext("Login"),
|
||||
to: Routes.user_session_path(@socket, :new),
|
||||
class:
|
||||
"relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
) %>
|
||||
<.link
|
||||
href={~p"/users/log_in"}
|
||||
class="relative inline-flex items-center px-4 py-1 text-base font-sm rounded-md text-white bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Login") %>
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,24 +69,7 @@
|
||||
<img src="/images/logo.svg" class="h-12 mx-auto mb-16" />
|
||||
</div>
|
||||
|
||||
<%= if @last_event do %>
|
||||
<%= live_patch to: Routes.event_show_path(@socket, :show, @last_event.code) do %>
|
||||
<div class="rounded-md bg-gray-600 p-4 mb-8">
|
||||
<div class="flex justify-center items-center">
|
||||
<p class="text-sm text-white">
|
||||
<%= gettext("Return to your last presentation") %> (#<span class="uppercase"><%= @last_event.code %></span>)
|
||||
</p>
|
||||
<p class="text-base ml-3 mt-1">
|
||||
<a href="#" class="whitespace-nowrap font-medium text-white">
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= form_for :event, Routes.event_join_path(@socket, :join), ["phx-submit": "join", "phx-hook": "JoinEvent", id: "form"], fn f -> %>
|
||||
<%= form_for :event, ~p"/join", ["phx-submit": "join", "phx-hook": "JoinEvent", id: "form"], fn f -> %>
|
||||
<div class="relative">
|
||||
<%= text_input(f, :code,
|
||||
required: true,
|
||||
@@ -107,6 +95,23 @@
|
||||
</button>
|
||||
<img src="/images/loading.gif" id="loading" class="hidden h-12 mx-auto" />
|
||||
</div>
|
||||
|
||||
<%= if @last_event do %>
|
||||
<.link href={~p"/e/#{@last_event.code}"}>
|
||||
<div class="rounded-md bg-gray-600 bg-opacity-50 p-4 mt-8">
|
||||
<div class="flex justify-center items-center">
|
||||
<p class="text-sm text-white">
|
||||
<%= gettext("Return to your last event") %> (<%= @last_event.name %>)
|
||||
</p>
|
||||
<p class="text-base ml-3 mt-1">
|
||||
<a href="#" class="whitespace-nowrap font-medium text-white">
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</.link>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
else
|
||||
if connected?(socket) do
|
||||
Claper.Events.Event.subscribe(event.uuid)
|
||||
# Claper.Presentations.subscribe(event.presentation_file.id)
|
||||
|
||||
Presence.track(
|
||||
self(),
|
||||
@@ -41,9 +40,14 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
|> assign(:attendees_nb, 1)
|
||||
|> assign(:event, event)
|
||||
|> assign(:state, event.presentation_file.presentation_state)
|
||||
|> assign(:pinned_posts, list_pinned_posts(socket, event.uuid))
|
||||
|> assign(:all_posts, list_all_posts(socket, event.uuid))
|
||||
|> stream(:pinned_posts, list_pinned_posts(socket, event.uuid))
|
||||
|> stream(:posts, list_all_posts(socket, event.uuid))
|
||||
|> assign(:pinned_post_count, length(list_pinned_posts(socket, event.uuid)))
|
||||
|> assign(:post_count, length(list_all_posts(socket, event.uuid)))
|
||||
|> assign(
|
||||
:form_submit_count,
|
||||
length(list_form_submits(socket, event.presentation_file.id))
|
||||
)
|
||||
|> assign(:polls, list_polls(socket, event.presentation_file.id))
|
||||
|> assign(:forms, list_forms(socket, event.presentation_file.id))
|
||||
|> assign(:embeds, list_embeds(socket, event.presentation_file.id))
|
||||
@@ -58,7 +62,7 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
|> form_at_position(false)
|
||||
|> embed_at_position(false)
|
||||
|
||||
{:ok, socket, temporary_assigns: [all_posts: [], pinned_posts: [], form_submits: []]}
|
||||
{:ok, socket}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,7 +82,8 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
def handle_info({:post_created, post}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:all_posts, [post | socket.assigns.all_posts])
|
||||
|> stream_insert(:posts, post)
|
||||
|> update(:post_count, fn post_count -> post_count + 1 end)
|
||||
|> push_event("scroll", %{})}
|
||||
end
|
||||
|
||||
@@ -86,27 +91,28 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
def handle_info({:post_updated, updated_post}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> update(:all_posts, fn posts -> [updated_post | posts] end)
|
||||
|> update(:pinned_posts, fn posts -> [updated_post | posts] end)}
|
||||
|> stream_insert(:posts, updated_post)
|
||||
|> stream_insert(:pinned_posts, updated_post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_deleted, deleted_post}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> update(:all_posts, fn posts -> [deleted_post | posts] end)
|
||||
|> update(:pinned_posts, fn posts -> [deleted_post | posts] end)
|
||||
|> stream_delete(:posts, deleted_post)
|
||||
|> stream_delete(:pinned_posts, deleted_post)
|
||||
|> update(:pinned_post_count, fn pinned_post_count ->
|
||||
pinned_post_count - if deleted_post.pinned, do: 1, else: 0
|
||||
end)}
|
||||
end)
|
||||
|> update(:post_count, fn post_count -> post_count - 1 end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_pinned, post}, socket) do
|
||||
updated_socket =
|
||||
socket
|
||||
|> update(:all_posts, fn all_posts -> [post | all_posts] end)
|
||||
|> update(:pinned_posts, fn pinned_posts -> [post | pinned_posts] end)
|
||||
|> stream_insert(:posts, post)
|
||||
|> stream_insert(:pinned_posts, post)
|
||||
|> assign(:pinned_post_count, socket.assigns.pinned_post_count + 1)
|
||||
|
||||
{:noreply, updated_socket}
|
||||
@@ -116,8 +122,8 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
def handle_info({:post_unpinned, post}, socket) do
|
||||
updated_socket =
|
||||
socket
|
||||
|> update(:all_posts, fn all_posts -> [post | all_posts] end)
|
||||
|> update(:pinned_posts, fn pinned_posts -> [post | pinned_posts] end)
|
||||
|> stream_insert(:posts, post)
|
||||
|> stream_delete(:pinned_posts, post)
|
||||
|> assign(:pinned_post_count, socket.assigns.pinned_post_count - 1)
|
||||
|
||||
{:noreply, updated_socket}
|
||||
@@ -127,18 +133,22 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
def handle_info({:form_submit_created, fs}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> update(:form_submits, fn form_submits -> [fs | form_submits] end)
|
||||
|> stream_insert(:form_submits, fs)
|
||||
|> update(:form_submit_count, fn form_submit_count -> form_submit_count + 1 end)
|
||||
|> push_event("scroll", %{})}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:form_submit_updated, fs}, socket) do
|
||||
{:noreply, socket |> update(:form_submits, fn form_submits -> [fs | form_submits] end)}
|
||||
{:noreply, socket |> stream_insert(:form_submits, fs)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:form_submit_deleted, fs}, socket) do
|
||||
{:noreply, socket |> update(:form_submits, fn form_submits -> [fs | form_submits] end)}
|
||||
{:noreply,
|
||||
socket
|
||||
|> stream_delete(:form_submits, fs)
|
||||
|> update(:form_submit_count, fn form_submit_count -> form_submit_count - 1 end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -218,14 +228,14 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Interactions imported successfully"))
|
||||
|> redirect(to: Routes.event_manage_path(socket, :show, current_event.code))}
|
||||
|> redirect(to: ~p"/e/#{current_event.code}/manage")}
|
||||
end
|
||||
rescue
|
||||
Ecto.NoResultsError ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("Interactions import failed"))
|
||||
|> redirect(to: Routes.event_manage_path(socket, :show, current_event.code))}
|
||||
|> redirect(to: ~p"/e/#{current_event.code}/manage")}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -496,6 +506,40 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
{:noreply, socket |> assign(:state, new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"checked",
|
||||
%{"key" => "message_reaction_enabled", "value" => value},
|
||||
%{assigns: %{state: state}} = socket
|
||||
) do
|
||||
{:ok, new_state} =
|
||||
Claper.Presentations.update_presentation_state(
|
||||
state,
|
||||
%{
|
||||
:message_reaction_enabled => value
|
||||
}
|
||||
)
|
||||
|
||||
{:noreply, socket |> assign(:state, new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"checked",
|
||||
%{"key" => "show_poll_results_enabled", "value" => value},
|
||||
%{assigns: %{state: state}} = socket
|
||||
) do
|
||||
{:ok, new_state} =
|
||||
Claper.Presentations.update_presentation_state(
|
||||
state,
|
||||
%{
|
||||
:show_poll_results_enabled => value
|
||||
}
|
||||
)
|
||||
|
||||
{:noreply, socket |> assign(:state, new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"checked",
|
||||
@@ -537,10 +581,13 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
|
||||
updated_socket =
|
||||
if post.pinned do
|
||||
assign(socket, :pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
|
||||
assign(socket, :all_posts, list_all_posts(socket, socket.assigns.event.uuid))
|
||||
stream(socket, :pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid),
|
||||
reset: true
|
||||
)
|
||||
|
||||
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
||||
else
|
||||
assign(socket, :all_posts, list_all_posts(socket, socket.assigns.event.uuid))
|
||||
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
||||
end
|
||||
|
||||
{:noreply, updated_socket}
|
||||
@@ -567,19 +614,24 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
case tab do
|
||||
"posts" ->
|
||||
socket
|
||||
|> assign(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
|
||||
|> assign(:all_posts, list_all_posts(socket, socket.assigns.event.uuid))
|
||||
|> stream(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid),
|
||||
reset: true
|
||||
)
|
||||
|> stream(:posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
||||
|
||||
"forms" ->
|
||||
assign(
|
||||
stream(
|
||||
socket,
|
||||
:form_submits,
|
||||
list_form_submits(socket, socket.assigns.event.presentation_file.id)
|
||||
list_form_submits(socket, socket.assigns.event.presentation_file.id),
|
||||
reset: true
|
||||
)
|
||||
|
||||
"pinned_posts" ->
|
||||
socket
|
||||
|> assign(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid))
|
||||
|> stream(:pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid),
|
||||
reset: true
|
||||
)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
@@ -590,7 +642,7 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
if socket.assigns.create != nil do
|
||||
{:noreply,
|
||||
socket
|
||||
|> push_redirect(to: Routes.event_manage_path(socket, :show, socket.assigns.event.code))}
|
||||
|> push_redirect(to: ~p"/e/#{socket.assigns.event.code}/manage")}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
@@ -788,6 +840,6 @@ defmodule ClaperWeb.EventLive.Manage do
|
||||
end
|
||||
|
||||
defp list_form_submits(_socket, presentation_file_id) do
|
||||
Claper.Forms.list_form_submits(presentation_file_id)
|
||||
Claper.Forms.list_form_submits(presentation_file_id, [:form])
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -59,7 +59,7 @@ defmodule ClaperWeb.EventLive.PollComponent do
|
||||
<%= if (length @current_poll_vote) > 0 do %>
|
||||
<button class="bg-gray-500 px-3 py-2 rounded-full flex justify-between items-center relative text-white">
|
||||
<div
|
||||
style={"width: #{opt.percentage}%;"}
|
||||
style={"width: #{if @show_results, do: opt.percentage, else: 0}%;"}
|
||||
class={"bg-gradient-to-r from-primary-500 to-secondary-500 h-full absolute left-0 transition-all rounded-l-full #{if opt.percentage == "100", do: 'rounded-r-full'}"}
|
||||
>
|
||||
</div>
|
||||
@@ -80,7 +80,9 @@ defmodule ClaperWeb.EventLive.PollComponent do
|
||||
<% end %>
|
||||
<span class="flex-1"><%= opt.content %></span>
|
||||
</div>
|
||||
<span class="text-sm z-10"><%= opt.percentage %>% (<%= opt.vote_count %>)</span>
|
||||
<span :if={@show_results} class="text-sm z-10">
|
||||
<%= opt.percentage %>% (<%= opt.vote_count %>)
|
||||
</span>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
@@ -90,7 +92,7 @@ defmodule ClaperWeb.EventLive.PollComponent do
|
||||
class="bg-gray-500 px-3 py-2 rounded-full flex justify-between items-center relative text-white"
|
||||
>
|
||||
<div
|
||||
style={"width: #{opt.percentage}%;"}
|
||||
style={"width: #{if @show_results, do: opt.percentage, else: 0}%;"}
|
||||
class={"bg-gradient-to-r from-primary-500 to-secondary-500 h-full absolute left-0 transition-all rounded-l-full #{if opt.percentage == "100", do: 'rounded-r-full'}"}
|
||||
>
|
||||
</div>
|
||||
@@ -111,7 +113,9 @@ defmodule ClaperWeb.EventLive.PollComponent do
|
||||
<% end %>
|
||||
<span class="flex-1"><%= opt.content %></span>
|
||||
</div>
|
||||
<span class="text-sm z-10"><%= opt.percentage %>% (<%= opt.vote_count %>)</span>
|
||||
<span :if={@show_results} class="text-sm z-10">
|
||||
<%= opt.percentage %>% (<%= opt.vote_count %>)
|
||||
</span>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule ClaperWeb.EventLive.PostComponent do
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id={"post-#{@post.uuid}"} class={if @post.__meta__.state == :deleted, do: "hidden"}>
|
||||
<div id={@id}>
|
||||
<%= if @post.attendee_identifier == @attendee_identifier || (not is_nil(@current_user) && @post.user_id == @current_user.id) do %>
|
||||
<div class="px-4 pt-3 pb-8 rounded-b-lg rounded-tl-lg bg-gray-700 text-white relative z-0 break-word">
|
||||
<button
|
||||
@@ -179,82 +179,84 @@ defmodule ClaperWeb.EventLive.PostComponent do
|
||||
<p><%= @post.body %></p>
|
||||
|
||||
<div class="flex h-6 text-xs float-right space-x-2">
|
||||
<%= if not Enum.member?(@liked_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="👍"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/thumb.svg" class="h-4" />
|
||||
<%= if @post.like_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.like_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="👍"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<span class="">
|
||||
<%= if @reaction_enabled do %>
|
||||
<%= if not Enum.member?(@liked_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="👍"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/thumb.svg" class="h-4" />
|
||||
</span>
|
||||
<%= if @post.like_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.like_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% end %>
|
||||
<%= if not Enum.member?(@loved_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="❤️"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/heart.svg" class="h-4" />
|
||||
<%= if @post.love_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.love_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="❤️"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<img src="/images/icons/heart.svg" class="h-4" />
|
||||
<%= if @post.love_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.love_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% end %>
|
||||
<%= if not Enum.member?(@loled_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="😂"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/laugh.svg" class="h-4" />
|
||||
<%= if @post.lol_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.lol_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="😂"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<img src="/images/icons/laugh.svg" class="h-4" />
|
||||
<%= if @post.lol_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.lol_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<%= if @post.like_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.like_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="👍"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<span class="">
|
||||
<img src="/images/icons/thumb.svg" class="h-4" />
|
||||
</span>
|
||||
<%= if @post.like_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.like_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% end %>
|
||||
<%= if not Enum.member?(@loved_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="❤️"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/heart.svg" class="h-4" />
|
||||
<%= if @post.love_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.love_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="❤️"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<img src="/images/icons/heart.svg" class="h-4" />
|
||||
<%= if @post.love_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.love_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% end %>
|
||||
<%= if not Enum.member?(@loled_posts, @post.id) do %>
|
||||
<button
|
||||
phx-click="react"
|
||||
phx-value-type="😂"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-white items-center"
|
||||
>
|
||||
<img src="/images/icons/laugh.svg" class="h-4" />
|
||||
<%= if @post.lol_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.lol_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
phx-click="unreact"
|
||||
phx-value-type="😂"
|
||||
phx-value-post-id={@post.uuid}
|
||||
class="flex rounded-full px-3 py-1 border border-gray-300 bg-gray-100 items-center"
|
||||
>
|
||||
<img src="/images/icons/laugh.svg" class="h-4" />
|
||||
<%= if @post.lol_count > 0 do %>
|
||||
<span class="ml-1"><%= @post.lol_count %></span>
|
||||
<% end %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
class="w-full min-h-screen flex items-center justify-center relative bg-black"
|
||||
>
|
||||
<div
|
||||
class={"#{if @state.chat_visible, do: 'opacity-100 w-3/12 px-4 showed', else: 'opacity-0 w-0 p-0'} transition-all duration-150 flex flex-col h-screen py-5 justify-end max-h-screen bg-black"}
|
||||
class={"#{if @state.chat_visible, do: (if @event.presentation_file.length > 0, do: 'opacity-100 w-3/12 px-4 showed', else: 'opacity-100 w-2/3 px-4 showed'), else: 'opacity-0 w-0 p-0'} transition-all duration-150 flex flex-col h-screen py-5 justify-end max-h-screen bg-black"}
|
||||
id="post-list-wrapper"
|
||||
phx-update="replace"
|
||||
>
|
||||
@@ -168,17 +168,19 @@
|
||||
</div>
|
||||
<!-- SLIDES -->
|
||||
<div id="slider" phx-update="ignore">
|
||||
<%= for index <- 1..@event.presentation_file.length do %>
|
||||
<%= if Application.get_env(:claper, :presentations) |> Keyword.get(:storage) == "local" do %>
|
||||
<img
|
||||
class="w-1/3 max-h-screen mx-auto"
|
||||
src={"/uploads/#{@event.presentation_file.hash}/#{index}.jpg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="w-full max-h-screen mx-auto inline-block"
|
||||
src={"https://#{Application.get_env(:claper, :presentations) |> Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws, :region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{index}.jpg"}
|
||||
/>
|
||||
<%= for index <- 1..max(1, @event.presentation_file.length) do %>
|
||||
<%= if @event.presentation_file.length > 0 do %>
|
||||
<%= if Application.get_env(:claper, :presentations) |> Keyword.get(:storage) == "local" do %>
|
||||
<img
|
||||
class="w-1/3 max-h-screen mx-auto"
|
||||
src={"/uploads/#{@event.presentation_file.hash}/#{index}.jpg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="w-full max-h-screen mx-auto inline-block"
|
||||
src={"https://#{Application.get_env(:claper, :presentations) |> Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws, :region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{index}.jpg"}
|
||||
/>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -70,11 +70,12 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
|
||||
maybe_update_audience_peak(event, online)
|
||||
|
||||
posts = list_posts(socket, event.uuid)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:attendees_nb, 1)
|
||||
|> assign(:post_changeset, post_changeset)
|
||||
|> assign(:posts, list_posts(socket, event.uuid))
|
||||
|> assign(:liked_posts, reacted_posts(socket, event.id, "👍"))
|
||||
|> assign(:loved_posts, reacted_posts(socket, event.id, "❤️"))
|
||||
|> assign(:loled_posts, reacted_posts(socket, event.id, "😂"))
|
||||
@@ -83,6 +84,8 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
|> assign(:event, event)
|
||||
|> assign(:state, event.presentation_file.presentation_state)
|
||||
|> assign(:nickname, "")
|
||||
|> stream(:posts, posts)
|
||||
|> assign(:post_count, Enum.count(posts))
|
||||
|> starting_soon_assigns(event)
|
||||
|> get_current_poll(event)
|
||||
|> get_current_form(event)
|
||||
@@ -90,8 +93,7 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
|> check_leader(event)
|
||||
|> leader_list(event)
|
||||
|
||||
{:ok, socket |> assign(:empty_room, Enum.empty?(socket.assigns.posts)),
|
||||
temporary_assigns: [posts: []]}
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
defp leader_list(socket, event) do
|
||||
@@ -145,9 +147,7 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
def handle_info(:tick, %{assigns: %{diff: 0}} = socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> redirect(
|
||||
to: Routes.event_show_path(socket, :show, String.downcase(socket.assigns.event.code))
|
||||
)}
|
||||
|> redirect(to: ~p"/e/#{String.downcase(socket.assigns.event.code)}")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -174,16 +174,24 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
def handle_info({:post_created, post}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> update(:posts, fn posts -> [post | posts] end)
|
||||
|> push_event("scroll", %{})
|
||||
|> maybe_disable_empty_room}
|
||||
|> stream_insert(:posts, post)
|
||||
|> update(:post_count, fn count -> count + 1 end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:state_updated, presentation_state}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:state, presentation_state)}
|
||||
|> assign(:state, presentation_state)
|
||||
|> stream(:posts, list_posts(socket, socket.assigns.event.uuid), reset: true)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:event_terminated, _event}, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("This event has been terminated"))
|
||||
|> push_redirect(to: ~p"/")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -196,7 +204,7 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("You have been banned from this event"))
|
||||
|> push_redirect(to: Routes.event_join_path(socket, :index))}
|
||||
|> push_redirect(to: ~p"/")}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
@@ -211,7 +219,7 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("You have been banned from this event"))
|
||||
|> push_redirect(to: Routes.event_join_path(socket, :index))}
|
||||
|> push_redirect(to: ~p"/")}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
@@ -256,32 +264,35 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_updated, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply, socket |> stream_insert(:posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_pinned, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply, socket |> stream_insert(:posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_unpinned, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply, socket |> stream_insert(:posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:reaction_added, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply, socket |> stream_insert(:posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:reaction_removed, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply, socket |> stream_insert(:posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:post_deleted, post}, socket) do
|
||||
{:noreply, socket |> update(:posts, fn posts -> [post | posts] end)}
|
||||
{:noreply,
|
||||
socket
|
||||
|> stream_delete(:posts, post)
|
||||
|> update(:post_count, fn count -> count - 1 end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -754,8 +765,4 @@ defmodule ClaperWeb.EventLive.Show do
|
||||
socket
|
||||
|> assign(:page_title, "##{socket.assigns.event.code} - #{socket.assigns.event.name}")
|
||||
end
|
||||
|
||||
defp maybe_disable_empty_room(%{assigns: %{empty_room: empty_room}} = socket) do
|
||||
if empty_room, do: assign(socket, :empty_room, false), else: socket
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<a
|
||||
class="flex items-center px-3 py-2 bg-gray-200 mb-15 rounded-lg mt-5"
|
||||
href={Routes.event_join_path(@socket, :index, %{disconnected_from: @event.uuid})}
|
||||
href={~p"/?disconnected_from=#{@event.uuid}"}
|
||||
>
|
||||
<img src="/images/icons/exit-outline.svg" class="h-5 mr-3" />
|
||||
<span><%= gettext("Leave") %></span>
|
||||
@@ -33,6 +33,12 @@
|
||||
class="w-full bg-black fixed z-10 lg:w-1/3"
|
||||
style="box-shadow: 0px 15px 14px 1px rgba(0,0,0,0.75); -webkit-box-shadow: 0px 15px 14px 1px rgba(0,0,0,0.75); -moz-box-shadow: 0px 15px 14px 1px rgba(0,0,0,0.75);"
|
||||
>
|
||||
<div id="banner" class="hidden w-full bg-gray-800 text-center" phx-hook="EmbeddedBanner">
|
||||
<a href="https://claper.co" target="_blank" class="text-xs text-white py-3 w-full">
|
||||
<%= gettext("Create your next presentation with") %>
|
||||
<span class="underline">Claper</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-between items-center px-5 py-3">
|
||||
<button
|
||||
phx-click={toggle_side_menu()}
|
||||
@@ -64,6 +70,7 @@
|
||||
event={@event}
|
||||
selected_poll_opt={@selected_poll_opt}
|
||||
current_poll_vote={@current_poll_vote}
|
||||
show_results={@state.show_poll_results_enabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,29 +116,29 @@
|
||||
<div
|
||||
class="flex flex-col space-y-4 px-5 pt-20 pb-32 lg:w-1/3 bg-black min-h-screen"
|
||||
id="post-list"
|
||||
phx-update="append"
|
||||
data-posts-nb={Enum.count(@posts)}
|
||||
phx-update="stream"
|
||||
data-posts-nb={Enum.count(@streams.posts)}
|
||||
phx-hook="Scroll"
|
||||
data-target="body"
|
||||
>
|
||||
<%= for post <- @posts do %>
|
||||
<.live_component
|
||||
module={ClaperWeb.EventLive.PostComponent}
|
||||
id={"#{post.id}-post"}
|
||||
post={post}
|
||||
leaders={@leaders}
|
||||
is_leader={@is_leader}
|
||||
current_user={@current_user}
|
||||
attendee_identifier={@attendee_identifier}
|
||||
event={@event}
|
||||
liked_posts={@liked_posts}
|
||||
loved_posts={@loved_posts}
|
||||
loled_posts={@loled_posts}
|
||||
/>
|
||||
<% end %>
|
||||
<.live_component
|
||||
:for={{id, post} <- @streams.posts}
|
||||
module={ClaperWeb.EventLive.PostComponent}
|
||||
id={id}
|
||||
post={post}
|
||||
leaders={@leaders}
|
||||
is_leader={@is_leader}
|
||||
current_user={@current_user}
|
||||
attendee_identifier={@attendee_identifier}
|
||||
event={@event}
|
||||
reaction_enabled={@state.message_reaction_enabled}
|
||||
liked_posts={@liked_posts}
|
||||
loved_posts={@loved_posts}
|
||||
loled_posts={@loled_posts}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<%= if @empty_room && @state.chat_enabled do %>
|
||||
<%= if @post_count == 0 && @state.chat_enabled do %>
|
||||
<div class="text-2xl text-white block fixed bottom-32 left-0 w-full lg:w-1/3 lg:left-1/2 lg:transform lg:-translate-x-1/2 text-center opacity-30">
|
||||
<span><%= gettext("Be the first to react !") %></span>
|
||||
<img src="/images/icons/arrow-white.svg" class="h-24 rotate-180 ml-12 mt-8" />
|
||||
|
||||
@@ -1,23 +1,2 @@
|
||||
defmodule ClaperWeb.LiveHelpers do
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
@doc """
|
||||
Renders a component inside the `ClaperWeb.ModalComponent` component.
|
||||
|
||||
The rendered modal receives a `:return_to` option to properly update
|
||||
the URL when the modal is closed.
|
||||
|
||||
## Examples
|
||||
|
||||
<%= live_modal ClaperWeb.PostLive.FormComponent,
|
||||
id: @post.id || :new,
|
||||
action: @live_action,
|
||||
post: @post,
|
||||
return_to: Routes.post_index_path(@socket, :index) %>
|
||||
"""
|
||||
def live_modal(component, opts) do
|
||||
path = Keyword.fetch!(opts, :return_to)
|
||||
modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
|
||||
live_component(ClaperWeb.ModalComponent, modal_opts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="mx-3 max-w-7xl sm:mx-auto">
|
||||
<div class="mx-3 md:max-w-3xl lg:max-w-5xl md:mx-auto">
|
||||
<div class="border-b border-gray-200 py-4 flex flex-col sm:flex-row sm:items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-2xl font-medium leading-6 text-gray-900 sm:truncate">
|
||||
@@ -157,18 +157,20 @@
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">
|
||||
<%= gettext("Interactions history") %>
|
||||
</h3>
|
||||
<%= for position <- 0..@event.presentation_file.length-1 do %>
|
||||
<%= for position <- 0..max(0, @event.presentation_file.length-1) do %>
|
||||
<div class="my-10">
|
||||
<%= if Application.get_env(:claper, :presentations) |> Keyword.get(:storage) == "local" do %>
|
||||
<img
|
||||
class="w-1/3 mx-auto"
|
||||
src={"/uploads/#{@event.presentation_file.hash}/#{position+1}.jpg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="w-1/2 md:w-1/3 mb-4"
|
||||
src={"https://#{Application.get_env(:claper, :presentations) |> Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws, :region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{position+1}.jpg"}
|
||||
/>
|
||||
<%= if @event.presentation_file.length > 0 do %>
|
||||
<%= if Application.get_env(:claper, :presentations) |> Keyword.get(:storage) == "local" do %>
|
||||
<img
|
||||
class="w-1/3 mx-auto"
|
||||
src={"/uploads/#{@event.presentation_file.hash}/#{position+1}.jpg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="w-1/2 md:w-1/3 mb-4"
|
||||
src={"https://#{Application.get_env(:claper, :presentations) |> Keyword.get(:aws_bucket)}.s3.#{Application.get_env(:ex_aws, :region)}.amazonaws.com/presentations/#{@event.presentation_file.hash}/#{position+1}.jpg"}
|
||||
/>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= for poll <- Enum.filter(@event.presentation_file.polls, fn p -> p.position == position end) do %>
|
||||
@@ -216,7 +218,7 @@
|
||||
</span>
|
||||
|
||||
<%= if length(form.form_submits) > 0 do %>
|
||||
<%= link to: Routes.stat_path(@socket, :export, form.id), class: "text-xs text-white bg-primary-500 rounded-md px-2 py-0.5", method: :post do %>
|
||||
<%= link to: ~p"/export/#{form.id}", class: "text-xs text-white bg-primary-500 rounded-md px-2 py-0.5", method: :post do %>
|
||||
<%= gettext("Export all submissions") %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -232,12 +234,12 @@
|
||||
<%= if fs.attendee_identifier do %>
|
||||
<img
|
||||
class="h-8 w-8"
|
||||
src={"https://avatars.dicebear.com/api/identicon/#{fs.attendee_identifier}.svg"}
|
||||
src={"https://api.dicebear.com/7.x/personas/svg?seed=#{fs.attendee_identifier}.svg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="h-8 w-8"
|
||||
src={"https://avatars.dicebear.com/api/identicon/#{fs.user_id}.svg"}
|
||||
src={"https://api.dicebear.com/7.x/personas/svg?seed=#{fs.user_id}.svg"}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
@@ -279,12 +281,12 @@
|
||||
<%= if post.attendee_identifier do %>
|
||||
<img
|
||||
class="h-8 w-8"
|
||||
src={"https://avatars.dicebear.com/api/identicon/#{post.attendee_identifier}.svg"}
|
||||
src={"https://api.dicebear.com/7.x/personas/svg?seed=#{post.attendee_identifier}.svg"}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
class="h-8 w-8"
|
||||
src={"https://avatars.dicebear.com/api/identicon/#{post.user_id}.svg"}
|
||||
src={"https://api.dicebear.com/7.x/personas/svg?seed=#{post.user_id}.svg"}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
defmodule ClaperWeb.UserLiveAuth do
|
||||
import Phoenix.LiveView
|
||||
import Phoenix.Component
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
use Phoenix.VerifiedRoutes,
|
||||
endpoint: ClaperWeb.Endpoint,
|
||||
router: ClaperWeb.Router
|
||||
|
||||
def on_mount(:default, _params, %{"current_user" => current_user} = _session, socket) do
|
||||
socket =
|
||||
@@ -19,11 +22,11 @@ defmodule ClaperWeb.UserLiveAuth do
|
||||
# else
|
||||
# {:halt,
|
||||
# redirect(socket,
|
||||
# to: Routes.user_registration_path(socket, :confirm, %{email: current_user.email})
|
||||
# to: ~p"/users/register/confirm?#{[%{email: current_user.email}]}"
|
||||
# )}
|
||||
# end
|
||||
end
|
||||
|
||||
def on_mount(:default, _params, _session, socket),
|
||||
do: {:halt, redirect(socket, to: Routes.user_registration_path(socket, :confirm))}
|
||||
do: {:halt, redirect(socket, to: ~p"/users/register/confirm")}
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ defmodule ClaperWeb.UserSettingsLive.Show do
|
||||
Accounts.deliver_update_email_instructions(
|
||||
applied_user,
|
||||
user.email,
|
||||
&Routes.user_settings_url(socket, :confirm_email, &1)
|
||||
&url(~p"/users/settings/confirm_email/#{&1}")
|
||||
)
|
||||
|
||||
{:noreply,
|
||||
@@ -68,7 +68,7 @@ defmodule ClaperWeb.UserSettingsLive.Show do
|
||||
:info,
|
||||
gettext("A link to confirm your email change has been sent to the new address.")
|
||||
)
|
||||
|> push_redirect(to: Routes.user_settings_show_path(socket, :show))}
|
||||
|> push_redirect(to: ~p"/users/settings")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :email_changeset, changeset)}
|
||||
@@ -90,13 +90,23 @@ defmodule ClaperWeb.UserSettingsLive.Show do
|
||||
:info,
|
||||
gettext("Your password has been updated.")
|
||||
)
|
||||
|> push_redirect(to: Routes.user_settings_show_path(socket, :show))}
|
||||
|> push_redirect(to: ~p"/users/settings")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :password_changeset, changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete_account", _params, %{assigns: %{current_user: user}} = socket) do
|
||||
Accounts.delete(user)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, gettext("Your account has been deleted."))
|
||||
|> redirect(to: ~p"/users/log_in")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", _params, socket) do
|
||||
{:noreply, socket}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="mx-3 max-w-7xl sm:mx-auto">
|
||||
<div class="mx-3 md:max-w-3xl lg:max-w-5xl md:mx-auto">
|
||||
<div class="border-b border-gray-200 py-4 sm:flex sm:items-center sm:justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-2xl font-medium leading-6 text-gray-900 sm:truncate">
|
||||
@@ -16,7 +16,7 @@
|
||||
id="modal-wrapper"
|
||||
title={@page_title}
|
||||
description={@page_description}
|
||||
return_to={Routes.user_settings_show_path(@socket, :show)}
|
||||
return_to={~p"/users/settings"}
|
||||
>
|
||||
<div>
|
||||
<.form
|
||||
@@ -52,7 +52,7 @@
|
||||
id="modal-wrapper"
|
||||
title={@page_title}
|
||||
description={@page_description}
|
||||
return_to={Routes.user_settings_show_path(@socket, :show)}
|
||||
return_to={~p"/users/settings"}
|
||||
>
|
||||
<div>
|
||||
<.form
|
||||
@@ -105,10 +105,12 @@
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<span class="flex-grow"><%= @current_user.email %></span>
|
||||
<span class="ml-4 flex-shrink-0">
|
||||
<%= live_patch(gettext("Change"),
|
||||
to: Routes.user_settings_show_path(@socket, :edit_email),
|
||||
class: "rounded-md font-medium text-purple-600 hover:text-purple-500"
|
||||
) %>
|
||||
<.link
|
||||
patch={~p"/users/settings/edit/email"}
|
||||
class="rounded-md font-medium text-purple-600 hover:text-purple-500"
|
||||
>
|
||||
<%= gettext("Change") %>
|
||||
</.link>
|
||||
</span>
|
||||
</dd>
|
||||
|
||||
@@ -118,15 +120,42 @@
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<span class="flex-grow">********</span>
|
||||
<span class="ml-4 flex-shrink-0">
|
||||
<%= live_patch(gettext("Change"),
|
||||
to: Routes.user_settings_show_path(@socket, :edit_password),
|
||||
class: "rounded-md font-medium text-purple-600 hover:text-purple-500"
|
||||
) %>
|
||||
<.link
|
||||
patch={~p"/users/settings/edit/password"}
|
||||
class="rounded-md font-medium text-purple-600 hover:text-purple-500"
|
||||
>
|
||||
<%= gettext("Change") %>
|
||||
</.link>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
<div class="py-5">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
<%= gettext("Danger zone") %>
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
<%= gettext("Be careful, these actions are irreversible") %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 py-5 sm:p-0">
|
||||
<dl class="sm:divide-y sm:divide-gray-200">
|
||||
<div class="mt-5">
|
||||
<button
|
||||
data-confirm={
|
||||
gettext("All your events and files will be permanently deleted, are you sure?")
|
||||
}
|
||||
phx-click="delete_account"
|
||||
class="w-full lg:w-auto px-6 text-center text-white py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline bg-gradient-to-tl from-supporting-red-600 to-supporting-red-400 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
|
||||
>
|
||||
<%= gettext("Delete account") %>
|
||||
</button>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
12
lib/claper_web/plugs/iframe.ex
Normal file
12
lib/claper_web/plugs/iframe.ex
Normal file
@@ -0,0 +1,12 @@
|
||||
defmodule ClaperWeb.Plugs.Iframe do
|
||||
import Plug.Conn
|
||||
def init(_), do: %{}
|
||||
|
||||
def call(conn, _opts) do
|
||||
conn
|
||||
|> put_resp_header(
|
||||
"x-frame-options",
|
||||
"ALLOWALL"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -29,7 +29,7 @@ defmodule ClaperWeb.Router do
|
||||
|
||||
live_session :attendee do
|
||||
scope "/", ClaperWeb do
|
||||
pipe_through([:browser, :attendee_registration])
|
||||
pipe_through([:browser, :attendee_registration, ClaperWeb.Plugs.Iframe])
|
||||
|
||||
live("/", EventLive.Join, :index)
|
||||
live("/join", EventLive.Join, :join)
|
||||
|
||||
@@ -5,20 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag(assigns[:page_title] || "Claper", suffix: " · Claper") %>
|
||||
<link rel="icon" type="image/png" href={Routes.static_path(@conn, "/images/favicon.png")} />
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")} />
|
||||
<link
|
||||
phx-track-static
|
||||
rel="stylesheet"
|
||||
href={Routes.static_path(@conn, "/assets/custom.css")}
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
phx-track-static
|
||||
type="text/javascript"
|
||||
src={Routes.static_path(@conn, "/assets/app.js")}
|
||||
>
|
||||
<title>Not found - Claper</title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/app.css" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/custom.css" />
|
||||
<script defer phx-track-static type="text/javascript" src="/assets/app.js">
|
||||
</script>
|
||||
</head>
|
||||
<body class="">
|
||||
@@ -41,10 +32,9 @@
|
||||
</p>
|
||||
|
||||
<div class="mt-10">
|
||||
<%= live_patch(gettext("Return to home"),
|
||||
to: Routes.event_join_path(@conn, :index),
|
||||
class: "text-sm text-white underline"
|
||||
) %>
|
||||
<a href="/" class="text-sm text-white underline">
|
||||
<%= gettext("Return to home") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,20 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag(assigns[:page_title] || "Claper", suffix: " · Claper.co") %>
|
||||
<link rel="icon" type="image/png" href={Routes.static_path(@conn, "/images/favicon.png")} />
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")} />
|
||||
<link
|
||||
phx-track-static
|
||||
rel="stylesheet"
|
||||
href={Routes.static_path(@conn, "/assets/custom.css")}
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
phx-track-static
|
||||
type="text/javascript"
|
||||
src={Routes.static_path(@conn, "/assets/app.js")}
|
||||
>
|
||||
<title>Not found - Claper</title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/app.css" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/custom.css" />
|
||||
<script defer phx-track-static type="text/javascript" src="/assets/app.js">
|
||||
</script>
|
||||
</head>
|
||||
<body class="">
|
||||
@@ -42,10 +33,9 @@
|
||||
|
||||
<div class="mt-10">
|
||||
<p class="mt-5">
|
||||
<%= live_patch(gettext("Return to home"),
|
||||
to: Routes.event_join_path(@conn, :index),
|
||||
class: "text-sm text-white underline"
|
||||
) %>
|
||||
<a href="/" class="text-sm text-white underline">
|
||||
<%= gettext("Return to home") %>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="sticky top-0 z-20 h-16 bg-black">
|
||||
<div class="max-w-7xl sm:mx-auto">
|
||||
<div class="mx-3 md:max-w-3xl lg:max-w-5xl md:mx-auto">
|
||||
<!-- Sidebar toggle, controls the 'sidebarOpen' sidebar state. -->
|
||||
<a href={Routes.event_index_path(@conn, :index)} class="mt-3 float-left">
|
||||
<a href={~p"/events"} class="mt-3 float-left">
|
||||
<img src="/images/logo.svg" class="h-8" />
|
||||
</a>
|
||||
|
||||
@@ -11,21 +11,8 @@
|
||||
<div class="ml-3 relative">
|
||||
<div>
|
||||
<button
|
||||
phx-click-away={
|
||||
JS.hide(
|
||||
to: "#profile-dropdown",
|
||||
transition: "animate__animated animate__fadeOut",
|
||||
time: 300
|
||||
)
|
||||
}
|
||||
phx-click={
|
||||
JS.toggle(
|
||||
to: "#profile-dropdown",
|
||||
out: "animate__animated animate__fadeOut",
|
||||
in: "animate__animated animate__fadeIn",
|
||||
time: 800
|
||||
)
|
||||
}
|
||||
phx-click-away={JS.hide(to: "#profile-dropdown")}
|
||||
phx-click={JS.toggle(to: "#profile-dropdown")}
|
||||
type="button"
|
||||
class="max-w-xs bg-gray-800 text-white px-3 py-2 flex items-center text-sm rounded-md"
|
||||
id="user-menu-button"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<div class="py-1" role="none">
|
||||
<%= live_patch(gettext("Settings"),
|
||||
to: Routes.user_settings_show_path(@conn, :show),
|
||||
class: "text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
) %>
|
||||
<a
|
||||
href={~p"/users/settings"}
|
||||
class="text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
<%= gettext("Settings") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<%= link(gettext("Logout"),
|
||||
to: Routes.user_session_path(@conn, :delete),
|
||||
to: ~p"/users/log_out",
|
||||
method: :delete,
|
||||
class: "text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900"
|
||||
) %>
|
||||
|
||||
@@ -5,20 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag(assigns[:page_title] || "Claper", suffix: " · Claper") %>
|
||||
<link rel="icon" type="image/png" href={Routes.static_path(@conn, "/images/favicon.png")} />
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")} />
|
||||
<link
|
||||
phx-track-static
|
||||
rel="stylesheet"
|
||||
href={Routes.static_path(@conn, "/assets/custom.css")}
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
phx-track-static
|
||||
type="text/javascript"
|
||||
src={Routes.static_path(@conn, "/assets/app.js")}
|
||||
>
|
||||
<.live_title suffix=" · Claper" )><%= assigns[:page_title] || "Claper" %></.live_title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/app.css" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/custom.css" />
|
||||
<script defer phx-track-static type="text/javascript" src="/assets/app.js">
|
||||
</script>
|
||||
</head>
|
||||
<body class="">
|
||||
|
||||
@@ -5,20 +5,11 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag(assigns[:page_title] || "Claper", suffix: " · Claper") %>
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")} />
|
||||
<link rel="icon" type="image/png" href={Routes.static_path(@conn, "/images/favicon.png")} />
|
||||
<link
|
||||
phx-track-static
|
||||
rel="stylesheet"
|
||||
href={Routes.static_path(@conn, "/assets/custom.css")}
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
phx-track-static
|
||||
type="text/javascript"
|
||||
src={Routes.static_path(@conn, "/assets/app.js")}
|
||||
>
|
||||
<.live_title suffix=" · Claper" )><%= assigns[:page_title] || "Claper" %></.live_title>
|
||||
<link phx-track-static rel="stylesheet" href="/assets/app.css" />
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
||||
<link phx-track-static rel="stylesheet" href="/assets/custom.css" />
|
||||
<script defer phx-track-static type="text/javascript" src="/assets/app.js">
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<h1>Confirm account</h1>
|
||||
|
||||
<.form :let={_f} for={:user} action={Routes.user_confirmation_path(@conn, :update, @token)}>
|
||||
<.form :let={_f} for={:user} action={~p"/users/confirm/#{@token}"}>
|
||||
<div>
|
||||
<%= submit("Confirm my account") %>
|
||||
</div>
|
||||
</.form>
|
||||
|
||||
<p>
|
||||
<%= link("Register", to: Routes.user_registration_path(@conn, :new)) %> | <%= link("Log in",
|
||||
to: Routes.user_session_path(@conn, :new)
|
||||
<%= link("Register", to: ~p"/users/register") %> | <%= link("Log in",
|
||||
to: ~p"/users/log_in"
|
||||
) %>
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<h1>Resend confirmation instructions</h1>
|
||||
|
||||
<.form :let={f} for={:user} action={Routes.user_confirmation_path(@conn, :create)}>
|
||||
<.form :let={f} for={:user} action={~p"/users/confirm"}>
|
||||
<%= label(f, :email) %>
|
||||
<%= email_input(f, :email, required: true) %>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</.form>
|
||||
|
||||
<p>
|
||||
<%= link("Register", to: Routes.user_registration_path(@conn, :new)) %> | <%= link("Log in",
|
||||
to: Routes.user_session_path(@conn, :new)
|
||||
<%= link("Register", to: ~p"/users/register") %> | <%= link("Log in",
|
||||
to: ~p"/users/log_in"
|
||||
) %>
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<h1>Resend confirmation instructions</h1>
|
||||
|
||||
<%= form_for :user, Routes.user_confirmation_path(@conn, :create), fn f -> %>
|
||||
<%= form_for :user, ~p"/users/confirm", fn f -> %>
|
||||
<%= label(f, :email) %>
|
||||
<%= email_input(f, :email, required: true) %>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<%= link("Register", to: Routes.user_registration_path(@conn, :new)) %> | <%= link("Log in",
|
||||
to: Routes.user_session_path(@conn, :new)
|
||||
<%= link("Register", to: ~p"/users/register") %> | <%= link("Log in",
|
||||
to: ~p"/users/log_in"
|
||||
) %>
|
||||
</p>
|
||||
|
||||
@@ -25,10 +25,9 @@
|
||||
</p>
|
||||
|
||||
<div class="mt-10">
|
||||
<%= live_patch(gettext("Return to home"),
|
||||
to: Routes.event_join_path(@conn, :index),
|
||||
class: "text-sm text-white underline"
|
||||
) %>
|
||||
<.link href={~p"/"} class="text-sm text-white underline">
|
||||
<%= gettext("Return to home") %>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,12 +13,7 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<.form
|
||||
:let={f}
|
||||
for={@changeset}
|
||||
action={Routes.user_registration_path(@conn, :create)}
|
||||
class="mt-8 space-y-6"
|
||||
>
|
||||
<.form :let={f} for={@changeset} action={~p"/users/register"} class="mt-8 space-y-6">
|
||||
<%= if @changeset.action do %>
|
||||
<ClaperWeb.Component.Alert.error
|
||||
message={gettext("Oops, check that all fields are filled in correctly.")}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<.form
|
||||
:let={f}
|
||||
for={@changeset}
|
||||
action={Routes.user_reset_password_path(@conn, :update, @token)}
|
||||
action={~p"/users/reset_password/#{@token}"}
|
||||
method="post"
|
||||
class="mt-8 space-y-6"
|
||||
>
|
||||
|
||||
@@ -13,12 +13,7 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<.form
|
||||
:let={f}
|
||||
for={@changeset}
|
||||
action={Routes.user_reset_password_path(@conn, :create)}
|
||||
class="mt-8 space-y-6"
|
||||
>
|
||||
<.form :let={f} for={@changeset} action={~p"/users/reset_password"} class="mt-8 space-y-6">
|
||||
<%= if @changeset.action do %>
|
||||
<ClaperWeb.Component.Alert.error
|
||||
message={gettext("Oops, check that all fields are filled in correctly.")}
|
||||
|
||||
@@ -27,13 +27,7 @@
|
||||
</div>
|
||||
<div class="flex flex-row justify-center items-center space-x-3"></div>
|
||||
|
||||
<.form
|
||||
:let={f}
|
||||
for={@conn}
|
||||
action={Routes.user_session_path(@conn, :create)}
|
||||
as={:user}
|
||||
class="mt-12 mb-4"
|
||||
>
|
||||
<.form :let={f} for={@conn} action={~p"/users/log_in"} as={:user} class="mt-12 mb-4">
|
||||
<%= if @error_message do %>
|
||||
<ClaperWeb.Component.Alert.error message={@error_message} stick={true} />
|
||||
<% end %>
|
||||
@@ -70,12 +64,12 @@
|
||||
|
||||
<div class="mt-4 text-center flex gap-x-2 justify-center">
|
||||
<%= link(gettext("Forgot your password?"),
|
||||
to: Routes.user_reset_password_path(@conn, :new),
|
||||
to: ~p"/users/reset_password",
|
||||
class: "text-white text-sm text-center"
|
||||
) %>
|
||||
<%= if Application.get_env(:claper, :enable_account_creation) do %>
|
||||
<%= link(gettext("Create account"),
|
||||
to: Routes.user_registration_path(@conn, :new),
|
||||
to: ~p"/users/register",
|
||||
class: "text-white text-sm text-center"
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
@@ -106,45 +106,29 @@ defmodule ClaperWeb.Component.Input do
|
||||
|> assign_new(:shortcut, fn -> nil end)
|
||||
|
||||
~H"""
|
||||
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
|
||||
<button
|
||||
phx-click={checked(@checked, @key)}
|
||||
disabled={@disabled}
|
||||
phx-value-key={@key}
|
||||
id={"check-#{@key}"}
|
||||
type="button"
|
||||
class={"#{if @checked, do: 'bg-primary-600', else: 'bg-gray-200'} relative inline-flex flex-shrink-0 h-8 w-14 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200"}
|
||||
class="group relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-full"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
phx-key={@shortcut}
|
||||
phx-window-keydown={if @shortcut && not @disabled, do: checked(@checked, @key)}
|
||||
>
|
||||
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
||||
<span class={"#{if @checked, do: 'translate-x-6', else: 'translate-x-0'} pointer-events-none relative inline-block h-7 w-7 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"}>
|
||||
<!-- Enabled: "opacity-0 ease-out duration-100", Not Enabled: "opacity-100 ease-in duration-200" -->
|
||||
<span
|
||||
class={"#{if @checked, do: 'opacity-0 ease-out duration-100', else: 'opacity-100 ease-in duration-200'} absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<!-- Enabled: "opacity-100 ease-in duration-200", Not Enabled: "opacity-0 ease-out duration-100" -->
|
||||
<span
|
||||
class={"#{if @checked, do: 'opacity-100 ease-in duration-200', else: 'opacity-0 ease-out duration-100'} absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="h-5 w-5 text-primary-400" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="pointer-events-none absolute h-full w-full rounded-md bg-white" aria-hidden="true">
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class={"#{if @checked, do: 'bg-primary-500', else: 'bg-gray-200'} pointer-events-none absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out"}
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class={"#{if @checked, do: 'translate-x-5', else: 'translate-x-0'} pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow ring-0 transition-transform duration-200 ease-in-out"}
|
||||
aria-hidden="true"
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
"""
|
||||
@@ -154,24 +138,6 @@ defmodule ClaperWeb.Component.Input do
|
||||
|
||||
def checked(false, key, js) do
|
||||
js
|
||||
|> JS.remove_class("translate-x-0",
|
||||
to: "#check-#{key} > span"
|
||||
)
|
||||
|> JS.add_class("translate-x-6",
|
||||
to: "#check-#{key} > span"
|
||||
)
|
||||
|> JS.remove_class("opacity-100 ease-in duration-200",
|
||||
to: "#check-#{key} > span > span"
|
||||
)
|
||||
|> JS.add_class("opacity-0 ease-out duration-100",
|
||||
to: "#check-#{key} > span > span"
|
||||
)
|
||||
|> JS.remove_class("opacity-0 ease-out duration-100",
|
||||
to: "#check-#{key} > span > span:nth-child(2)"
|
||||
)
|
||||
|> JS.add_class("opacity-100 ease-in duration-200",
|
||||
to: "#check-#{key} > span > span:nth-child(2)"
|
||||
)
|
||||
|> JS.push("checked", value: %{key: key, value: true})
|
||||
end
|
||||
|
||||
@@ -233,63 +199,6 @@ defmodule ClaperWeb.Component.Input do
|
||||
end
|
||||
|
||||
def date(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:required, fn -> false end)
|
||||
|> assign_new(:autofocus, fn -> false end)
|
||||
|> assign_new(:value, fn -> Map.get(assigns.form.data, assigns.key) end)
|
||||
|
||||
assigns =
|
||||
if Map.has_key?(assigns, :dark),
|
||||
do: assign(assigns, :containerTheme, "text-white"),
|
||||
else: assign(assigns, :containerTheme, "text-black")
|
||||
|
||||
~H"""
|
||||
<div
|
||||
class="relative flatpickr"
|
||||
x-data={"{input: moment.utc(#{if assigns.value == nil, do: 'undefined', else: '\'#{assigns.value}\''}).local().format('Y-MM-DD HH:mm')}"}
|
||||
data-default-date={"#{assigns.value}"}
|
||||
x-on:click="$refs.input.focus()"
|
||||
id="date"
|
||||
phx-hook="Pickr"
|
||||
data-enable={"[
|
||||
{
|
||||
\"from\": \"#{@from}\",
|
||||
\"to\": \"#{@to}\"
|
||||
}]"}
|
||||
>
|
||||
<%= hidden_input(@form, :utc_date,
|
||||
required: @required,
|
||||
"x-ref": "utc",
|
||||
"phx-hook": "DefaultValue",
|
||||
"data-default-value": "#{assigns.value}"
|
||||
) %>
|
||||
<%= text_input(@form, @key,
|
||||
required: @required,
|
||||
autofocus: @autofocus,
|
||||
autocomplete: @key,
|
||||
class:
|
||||
"transition-all bg-transparent w-full #{@containerTheme} rounded px-3 border border-gray-500 focus:border-2 focus:border-primary-500 pt-5 pb-2 focus:outline-none input active:outline-none text-left",
|
||||
"x-model": "input",
|
||||
"x-ref": "input",
|
||||
"data-input": "true",
|
||||
"x-on:change": "$refs.utc.value = moment($refs.input.value).utc().format()"
|
||||
) %>
|
||||
<%= label(@form, @key, @name,
|
||||
class:
|
||||
"label absolute mb-0 -mt-2 pt-5 pl-3 leading-tighter text-gray-500 mt-2 cursor-text transition-all left-0",
|
||||
"x-bind:class": "input.length > 0 ? 'text-sm -top-1.5' : 'top-1'",
|
||||
"x-on:click": "$refs.input.focus()",
|
||||
"x-on:click.away": "$refs.input.blur()"
|
||||
) %>
|
||||
<%= if Keyword.has_key?(@form.errors, @key) do %>
|
||||
<p class="text-supporting-red-500 text-sm"><%= error_tag(@form, @key) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def date_range(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:required, fn -> false end)
|
||||
@@ -298,47 +207,20 @@ defmodule ClaperWeb.Component.Input do
|
||||
|> assign_new(:readonly, fn -> false end)
|
||||
|
||||
~H"""
|
||||
<div x-data="{getDate (start, end) {
|
||||
s = start == undefined || start.length === 0 ? moment().format('Y-MM-DD HH:mm') : moment.utc(start).local().format('Y-MM-DD HH:mm')
|
||||
e = end == undefined || end.length === 0 ? moment().add(2, 'hours').format('Y-MM-DD HH:mm') : moment.utc(end).local().format('Y-MM-DD HH:mm')
|
||||
return s + ' - ' + e }
|
||||
}">
|
||||
<div
|
||||
x-effect="date = getDate($refs.startDate.value, $refs.endDate.value)"
|
||||
class="relative flatpickr"
|
||||
x-data="{date: getDate($refs.startDate.value, $refs.endDate.value)}"
|
||||
data-mode="range"
|
||||
data-default-date-start={Map.get(assigns.form.data, assigns.start_date_field)}
|
||||
data-default-date-end={Map.get(assigns.form.data, assigns.end_date_field)}
|
||||
id="date-range"
|
||||
phx-hook={"#{if not @readonly, do: 'Pickr'}"}
|
||||
data-enable={"[
|
||||
{
|
||||
\"from\": \"#{@from}\",
|
||||
\"to\": \"#{@to}\"
|
||||
}]"}
|
||||
>
|
||||
<%= hidden_input(@form, @start_date_field, "x-ref": "startDate") %>
|
||||
<%= hidden_input(@form, @end_date_field, "x-ref": "endDate") %>
|
||||
<div>
|
||||
<div class="relative" id="date" phx-hook="Pickr">
|
||||
<%= label(@form, @key, @name, class: "block text-sm font-medium text-gray-700") %>
|
||||
<div class="mt-1 relative">
|
||||
<%= hidden_input(@form, @key) %>
|
||||
<%= text_input(@form, :local_date,
|
||||
required: @required,
|
||||
readonly: @readonly,
|
||||
class:
|
||||
"absolute z-0 outline-none shadow-base focus:ring-primary-500 focus:border-primary-500 block w-full text-lg border-gray-300 rounded-md py-4 px-3 read-only:opacity-50",
|
||||
"x-model": "date"
|
||||
) %>
|
||||
|
||||
<%= text_input(@form, @key,
|
||||
autofocus: @autofocus,
|
||||
placeholder: @placeholder,
|
||||
autocomplete: @key,
|
||||
autocomplete: false,
|
||||
class:
|
||||
"absolute z-10 bg-transparent text-transparent outline-none block w-full py-4 px-3",
|
||||
"data-input": "true"
|
||||
"outline-none shadow-base focus:ring-primary-500 focus:border-primary-500 block w-full text-lg border-gray-300 rounded-md py-4 px-3 read-only:opacity-50"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= if Keyword.has_key?(@form.errors, @key) do %>
|
||||
<p class="text-supporting-red-500 text-sm"><%= error_tag(@form, @key) %></p>
|
||||
<% end %>
|
||||
|
||||
@@ -3,7 +3,8 @@ defmodule ClaperWeb.ErrorHelpers do
|
||||
Conveniences for translating and building error messages.
|
||||
"""
|
||||
|
||||
use Phoenix.HTML
|
||||
import Phoenix.HTML.Form
|
||||
use PhoenixHTMLHelpers
|
||||
|
||||
@doc """
|
||||
Generates tag for inlined form input errors.
|
||||
|
||||
@@ -47,6 +47,6 @@ defmodule ClaperWeb.LayoutView do
|
||||
opts
|
||||
|> Keyword.put(:class, class)
|
||||
|
||||
live_patch(text, opts)
|
||||
link(text, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule ClaperWeb.LeaderNotifierView do
|
||||
use Phoenix.View, root: "lib/claper_web/templates"
|
||||
import ClaperWeb.Gettext
|
||||
use Phoenix.HTML
|
||||
use PhoenixHTMLHelpers
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
defmodule ClaperWeb.UserNotifierView do
|
||||
use Phoenix.View, root: "lib/claper_web/templates"
|
||||
import ClaperWeb.Gettext
|
||||
use Phoenix.HTML
|
||||
import Phoenix.HTML
|
||||
use PhoenixHTMLHelpers
|
||||
end
|
||||
|
||||
19
mix.exs
19
mix.exs
@@ -1,7 +1,7 @@
|
||||
defmodule Claper.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "1.7.0"
|
||||
@version "2.0.0"
|
||||
|
||||
def project do
|
||||
[
|
||||
@@ -90,16 +90,17 @@ defmodule Claper.MixProject do
|
||||
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
|
||||
{:bcrypt_elixir, "~> 2.0"},
|
||||
{:phoenix, "~> 1.7"},
|
||||
{:phoenix_ecto, "~> 4.4"},
|
||||
{:ecto_sql, "~> 3.10"},
|
||||
{:phoenix_ecto, "~> 4.5"},
|
||||
{:ecto_sql, "~> 3.11"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:phoenix_html, "~> 3.0"},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.18.3"},
|
||||
{:phoenix_swoosh, "~> 1.0"},
|
||||
{:phoenix_html, "~> 4.1"},
|
||||
{:phoenix_html_helpers, "~> 1.0"},
|
||||
{:phoenix_live_reload, "~> 1.5.2", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.20.14"},
|
||||
{:phoenix_swoosh, "~> 1.2.1"},
|
||||
{:phoenix_view, "~> 2.0"},
|
||||
{:floki, ">= 0.30.0", only: :test},
|
||||
{:phoenix_live_dashboard, "~> 0.7"},
|
||||
{:floki, ">= 0.36.1", only: :test},
|
||||
{:phoenix_live_dashboard, "~> 0.8"},
|
||||
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
{:dart_sass, "~> 0.5", runtime: Mix.env() == :dev},
|
||||
{:swoosh, "~> 1.12"},
|
||||
|
||||
63
mix.lock
63
mix.lock
@@ -1,30 +1,30 @@
|
||||
%{
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
|
||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
||||
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
|
||||
"credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
|
||||
"csv": {:hex, :csv, "3.0.5", "3c1455127e92de8845806db89554ad7d45e0212974be41dd9c38a5c881861713", [:mix], [], "hexpm", "cbbe5455c93df5f3f2943e995e28b7a8808361ba34cf3e44267d77a01eaf1609"},
|
||||
"dart_sass": {:hex, :dart_sass, "0.6.0", "1fe560c3ed5c577b6b9cf97134a0e05c82b69645d313b1ef0ffb4d659c3d0300", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "41f7bb065b5c30c3ea05e8b41aa3f9b5c62817079b94f70e2a22d133828475bb"},
|
||||
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
|
||||
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
|
||||
"ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
|
||||
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
|
||||
"esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
|
||||
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
|
||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||
"finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
|
||||
"floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
|
||||
"floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
|
||||
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
|
||||
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
|
||||
@@ -41,37 +41,38 @@
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"},
|
||||
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
|
||||
"mogrify": {:hex, :mogrify, "0.9.2", "b360984adea7dd6a55f18028e6327973c58de7f548fdb86c9859848aa904d5b0", [:mix], [], "hexpm", "c18d10fd70ca20e2585301616c89f6e4f7159d92efc9cc8ee579e00c886f699d"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.0", "9e18a119d9efc3370a3ef2a937bf0b24c088d9c4bf0ba9d7c3751d49d347d035", [:mix], [], "hexpm", "7977f183127a7cbe9346981e2f480dc04c55ffddaef746bd58debd566070eef8"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
|
||||
"plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
|
||||
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.2", "354460993a480656b71c3887f5565f612b3bdbdd8688c83f9e6f512307067dd4", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "2bb3722f327e14a7aa47b1acf27ed633c8cd27b167e18b8237954b9b4804af39"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
|
||||
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
||||
"porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], [], "hexpm", "dc996ab8fadbc09912c787c7ab8673065e50ea1a6245177b0c24569013d23620"},
|
||||
"postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"},
|
||||
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||
"stripity_stripe": {:hex, :stripity_stripe, "2.13.0", "b9ea806fcf46e85232b75f2145c34770b17faa44c59cdd13ff493aaa6e84b4a9", [:mix], [{:hackney, "~> 1.15", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:uri_query, "~> 0.1.2", [hex: :uri_query, repo: "hexpm", optional: false]}], "hexpm", "d6931ed9816552320f95428fd997edf15e99a913ca78fc4342d5516b98f42476"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
||||
"swoosh": {:hex, :swoosh, "1.12.0", "ecc85ee12947932986243299b8d28e6cdfc192c8d9e24c4c64f6738efdf344cb", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87db7ab0f35e358ba5eac3afc7422ed0c8c168a2d219d2a83ad8cb7a424f6cc9"},
|
||||
"swoosh": {:hex, :swoosh, "1.16.3", "4ab7dc429e84afaf8ffe1c7c06ce1acbc7ddde758d2cb9152dd2ac32289d5498", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.1.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ff70980087650a72951ebd109a286d83c270e2b6610aba447140562adff8cf0a"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"},
|
||||
"websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.0", "cea35d8bbf1a6964e32d4b02ceb561dfb769c04f16d60d743885587e7d2ca55b", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "16318b124effab8209b1eb7906c636374f623dc9511a8278ad09c083cea5bb83"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
defmodule Claper.Repo.Migrations.AddMessageReactionEnabledToPresentationStates do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:presentation_states) do
|
||||
add :message_reaction_enabled, :boolean, default: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
defmodule Claper.Repo.Migrations.AddShowPollResultsEnabledToPresentationStates do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:presentation_states) do
|
||||
add :show_poll_results_enabled, :boolean, default: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
docker stop claper-db
|
||||
docker rm claper-db
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:9
|
||||
docker run -p 5432:5432 -e POSTGRES_PASSWORD=claper -e POSTGRES_USER=claper -e POSTGRES_DB=claper --name claper-db -d postgres:15
|
||||
sleep 5
|
||||
mix ecto.migrate
|
||||
mix run priv/repo/seeds.exs
|
||||
@@ -191,7 +191,14 @@ defmodule Claper.AccountsTest do
|
||||
end
|
||||
|
||||
test "does not update email if token expired", %{user: user, token: token} do
|
||||
{1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
context = "change:#{user.email}"
|
||||
|
||||
{1, nil} =
|
||||
from(ut in UserToken,
|
||||
where: ut.user_id == ^user.id and ut.context == ^context
|
||||
)
|
||||
|> Repo.update_all(set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
|
||||
assert Accounts.update_user_email(user, token) == :error
|
||||
assert Repo.get!(User, user.id).email == user.email
|
||||
assert Repo.get_by(UserToken, user_id: user.id)
|
||||
@@ -235,8 +242,11 @@ defmodule Claper.AccountsTest do
|
||||
refute Accounts.get_user_by_session_token("oops")
|
||||
end
|
||||
|
||||
test "does not return user for expired token", %{token: token} do
|
||||
{1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
test "does not return user for expired token", %{user: user, token: token} do
|
||||
{1, nil} =
|
||||
from(ut in UserToken, where: ut.user_id == ^user.id and ut.context == "session")
|
||||
|> Repo.update_all(set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
|
||||
refute Accounts.get_user_by_session_token(token)
|
||||
end
|
||||
end
|
||||
@@ -296,7 +306,10 @@ defmodule Claper.AccountsTest do
|
||||
end
|
||||
|
||||
test "does not confirm email if token expired", %{user: user, token: token} do
|
||||
{1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
{1, nil} =
|
||||
from(ut in UserToken, where: ut.user_id == ^user.id and ut.context == "confirm")
|
||||
|> Repo.update_all(set: [inserted_at: ~N[2020-01-01 00:00:00]])
|
||||
|
||||
assert Accounts.confirm_user(token) == :error
|
||||
refute Repo.get!(User, user.id).confirmed_at
|
||||
assert Repo.get_by(UserToken, user_id: user.id)
|
||||
|
||||
@@ -131,8 +131,10 @@ defmodule ClaperWeb.UserAuthTest do
|
||||
test "redirects if user is not authenticated", %{conn: conn} do
|
||||
conn = conn |> fetch_flash() |> UserAuth.require_authenticated_user([])
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == Routes.user_session_path(conn, :new)
|
||||
assert get_flash(conn, :error) == "You must log in to access this page."
|
||||
assert redirected_to(conn) == ~p"/users/log_in"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
|
||||
"You must log in to access this page."
|
||||
end
|
||||
|
||||
test "stores the path to redirect to on GET", %{conn: conn} do
|
||||
|
||||
@@ -13,12 +13,12 @@ defmodule ClaperWeb.UserConfirmationControllerTest do
|
||||
@tag :capture_log
|
||||
test "sends a new confirmation token", %{conn: conn, user: user} do
|
||||
conn =
|
||||
post(conn, Routes.user_confirmation_path(conn, :create), %{
|
||||
post(conn, ~p"/users/confirm", %{
|
||||
"user" => %{"email" => user.email}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :info) =~ "If your email is in our system"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system"
|
||||
assert Repo.get_by!(Accounts.UserToken, user_id: user.id).context == "confirm"
|
||||
end
|
||||
|
||||
@@ -26,24 +26,28 @@ defmodule ClaperWeb.UserConfirmationControllerTest do
|
||||
Repo.update!(Accounts.User.confirm_changeset(user))
|
||||
|
||||
conn =
|
||||
post(conn, Routes.user_confirmation_path(conn, :create), %{
|
||||
post(conn, ~p"/users/confirm", %{
|
||||
"user" => %{"email" => user.email}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :info) =~ "If your email is in our system"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system"
|
||||
refute Repo.get_by(Accounts.UserToken, user_id: user.id)
|
||||
end
|
||||
|
||||
test "does not send confirmation token if email is invalid", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, Routes.user_confirmation_path(conn, :create), %{
|
||||
post(conn, ~p"/users/confirm", %{
|
||||
"user" => %{"email" => "unknown@example.com"}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :info) =~ "If your email is in our system"
|
||||
assert Repo.all(Accounts.UserToken) == []
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system"
|
||||
|
||||
assert from(ut in Accounts.UserToken,
|
||||
where: ut.context == "confirm"
|
||||
)
|
||||
|> Repo.all() == []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,32 +58,41 @@ defmodule ClaperWeb.UserConfirmationControllerTest do
|
||||
Accounts.deliver_user_confirmation_instructions(user, url)
|
||||
end)
|
||||
|
||||
conn = post(conn, Routes.user_confirmation_path(conn, :update, token))
|
||||
conn = post(conn, ~p"/users/confirm/#{token}")
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :info) =~ "User confirmed successfully"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "User confirmed successfully"
|
||||
assert Accounts.get_user!(user.id).confirmed_at
|
||||
refute get_session(conn, :user_token)
|
||||
assert Repo.all(Accounts.UserToken) == []
|
||||
|
||||
assert from(ut in Accounts.UserToken,
|
||||
where: ut.context == "confirm"
|
||||
)
|
||||
|> Repo.all() == []
|
||||
|
||||
# When not logged in
|
||||
conn = post(conn, Routes.user_confirmation_path(conn, :update, token))
|
||||
conn = post(conn, ~p"/users/confirm/#{token}")
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :error) =~ "User confirmation link is invalid or it has expired"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~
|
||||
"User confirmation link is invalid or it has expired"
|
||||
|
||||
# When logged in
|
||||
conn =
|
||||
build_conn()
|
||||
|> log_in_user(user)
|
||||
|> post(Routes.user_confirmation_path(conn, :update, token))
|
||||
|> post(~p"/users/confirm/#{token}")
|
||||
|
||||
assert redirected_to(conn) == "/"
|
||||
refute get_flash(conn, :error)
|
||||
refute Phoenix.Flash.get(conn.assigns.flash, :error)
|
||||
end
|
||||
|
||||
test "does not confirm email with invalid token", %{conn: conn, user: user} do
|
||||
conn = post(conn, Routes.user_confirmation_path(conn, :update, "oops"))
|
||||
conn = post(conn, ~p"/users/confirm/#{"oops"}")
|
||||
assert redirected_to(conn) == "/"
|
||||
assert get_flash(conn, :error) =~ "User confirmation link is invalid or it has expired"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~
|
||||
"User confirmation link is invalid or it has expired"
|
||||
|
||||
refute Accounts.get_user!(user.id).confirmed_at
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,26 +9,26 @@ defmodule ClaperWeb.UserSessionControllerTest do
|
||||
|
||||
describe "GET /users/log_in" do
|
||||
test "renders log in page", %{conn: conn} do
|
||||
conn = get(conn, Routes.user_session_path(conn, :new))
|
||||
conn = get(conn, ~p"/users/log_in")
|
||||
response = html_response(conn, 200)
|
||||
assert response =~ "Your email address"
|
||||
end
|
||||
|
||||
test "redirects if already logged in", %{conn: conn, user: user} do
|
||||
conn = conn |> log_in_user(user) |> get(Routes.user_session_path(conn, :new))
|
||||
conn = conn |> log_in_user(user) |> get(~p"/users/log_in")
|
||||
assert redirected_to(conn) == "/events"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /users/log_out" do
|
||||
test "logs the user out", %{conn: conn, user: user} do
|
||||
conn = conn |> log_in_user(user) |> delete(Routes.user_session_path(conn, :delete))
|
||||
conn = conn |> log_in_user(user) |> delete(~p"/users/log_out")
|
||||
assert redirected_to(conn) == "/"
|
||||
refute get_session(conn, :user_token)
|
||||
end
|
||||
|
||||
test "succeeds even if the user is not logged in", %{conn: conn} do
|
||||
conn = delete(conn, Routes.user_session_path(conn, :delete))
|
||||
conn = delete(conn, ~p"/users/log_out")
|
||||
assert redirected_to(conn) == "/"
|
||||
refute get_session(conn, :user_token)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
defmodule ClaperWeb.EventCardComponentTest do
|
||||
use ClaperWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Claper.{PresentationsFixtures, EventsFixtures}
|
||||
|
||||
@spec create_event(Claper.Accounts.User.t(), NaiveDateTime.t(), NaiveDateTime.t()) ::
|
||||
Claper.Presentations.PresentationFile.t()
|
||||
defp create_event(user, started_at, expired_at \\ nil) do
|
||||
event = event_fixture(%{user: user, started_at: started_at, expired_at: expired_at})
|
||||
presentation_file = presentation_file_fixture(%{event: event}, [:event])
|
||||
presentation_state_fixture(%{presentation_file: presentation_file})
|
||||
presentation_file
|
||||
end
|
||||
|
||||
describe "EventCardComponent" do
|
||||
setup [:register_and_log_in_user]
|
||||
|
||||
test "renders incoming for future event", %{conn: conn, user: user} do
|
||||
create_event(user, NaiveDateTime.add(NaiveDateTime.utc_now(), 7200, :second))
|
||||
{:ok, _view, html} = live(conn, "/events")
|
||||
assert html =~ "Incoming"
|
||||
end
|
||||
|
||||
test "renders live for current event", %{conn: conn, user: user} do
|
||||
create_event(user, NaiveDateTime.utc_now())
|
||||
{:ok, _view, html} = live(conn, "/events")
|
||||
assert html =~ "Live"
|
||||
end
|
||||
|
||||
test "renders finished for expired event", %{conn: conn, user: user} do
|
||||
create_event(
|
||||
user,
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), -7200, :second),
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), -10, :second)
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/events")
|
||||
assert html =~ "Finished"
|
||||
end
|
||||
|
||||
test "renders finished for expired event before starting", %{conn: conn, user: user} do
|
||||
create_event(
|
||||
user,
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), 7200, :second),
|
||||
NaiveDateTime.utc_now()
|
||||
)
|
||||
|
||||
{:ok, _view, html} = live(conn, "/events")
|
||||
assert html =~ "Finished"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,34 +16,34 @@ defmodule ClaperWeb.EventLiveTest do
|
||||
setup [:register_and_log_in_user, :create_event]
|
||||
|
||||
test "lists all events", %{conn: conn, presentation_file: presentation_file} do
|
||||
{:ok, _index_live, html} = live(conn, Routes.event_index_path(conn, :index))
|
||||
{:ok, _index_live, html} = live(conn, ~p"/events")
|
||||
|
||||
assert html =~ "presentations"
|
||||
assert html =~ "events"
|
||||
assert html =~ presentation_file.event.name
|
||||
end
|
||||
|
||||
test "updates event in listing", %{conn: conn, presentation_file: presentation_file} do
|
||||
{:ok, index_live, _html} = live(conn, Routes.event_index_path(conn, :index))
|
||||
{:ok, index_live, _html} = live(conn, ~p"/events")
|
||||
|
||||
assert index_live
|
||||
|> element("#event-#{presentation_file.event.uuid} a", "Edit")
|
||||
|> render_click() =~
|
||||
"Edit"
|
||||
|
||||
assert_patch(index_live, Routes.event_index_path(conn, :edit, presentation_file.event.uuid))
|
||||
assert_patch(index_live, ~p"/events/#{presentation_file.event.uuid}/edit")
|
||||
|
||||
{:ok, _, html} =
|
||||
index_live
|
||||
|> form("#event-form", event: @update_attrs)
|
||||
|> render_submit()
|
||||
|> follow_redirect(conn, Routes.event_index_path(conn, :index))
|
||||
|> follow_redirect(conn, ~p"/events")
|
||||
|
||||
assert html =~ "Updated successfully"
|
||||
assert html =~ "some updated name"
|
||||
end
|
||||
|
||||
test "deletes event in listing", %{conn: conn, presentation_file: presentation_file} do
|
||||
{:ok, index_live, _html} = live(conn, Routes.event_index_path(conn, :index))
|
||||
{:ok, index_live, _html} = live(conn, ~p"/events")
|
||||
|
||||
assert index_live
|
||||
|> element("#event-#{presentation_file.event.uuid} a", "Edit")
|
||||
@@ -54,9 +54,9 @@ defmodule ClaperWeb.EventLiveTest do
|
||||
index_live
|
||||
|> element(~s{a[phx-value-id=#{presentation_file.event.uuid}]})
|
||||
|> render_click()
|
||||
|> follow_redirect(conn, Routes.event_index_path(conn, :index))
|
||||
|> follow_redirect(conn, ~p"/events")
|
||||
|
||||
{:ok, index_live, _html} = live(conn, Routes.event_index_path(conn, :index))
|
||||
{:ok, index_live, _html} = live(conn, ~p"/events")
|
||||
|
||||
refute has_element?(index_live, "#event-#{presentation_file.event.uuid}")
|
||||
end
|
||||
@@ -67,9 +67,9 @@ defmodule ClaperWeb.EventLiveTest do
|
||||
|
||||
test "displays event", %{conn: conn, presentation_file: presentation_file} do
|
||||
{:ok, _show_live, html} =
|
||||
live(conn, Routes.event_show_path(conn, :show, presentation_file.event.code))
|
||||
live(conn, ~p"/e/#{presentation_file.event.code}")
|
||||
|
||||
assert html =~ "Be the first to react"
|
||||
assert html =~ "Be the first to react !"
|
||||
assert html =~ presentation_file.event.name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule ClaperWeb.PostLiveTest do
|
||||
|
||||
test "list posts", %{conn: conn, presentation_file: presentation_file} do
|
||||
{:ok, _index_live, html} =
|
||||
live(conn, Routes.event_show_path(conn, :show, presentation_file.event.code))
|
||||
live(conn, ~p"/e/#{presentation_file.event.code}")
|
||||
|
||||
assert html =~ "some body"
|
||||
end
|
||||
|
||||
@@ -19,10 +19,13 @@ defmodule ClaperWeb.ConnCase do
|
||||
|
||||
using do
|
||||
quote do
|
||||
use ClaperWeb, :verified_routes
|
||||
|
||||
# Import conveniences for testing with connections
|
||||
import Plug.Conn
|
||||
import Phoenix.ConnTest
|
||||
import ClaperWeb.ConnCase
|
||||
import Ecto.Query
|
||||
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ defmodule Claper.EventsFixtures do
|
||||
uuid: Ecto.UUID.generate(),
|
||||
user_id: assoc.user.id,
|
||||
started_at: NaiveDateTime.utc_now(),
|
||||
# add 2 hours
|
||||
expired_at: NaiveDateTime.add(NaiveDateTime.utc_now(), 7200, :second)
|
||||
expired_at: nil
|
||||
})
|
||||
|> Claper.Events.create_event()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user