Skip to content
Snippets Groups Projects
Commit 53cd7df1 authored by shyngys's avatar shyngys
Browse files

Merge branch '31-fr-21-dark-light-mode-appearance-switch' into 'main'

Resolve "FR-21: Dark/Light mode appearance switch"

Closes #31

See merge request !41
parents 823b0cec 44312c3b
No related branches found
No related tags found
1 merge request!41Resolve "FR-21: Dark/Light mode appearance switch"
Pipeline #46842 passed
Showing
with 662 additions and 111 deletions
......@@ -2,6 +2,148 @@
@tailwind components;
@tailwind utilities;
/* Theme variables */
:root[data-theme="light"] {
--color-background: #ffffff;
--color-text: #1a1a1a;
--color-primary: #4a90e2;
--color-secondary: #f5f5f5;
--color-border: #e2e8f0;
--color-accent: #3182ce;
--color-card: #ffffff;
--color-card-hover: #f7fafc;
--heading-color: #1a1a1a;
}
:root[data-theme="dark"] {
--color-background: #0f172a;
--color-text: #e2e8f0;
--color-primary: #60a5fa;
--color-secondary: #1e293b;
--color-border: #334155;
--color-accent: #93c5fd;
--color-card: #1e293b;
--color-card-hover: #334155;
--heading-color: #f1f5f9;
}
/* Apply theme colors */
body {
background-color: var(--color-background);
color: var(--color-text);
transition: background-color 0.3s ease, color 0.3s ease;
}
h1, h2, h3, h4, h5, h6 {
color: var(--heading-color);
}
/* Card styles */
.property-card {
background-color: var(--color-card);
border: 1px solid var(--color-border);
transition: all 0.3s ease;
}
.property-card:hover {
background-color: var(--color-card-hover);
}
/* Form element styles */
input, textarea, select {
background-color: var(--color-card);
color: var(--color-text);
border-color: var(--color-border);
}
input:focus, textarea:focus, select:focus {
border-color: var(--color-accent);
}
/* Button styles */
.button-primary {
background-color: var(--color-primary);
color: white;
}
.button-secondary {
background-color: var(--color-secondary);
color: var(--color-text);
}
/* Theme toggle */
.theme-toggle-button {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 50%;
color: var(--color-text);
transition: background-color 0.3s ease;
}
.theme-toggle-button:hover {
background-color: var(--color-secondary);
}
/* Dark mode specific overrides */
.dark {
/* Form inputs */
input, textarea, select {
@apply bg-gray-800 border-gray-700 text-gray-200;
}
/* Card styles */
.card, .property-card {
@apply bg-gray-800 border-gray-700;
}
/* Headers and text */
h1, h2, h3, h4, h5, h6 {
@apply text-gray-100;
}
/* Buttons */
.button, button[type="submit"] {
@apply bg-gray-700 hover:bg-gray-600 text-white;
}
/* Links */
a {
@apply text-blue-400 hover:text-blue-300;
}
/* Tables */
table {
@apply bg-gray-800 border-gray-700;
}
td, th {
@apply border-gray-700 text-gray-200;
}
}
.link[href*="/edit"] {
@apply text-blue-400 hover:text-blue-300;
}
.link[href*="/properties"][data-confirm] {
@apply bg-red-600 text-white hover:bg-red-700;
}
.interest-menu-button {
@apply text-gray-900 dark:text-white;
}
.interest-menu {
@apply bg-white dark:bg-gray-800 border dark:border-gray-700;
}
.interest-option {
@apply text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700;
}
/* Previous code */
.favorite-button {
background: none;
border: none;
......
......@@ -7,6 +7,9 @@ import "./favorites";
import "./status";
import "./pricecalculation";
import "./interest";
import initTheme from "./theme";
initTheme();
let csrfToken = document
.querySelector("meta[name='csrf-token']")
......
const initTheme = () => {
// Check for saved theme preference or default to system preference
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
// Apply initial theme
document.documentElement.setAttribute('data-theme', savedTheme);
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark');
}
// Add click handler to theme toggle button
document.addEventListener('click', (e) => {
const themeToggle = e.target.closest('[data-theme-toggle]');
if (themeToggle) {
const currentTheme = localStorage.getItem('theme') || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
// Update localStorage
localStorage.setItem('theme', newTheme);
// Update document classes and data attributes
document.documentElement.setAttribute('data-theme', newTheme);
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
});
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', e => {
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', newTheme);
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
});
};
export default initTheme;
\ No newline at end of file
......@@ -6,15 +6,38 @@ const fs = require("fs")
const path = require("path")
module.exports = {
darkMode: 'class',
content: [
"./js/**/*.js",
"../lib/proptrackr_web.ex",
"../lib/proptrackr_web/**/*.*ex"
"../lib/proptrackr_web/**/*.*ex",
"../lib/proptrackr_web/components/**/*.*ex"
],
theme: {
extend: {
colors: {
brand: "#FD4F00",
dark: {
background: '#0f172a',
card: '#1e293b',
text: '#e2e8f0',
heading: '#f1f5f9',
border: '#334155',
primary: '#60a5fa',
accent: '#93c5fd'
}
},
backgroundColor: {
'dark-form': '#1e293b',
'dark-card': '#1e293b',
'dark-hover': '#334155'
},
textColor: {
'dark-primary': '#f1f5f9',
'dark-secondary': '#e2e8f0'
},
borderColor: {
'dark-border': '#334155'
}
},
},
......@@ -71,4 +94,4 @@ module.exports = {
}, {values})
})
]
}
}
\ No newline at end of file
......@@ -73,4 +73,8 @@ defmodule WhiteBreadConfig do
context: PropertySearchContext,
feature_paths: ["features/property_search.feature"]
suite name: "FR-21: Dark/Light mode appearance switch",
context: DarkModeContext,
feature_paths: ["features/dark_mode.feature"]
end
defmodule DarkModeContext do
use WhiteBread.Context
use Hound.Helpers
alias PropTrackr.Properties.Property
alias PropTrackr.Accounts.User
alias PropTrackr.Repo
scenario_starting_state fn _state ->
Ecto.Adapters.SQL.Sandbox.checkout(PropTrackr.Repo)
Ecto.Adapters.SQL.Sandbox.mode(PropTrackr.Repo, {:shared, self()})
Hound.start_session()
%{}
end
scenario_finalize fn _status, _state ->
Ecto.Adapters.SQL.Sandbox.checkin(PropTrackr.Repo)
Hound.end_session()
end
given_ ~r/^there exists following accounts$/, fn state, %{table_data: table} ->
table
|> Enum.map(fn user_details -> User.changeset(%User{}, user_details) end)
|> Enum.each(fn changeset -> Repo.insert!(changeset) end)
user = List.last(table)
{
:ok,
state
|> Map.put(:user_email, user[:email])
|> Map.put(:user_password, user[:password])
}
end
and_ ~r/^the following properties exist$/, fn state, %{ table_data: table } ->
owner = Repo.get_by(User, email: state[:user_email])
advertisements =
table
|> Enum.map(fn details -> details |> Map.put(:reference, Ecto.UUID.generate()) end)
|> Enum.map(fn details -> {Ecto.build_assoc(owner, :properties, details), details} end)
|> Enum.map(fn {assoc, details} -> Property.changeset(assoc, details) end)
|> Enum.map(fn changeset -> Repo.insert!(changeset) end)
{
:ok,
state
|> Map.put(:advertisements, advertisements)
}
end
# Navigation steps
when_ ~r/^I visit the home page$/, fn state ->
navigate_to("/")
{:ok, state}
end
given_ ~r/^I am on the home page$/, fn state ->
navigate_to("/")
{:ok, state}
end
when_ ~r/^I click the theme toggle button$/, fn state ->
find_element(:css, "[data-theme-toggle]") |> click()
:timer.sleep(500)
{:ok, state}
end
given_ ~r/^I have enabled dark mode$/, fn state ->
navigate_to("/")
find_element(:css, "[data-theme-toggle]") |> click()
:timer.sleep(500)
{:ok, state}
end
when_ ~r/^I navigate to the (?<page>[^"]+) page$/, fn state, %{page: page} ->
path = case page do
"profile" -> "/me"
"login" -> "/login"
"home" -> "/"
end
navigate_to(path)
:timer.sleep(500)
{:ok, state}
end
when_ ~r/^I refresh the page$/, fn state ->
refresh_page()
:timer.sleep(500)
{:ok, state}
end
when_ ~r/^I visit the property details page$/, fn state ->
property = hd(state.advertisements)
navigate_to("/properties/#{property.reference}")
:timer.sleep(1000)
{:ok, state}
end
# Light mode assertions
then_ ~r/^I should see the application in light mode$/, fn state ->
assert get_css_variable("--color-background") == "#ffffff"
assert get_css_variable("--color-text") == "#1a1a1a"
assert get_css_variable("--color-card") == "#ffffff"
{:ok, state}
end
# Dark mode assertions
then_ ~r/^I should see the application in dark mode$/, fn state ->
assert get_css_variable("--color-background") == "#0f172a"
assert get_css_variable("--color-text") == "#e2e8f0"
assert get_css_variable("--color-card") == "#1e293b"
{:ok, state}
end
then_ ~r/^I should see the theme toggle button shows (?<icon>[^"]+) icon$/, fn state, %{icon: icon} ->
icon_class = case icon do
"moon" -> "hero-moon-solid"
"sun" -> "hero-sun-solid"
end
assert element?(:css, ".#{icon_class}")
{:ok, state}
end
then_ ~r/^the background should be dark$/, fn state ->
assert get_css_variable("--color-background") == "#0f172a"
{:ok, state}
end
and_ ~r/^I am logged in$/, fn state ->
setup_session(state[:user_email], state[:user_password])
:timer.sleep(500)
{:ok, state}
end
and_ ~r/^the theme toggle should show the moon icon$/, fn state ->
moon_icon = find_element(:css, ".theme-toggle-button .hero-moon-solid")
assert element_displayed?(moon_icon)
{:ok, state}
end
and_ ~r/^the theme toggle should show the sun icon$/, fn state ->
sun_icon = find_element(:css, ".theme-toggle-button .hero-sun-solid")
assert element_displayed?(sun_icon)
{:ok, state}
end
then_ ~r/^the text should be light colored$/, fn state ->
assert get_css_variable("--color-text") == "#e2e8f0"
{:ok, state}
end
then_ ~r/^I should see the (?<page>[^"]+) page in dark mode$/, fn state, %{page: page} ->
assert get_css_variable("--color-background") == "#0f172a"
{:ok, state}
end
then_ ~r/^I should see dark mode colors for:$/, fn state, %{table_data: table} ->
variables = %{
"Background" => "--color-background",
"Text" => "--color-text",
"Cards" => "--color-card",
"Buttons" => "--color-primary"
}
{:ok, Map.put(state, :colors,
Enum.reduce(table, %{}, fn row, acc ->
css_var = variables[row["Element"]]
value = get_css_variable(css_var)
Map.put(acc, row["Element"], value)
end))}
end
defp get_css_variable(variable_name) do
script = """
var style = getComputedStyle(document.documentElement);
return style.getPropertyValue('#{variable_name}').trim();
"""
execute_script(script)
end
defp setup_session(email, password) do
fill_field({:id, "email"}, email)
fill_field({:id, "password"}, password)
click({:id, "login_button"})
end
end
Feature: FR-21: Dark/Light mode appearance switch
Scenario: Default theme is light
When I visit the home page
Then I should see the application in light mode
And the theme toggle should show the moon icon
Scenario: Switching to dark mode
Given I am on the home page
When I click the theme toggle button
Then I should see the application in dark mode
And the theme toggle should show the sun icon
And the background should be dark
And the text should be light colored
Scenario: Dark mode persists across pages
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I have enabled dark mode
When I navigate to the login page
Then I should see the login page in dark mode
And I am logged in
When I navigate to the profile page
Then I should see the profile page in dark mode
When I navigate to the home page
Then I should see the home page in dark mode
Scenario: Theme preference persists after refresh
Given I have enabled dark mode
When I refresh the page
Then I should see the application in dark mode
Scenario: Dark mode affects all UI elements
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And the following properties exist
| title | description | type | property_type | state | location | room_count | area | floor | floor_count | price |
| Really cool property | Selling this really really house | sell | house | available | London | 3 | 100.0 | 2 | 5 | 500000 |
And I have enabled dark mode
When I visit the property details page
Then I should see dark mode colors for:
| Element | Color |
| Background | #0f172a |
# | Text | #e2e8f0 |
# | Cards | #1e293b |
# | Buttons | #60a5fa |
\ No newline at end of file
......@@ -202,7 +202,7 @@ defmodule PropTrackrWeb.CoreComponents do
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="mt-10 space-y-8 bg-white">
<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6 space-y-8">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
......@@ -231,7 +231,7 @@ defmodule PropTrackrWeb.CoreComponents do
<button
type={@type}
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 dark:bg-gray-700 dark:hover:bg-gray-600 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
@class
]}
......@@ -335,7 +335,7 @@ defmodule PropTrackrWeb.CoreComponents do
<select
id={@id}
name={@name}
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
class="mt-2 block w-full rounded-md border border-gray-300 bg-white dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
multiple={@multiple}
{@rest}
>
......@@ -355,7 +355,8 @@ defmodule PropTrackrWeb.CoreComponents do
id={@id}
name={@name}
class={[
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
"mt-2 block w-full rounded-lg text-zinc-900 dark:text-gray-200 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
"dark:bg-gray-800 dark:border-gray-700",
@errors == [] && "border-zinc-300 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
......@@ -377,7 +378,8 @@ defmodule PropTrackrWeb.CoreComponents do
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6",
"mt-2 block w-full rounded-lg text-zinc-900 dark:text-gray-200 focus:ring-0 sm:text-sm sm:leading-6",
"dark:bg-gray-800 dark:border-gray-700",
@errors == [] && "border-zinc-300 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
......@@ -396,7 +398,7 @@ defmodule PropTrackrWeb.CoreComponents do
def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800 dark:text-dark-text">
<%= render_slot(@inner_block) %>
</label>
"""
......@@ -429,10 +431,10 @@ defmodule PropTrackrWeb.CoreComponents do
~H"""
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
<div>
<h1 class="text-lg font-semibold leading-8 text-zinc-800">
<h1 class="text-lg font-semibold leading-8 text-zinc-800 dark:text-gray-100">
<%= render_slot(@inner_block) %>
</h1>
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600 dark:text-gray-300">
<%= render_slot(@subtitle) %>
</p>
</div>
......@@ -673,4 +675,60 @@ defmodule PropTrackrWeb.CoreComponents do
def translate_errors(errors, field) when is_list(errors) do
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
end
def theme_toggle(assigns) do
~H"""
<div class="theme-toggle">
<button
type="button"
class="theme-toggle-button"
aria-label="Toggle theme"
data-theme-toggle
>
<.icon name="hero-moon-solid" class="h-6 w-6 dark:hidden" />
<.icon name="hero-sun-solid" class="hidden h-6 w-6 dark:block" />
</button>
</div>
"""
end
def content_card(assigns) do
~H"""
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
<%= render_slot(@inner_block) %>
</div>
"""
end
def section_title(assigns) do
~H"""
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">
<%= render_slot(@inner_block) %>
</h3>
"""
end
def detail_item(assigns) do
~H"""
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= @label %></dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-200">
<%= render_slot(@inner_block) %>
</dd>
</div>
"""
end
def property_link(assigns) do
assigns = assign_new(assigns, :class, fn -> "text-blue-400 hover:text-blue-300" end)
~H"""
<.link
href={@href}
class={@class}
>
<%= render_slot(@inner_block) %>
</.link>
"""
end
end
<header class="header flex flex-row-reverse gap-x-4 px-4 py-1">
<header class="header flex items-center justify-between px-4 py-1">
<span class="logo"></span>
<div class="flex items-center gap-4">
<.theme_toggle />
<%= if @conn.assigns.current_user do %>
<button
id="logout_button"
phx-click={JS.navigate("/logout")}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700 dark:text-zinc-100 dark:hover:text-zinc-300"
>
Logout
<.icon name="hero-arrow-right-solid" class="h-3 w-3" />
</button>
<span class="text-zinc-900 dark:text-zinc-100">Hello, <%= @conn.assigns.current_user.name %>!</span>
<% else %>
<button
id="nav_login_button"
phx-click={JS.navigate("/login")}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700 dark:text-zinc-100 dark:hover:text-zinc-300"
>
Log in
<.icon name="hero-arrow-right-solid" class="h-3 w-3" />
</button>
<% end %>
</div>
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
<%!-- <header class="header flex flex-row-reverse gap-x-4 px-4 py-1">
<%= if @conn.assigns.current_user do %>
<button
id="logout_button"
......@@ -24,4 +60,4 @@
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl"><.flash_group flash={@flash} /> <%= @inner_content %></div>
</main>
</main> --%>
<!DOCTYPE html>
<html lang="en" class="[scrollbar-gutter:stable]">
<html lang="en" class="[scrollbar-gutter:stable] dark:dark" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
......@@ -11,7 +11,7 @@
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class="bg-white">
<body class="bg-white dark:bg-dark-background text-zinc-900 dark:text-dark-text transition-colors duration-300">
<%= @inner_content %>
</body>
</html>
<.header>
My Favorite Properties
<span>My Favorite Properties</span>
</.header>
<div id="favorites" class="flex flex-col gap-y-4 mt-8">
<%= if @favorites == [] do %>
<p>You haven't favorited any properties yet.</p>
<p class="text-gray-600 dark:text-gray-300">You haven't favorited any properties yet.</p>
<% end %>
<%= for favorite <- @favorites do %>
<div class="bg-white border-black border rounded px-4 py-2">
<.content_card>
<div class="flex gap-4">
<div class="w-48 h-48 flex-shrink-0">
<%= if first_photo = Enum.at(favorite.property.photos, 0) do %>
......@@ -16,33 +18,39 @@
class="w-full h-full object-cover rounded"
/>
<% else %>
<div class="w-full h-full bg-gray-200 flex items-center justify-center rounded">
<span class="text-gray-400">No photo</span>
<div class="w-full h-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center rounded">
<span class="text-gray-400 dark:text-gray-300">No photo</span>
</div>
<% end %>
</div>
<div class="flex-grow">
<div class="flex justify-between items-start">
<h2 class="font-bold"><%= favorite.property.title %></h2>
<.section_title><%= favorite.property.title %></.section_title>
<button
type="button"
class="favorite-button text-2xl"
class="favorite-button text-2xl text-gray-900 dark:text-white"
data-property-reference={favorite.property.reference}
data-favorited="true"
>
</button>
</div>
<p><%= favorite.property.description %></p>
<p class="italic"><%= favorite.property.location %></p>
<p class="text-gray-600 dark:text-gray-300"><%= favorite.property.description %></p>
<p class="italic text-gray-600 dark:text-gray-300"><%= favorite.property.location %></p>
<div class="flex flex-row gap-x-2">
<span><%= favorite.property.price %></span>
<span><%= favorite.property.room_count %> rooms</span>
<span><%= favorite.property.area %> m<sup>2</sup></span>
<.detail_item label="Price">
<%= favorite.property.price %>
</.detail_item>
<.detail_item label="Rooms">
<%= favorite.property.room_count %>
</.detail_item>
<.detail_item label="Area">
<%= favorite.property.area %> m<sup>2</sup>
</.detail_item>
</div>
<div class="flex flex-row justify-end">
<.link href={~p"/properties/#{favorite.property.reference}"}>
<div class="flex flex-row justify-end mt-4">
<.property_link href={~p"/properties/#{favorite.property.reference}"}>
<.button
type="button"
class="text-white rounded px-4 py-2"
......@@ -50,10 +58,10 @@
>
View more
</.button>
</.link>
</.property_link>
</div>
</div>
</div>
</div>
</.content_card>
<% end %>
</div>
......@@ -3,7 +3,7 @@
</.header>
<div id="properties" class="flex flex-col gap-y-4 mt-20">
<%= for property <- @properties do %>
<div class="bg-white border-black border rounded px-4 py-2">
<.content_card>
<div class="flex gap-4">
<div class="w-48 h-48 flex-shrink-0">
<%= if first_photo = Enum.at(property.photos, 0) do %>
......@@ -20,7 +20,7 @@
</div>
<div class="flex-grow">
<h2 class="font-bold"><%= property.title %></h2>
<h2 class="font-bold text-gray-900 dark:text-white"><%= property.title %></h2>
<p><%= property.description %></p>
<p class="italic"><%= property.location %></p>
<div class="flex flex-row gap-x-2">
......@@ -41,6 +41,6 @@
</div>
</div>
</div>
</div>
</.content_card>
<% end %>
</div>
......@@ -56,7 +56,7 @@
<.input field={f[:price]} id="price" type="text" label="Price" required />
<div phx-feedback-for="property[photos]">
<label for="property_photos" class="block text-sm font-medium text-gray-700">
<label for="property_photos" class="block text-sm font-medium text-gray-700 dark:text-dark-text">
Photos (Upload 1-5 images)
</label>
<input
......
<.flash_group flash={@flash} />
<.header>
<%= @property.title %>
<span><%= @property.title %></span>
<:actions>
<%= if @can_edit do %>
<.link
href={~p"/properties/#{@property.reference}/edit"}
class="bg-zinc-900 hover:bg-zinc-700 text-white px-4 py-2 rounded-md mr-4"
class="bg-zinc-900 hover:bg-zinc-700 text-white hover:text-white bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-md mr-4"
id="edit-property"
>
Edit Property
......@@ -16,7 +16,7 @@
href={~p"/properties/#{@property.reference}"}
method="delete"
data-confirm="Are you sure you want to delete this property?"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700 ml-2"
class="text-white hover:text-white bg-red-600 hover:bg-red-700 rounded-lg px-4 py-2"
id="delete-property"
>
Delete Property
......@@ -26,7 +26,7 @@
<%= if @conn.assigns[:current_user] && !@can_edit do %>
<button
type="button"
class="favorite-button text-2xl mr-4"
class="favorite-button text-2xl mr-4 dark:text-white"
data-property-reference={@property.reference}
data-favorited={if @property.id in @favorites, do: "true", else: "false"}
>
......@@ -34,17 +34,16 @@
</button>
<% end %>
<.link href={~p"/"}>
<.property_link href={~p"/"}>
Back to listings <.icon name="hero-arrow-right-solid" class="h-3 w-3" />
</.link>
</.property_link>
</:actions>
</.header>
<div class="mt-8 bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Property Photos</h3>
<.content_card class="flex flex-col gap-y-4 mt-20">
<.section_title>Property Photos</.section_title>
<div class="relative">
<div class="w-full h-[500px] relative overflow-hidden rounded-lg">
<%= if length(@property.photos) > 0 do %>
<%= for {photo, index} <- Enum.with_index(@property.photos) do %>
......@@ -61,8 +60,8 @@
</div>
<% end %>
<% else %>
<div class="w-full h-full bg-gray-200 flex items-center justify-center">
<span class="text-gray-400">No photos available</span>
<div class="w-full h-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
<span class="text-gray-400 dark:text-gray-300">No photos available</span>
</div>
<% end %>
</div>
......@@ -105,26 +104,21 @@
<% end %>
</div>
<% end %>
</div>
</.content_card>
<div class="mt-8 space-y-8">
<div class="bg-white shadow rounded-lg p-6">
<.content_card>
<dl class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500">Type</dt>
<dd class="mt-1 text-sm text-gray-900">
<%= String.capitalize(to_string(@property.type)) %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Property Type</dt>
<dd class="mt-1 text-sm text-gray-900">
<%= String.capitalize(to_string(@property.property_type)) %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Status</dt>
<dd class="mt-1 text-sm text-gray-900 flex items-center justify-between">
<.detail_item label="Type">
<%= String.capitalize(to_string(@property.type)) %>
</.detail_item>
<.detail_item label="Property Type">
<%= String.capitalize(to_string(@property.property_type)) %>
</.detail_item>
<.detail_item label="Status">
<div class="flex items-center justify-between">
<span id="property-status-text">
<%= cond do %>
<% @property.state == :unavailable and @property.type == :rent -> %>
......@@ -140,7 +134,7 @@
<div>
<button
type="button"
class="status-dropdown-button inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
class="status-dropdown-button inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600"
id="status-menu-button"
aria-expanded="false"
aria-haspopup="true"
......@@ -163,7 +157,7 @@
</button>
</div>
<div
class="status-dropdown-menu hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none"
class="status-dropdown-menu hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-700 ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 dark:divide-gray-600"
role="menu"
aria-orientation="vertical"
aria-labelledby="status-menu-button"
......@@ -171,21 +165,21 @@
>
<div class="py-1" role="none">
<button
class="status-option text-gray-700 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100"
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="available"
>
Available
</button>
<button
class="status-option text-gray-700 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100"
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="reserved"
>
Reserved
</button>
<button
class="status-option text-gray-700 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100"
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="unavailable"
>
......@@ -195,56 +189,54 @@
</div>
</div>
<% end %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Location</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @property.location %></dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Price</dt>
<dd class="mt-1 text-sm text-gray-900">
<%= :erlang.float_to_binary(@property.price, decimals: 2) %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Area</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @property.area %></dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Rooms</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @property.room_count %></dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Floor</dt>
<dd class="mt-1 text-sm text-gray-900">
<%= @property.floor %>/<%= @property.floor_count %>
</dd>
</div>
</div>
</.detail_item>
<.detail_item label="Location">
<%= @property.location %>
</.detail_item>
<.detail_item label="Price">
<%= :erlang.float_to_binary(@property.price, decimals: 2) %>
</.detail_item>
<.detail_item label="Area">
<%= @property.area %>
</.detail_item>
<.detail_item label="Rooms">
<%= @property.room_count %>
</.detail_item>
<.detail_item label="Floor">
<%= @property.floor %>/<%= @property.floor_count %>
</.detail_item>
</dl>
</div>
</.content_card>
<div class="bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900">Description</h3>
<p class="mt-4 text-sm text-gray-600"><%= @property.description %></p>
</div>
<.content_card>
<.section_title>Description</.section_title>
<p class="mt-4 text-sm text-gray-600 dark:text-gray-300"><%= @property.description %></p>
</.content_card>
<div class="bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900">Contact Information</h3>
<.content_card>
<.section_title>Contact Information</.section_title>
<div class="mt-4">
<p class="text-sm text-gray-600">
<p class="text-sm text-gray-600 dark:text-gray-300">
Listed by <%= @property.user.name %> <%= @property.user.surname %>
</p>
<p class="text-sm text-gray-600">Phone: <%= @property.user.phone_number %></p>
<p class="text-sm text-gray-600">Email: <%= @property.user.email %></p>
<p class="text-sm text-gray-600 dark:text-gray-300">
Phone: <%= @property.user.phone_number %>
</p>
<p class="text-sm text-gray-600 dark:text-gray-300">Email: <%= @property.user.email %></p>
</div>
</div>
</.content_card>
<div class="bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900">Similar properties</h3>
<.content_card>
<.section_title>Similar properties</.section_title>
<div class="mt-4">
<%= if @similar_properties == [] do %>
<p class="text-sm text-gray-600">No similar properties found</p>
<p class="text-sm text-gray-600 dark:text-gray-300">No similar properties found</p>
<% else %>
<%= for property <- @similar_properties do %>
<div
......@@ -253,12 +245,15 @@
data-price={property.price}
>
<div>
<p class="text-sm text-gray-600"><%= property.title %></p>
<p class="text-sm text-gray-600">
<p class="text-sm text-gray-600 dark:text-gray-300"><%= property.title %></p>
<p class="text-sm text-gray-600 dark:text-gray-300">
<%= :erlang.float_to_binary(property.price, decimals: 2) %>
</p>
<p class="text-sm text-gray-600"><%= property.location %></p>
<p class="text-sm text-gray-600 dark:text-gray-300"><%= property.location %></p>
</div>
<%!-- <.property_link href={~p"/properties/#{property.reference}"} id={"view-similar-property-#{property.reference}"}>
View
</.property_link> --%>
<a
href={~p"/properties/#{property.reference}"}
class="text-sm text-blue-600 hover:underline"
......@@ -270,7 +265,7 @@
<% end %>
<% end %>
</div>
</div>
</.content_card>
</div>
<script>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment