Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
<template>
  <BaseLayout title="Technician - Profile">
    <div class="profile-container">
      <h2 class="profile-title">Your Profile</h2>

      <!-- User Details Section -->
      <div class="section">
        <h3 class="section-heading">Your Details</h3>
        <div class="details-grid">
          <div class="detail-item">
            <ion-icon :icon="personOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Name</span>
              <span class="detail-value">{{ user?.name }}</span>
            </div>
          </div>
          <div class="detail-item">
            <ion-icon :icon="mailOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Email</span>
              <span class="detail-value">{{ user?.email }}</span>
            </div>
          </div>
        </div>
      </div>

      <!-- Active Organization Section -->
      <div class="section">
        <h3 class="section-heading">Your Active Organization</h3>
        <div class="details-grid">
          <div class="detail-item">
            <ion-icon :icon="peopleOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Organization</span>
              <span class="detail-value">{{ activeOrg?.name }}</span>
            </div>
            <ion-button
              fill="clear"
              size="small"
              @click="navigateTo(`/organization/${activeOrg?.id}`)"
            >
              <ion-icon slot="icon-only" :icon="eyeOutline"></ion-icon>
            </ion-button>
          </div>

          <div class="detail-item">
            <ion-icon :icon="buildOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Your Roles</span>
              <div class="roles-list">
                <ion-chip v-for="role in roles" :key="role.id" color="primary">{{ role?.name }}</ion-chip>
              </div>
            </div>
          </div>

          <div v-if="roles && roles.length > 1" class="detail-item full-width">
            <ion-icon :icon="swapHorizontalOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Switch Role</span>
              <ion-select
                :value="tnfrStore.role"
                placeholder="Select Role"
                @ionChange="tnfrStore.setPreference('role', $event.detail.value)"
                class="org-select"
              >
                <ion-select-option
                  v-for="role in roles"
                  :key="role.id"
                  :value="role.name"
                >
                  {{ role.name }}
                </ion-select-option>
              </ion-select>
            </div>
          </div>

          <div v-if="availableOrgs.length > 1" class="detail-item full-width">
            <ion-icon :icon="swapHorizontalOutline" class="detail-icon"></ion-icon>
            <div class="detail-content">
              <span class="detail-label">Switch Organization</span>
              <ion-select
                :value="activeOrg?.id"
                placeholder="Select Organization"
                @ionChange="switchActiveOrg($event.detail.value)"
                class="org-select"
              >
                <ion-select-option
                  v-for="org in availableOrgs"
                  :key="org.id"
                  :value="org.id"
                >
                  {{ org.name }}
                </ion-select-option>
              </ion-select>
            </div>
          </div>
        </div>
      </div>

      <!-- Admin-only sections -->
      <template v-if="tnfrStore.role === 'admin'">
      <!-- Organization Variables Section -->
      <div class="section">
        <h3 class="section-heading">Organization Variables</h3>
        <p class="section-message">Configure pricing and operational variables for {{ activeOrg?.name }}</p>

        <div class="variables-grid">
          <div v-for="(value, key) in filteredDisplayVariables" :key="key" class="variable-item">
            <div class="variable-content">
              <span class="variable-label">{{ key }}</span>
              <div v-if="editingVariable !== key" class="variable-display">
                <ShowCurrency
                  v-if="isCurrencyVariable(key)"
                  :currencyIn="orgVariables[key]"
                />
                <span v-else class="variable-value">{{ orgVariables[key] }}</span>
              </div>
              <ion-input
                v-if="editingVariable === key"
                :value="value"
                placeholder="Enter value"
                @ionInput="updateVariable(key, $event.detail.value)"
                @ionBlur="stopEditing(key)"
                @keyup.enter="stopEditing(key)"
                class="variable-input"
              ></ion-input>
            </div>
            <div class="variable-actions">
              <ion-button
                v-if="editingVariable !== key"
                fill="clear"
                size="small"
                color="primary"
                @click="startEditing(key)"
              >
                <ion-icon slot="icon-only" :icon="pencilOutline"></ion-icon>
              </ion-button>
              <ion-button
                v-if="editingVariable === key"
                fill="clear"
                size="small"
                color="success"
                @click="stopEditing(key)"
              >
                <ion-icon slot="icon-only" :icon="checkmarkOutline"></ion-icon>
              </ion-button>
            </div>
          </div>
        </div>
      </div>

      <!-- User Management Section -->
      <div class="section">
        <h3 class="section-heading">User Management</h3>
        <p class="section-message">Manage users and their roles within {{ activeOrg?.name }}</p>

        <div class="add-user-wrapper">
          <ion-button fill="solid" color="primary" shape="round" class="add-user-btn" @click="showAddEmailsModal = true">
            <ion-icon slot="icon-only" :icon="addOutline"></ion-icon>
          </ion-button>
        </div>

        <!-- Add Pre-approved Emails Modal -->
        <ion-modal :is-open="showAddEmailsModal" @didDismiss="showAddEmailsModal = false">
          <ion-header>
            <ion-toolbar>
              <ion-title>Pre-approve Emails</ion-title>
              <ion-button slot="end" fill="clear" @click="showAddEmailsModal = false">
                <ion-icon slot="icon-only" :icon="closeOutline"></ion-icon>
              </ion-button>
            </ion-toolbar>
          </ion-header>
          <ion-content class="ion-padding">
            <p class="modal-description">
              Enter email addresses to pre-approve for joining this organization.
              You can type one email or paste multiple emails (one per line).
            </p>

            <div class="email-input-section">
              <ion-textarea
                v-model="emailInput"
                placeholder="Enter emails here (one per line)..."
                :rows="4"
                class="email-textarea"
                @keyup.enter="parseEmails"
              ></ion-textarea>
              <ion-button expand="block" @click="parseEmails" :disabled="!emailInput.trim()">
                Add Email(s)
              </ion-button>
            </div>

            <div v-if="preApprovedEmails.length > 0" class="emails-list">
              <h4 class="emails-list-heading">Pre-approved Emails ({{ preApprovedEmails.length }})</h4>
              <div v-for="email in preApprovedEmails" :key="email" class="email-row">
                <ion-icon :icon="mailOutline" class="email-row-icon"></ion-icon>
                <span class="email-row-text">{{ email }}</span>
                <ion-button fill="clear" color="danger" size="small" @click="removeEmail(email)">
                  <ion-icon slot="icon-only" :icon="trashOutline"></ion-icon>
                </ion-button>
              </div>
            </div>

          </ion-content>
        </ion-modal>

        <ion-loading
          :is-open="loadingProfiles"
          message="Loading users..."
        ></ion-loading>

        <div v-if="!loadingProfiles && orgProfiles && orgProfiles.length > 0" class="users-grid">
          <div v-for="profile in orgProfiles" :key="profile.id" class="user-item">
            <div class="user-avatar">
              <ion-icon :icon="personOutline"></ion-icon>
            </div>
            <div class="user-content">
              <span class="user-email">{{ (profile.expand as any)?.user?.email || "No email" }}</span>
              <span class="user-name">{{ (profile.expand as any)?.user?.name || "Unknown User" }}</span>
            </div>
            <ion-select
              :value="profile.roles"
              :multiple="true"
              placeholder="Select roles..."
              @ionChange="updateUserRoles(profile, $event.detail.value)"
              class="user-roles-select"
            >
              <ion-select-option
                v-for="role in availableRoles"
                :key="role.id"
                :value="role.id"
              >
                {{ role.name }}
              </ion-select-option>
            </ion-select>
          </div>
        </div>

        <div v-if="!loadingProfiles && orgProfiles?.length === 0 && preApprovedEmails.length === 0" class="empty-message">
          <p>No users found in this organization.</p>
        </div>

        <!-- Pre-approved emails waiting for signup -->
        <div v-if="!loadingProfiles && preApprovedEmails.length > 0" class="users-grid pending-users">
          <div v-for="email in preApprovedEmails" :key="email" class="user-item pending">
            <div class="user-avatar">
              <ion-icon :icon="timeOutline"></ion-icon>
            </div>
            <div class="user-content">
              <span class="user-email">{{ email }}</span>
              <span class="user-status">Waiting for user to sign up</span>
            </div>
            <ion-button fill="clear" size="small" @click="removeEmail(email)">
              <ion-icon slot="icon-only" :icon="trashOutline"></ion-icon>
            </ion-button>
          </div>
        </div>
      </div>

      <!-- Available Roles Section -->
      <div class="section">
        <h3 class="section-heading">Available Roles</h3>
        <p class="section-message">Roles that can be assigned to users</p>

        <div v-if="availableRoles && availableRoles.length > 0" class="roles-grid">
          <div v-for="role in availableRoles" :key="role.id" class="role-item">
            <ion-icon :icon="buildOutline" class="role-icon"></ion-icon>
            <span class="role-name">{{ role.name }}</span>
          </div>
        </div>

        <div v-if="availableRoles?.length === 0" class="empty-message">
          <p>No roles defined for this organization.</p>
        </div>
      </div>

      <!-- Invite Link Section -->
      <div class="section">
        <h3 class="section-heading">Invite Link</h3>
        <p class="section-message">Share this link for users to request to join the organization</p>

        <div class="invite-box">
          <pre class="invite-link">https://pricebookplatform.com/organization/{{ activeOrg?.id }}/join</pre>
          <ion-button expand="block" color="secondary" @click="copyOrgLink">
            Copy Link
          </ion-button>
        </div>
      </div>
      </template>
    </div>

  </BaseLayout>
</template>

<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import {
  IonIcon,
  IonChip,
  IonSelect,
  IonSelectOption,
  IonButton,
  IonInput,
  IonLoading,
  IonModal,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonTextarea,
  alertController,
  toastController,
} from "@ionic/vue";

import {
  addOutline,
  buildOutline,
  closeOutline,
  eyeOutline,
  personOutline,
  mailOutline,
  peopleOutline,
  swapHorizontalOutline,
  checkmarkOutline,
  trashOutline,
  timeOutline,
} from "ionicons/icons";
import { mdPencil as pencilOutline } from "@/icons/customIcons";

import { useRouter } from "vue-router";
import { getApi } from "@/dataAccess/getApi";
import BaseLayout from "@/components/BaseLayout.vue";
import ShowCurrency from "@/components/ShowCurrency.vue";
import { useTnfrStore } from "@/stores/tnfr";
import {
  convertToBaseCurrency,
  convertFromBaseCurrency,
} from "@/utils/currencyConverter";
import { useCurrencyStore } from "@/stores/currency";
import { usePreferencesStore } from "@/stores/preferences";

import {
  UsersRecord,
  OrganizationsRecord,
  ProfilesRecord,
  RolesRecord,
  ProfilesResponse,
  RolesResponse,
} from "@/pocketbase-types";

const user = ref<UsersRecord>();
const activeOrg = ref<OrganizationsRecord>();
const roles = ref<RolesRecord[]>();
const availableOrgs = ref<OrganizationsRecord[]>([]);

// Org management state
const orgVariables = ref<Record<string, any>>({});
const displayVariables = ref<Record<string, any>>({});
const editingVariable = ref<string | null>(null);
const orgProfiles = ref<ProfilesResponse[]>([]);
const availableRoles = ref<RolesResponse[]>([]);
const loadingProfiles = ref(true);

// Pre-approved emails modal state
const showAddEmailsModal = ref(false);
const emailInput = ref("");
const preApprovedEmails = ref<string[]>([]);

const currencyStore = useCurrencyStore();
const preferencesStore = usePreferencesStore();
const tnfrStore = useTnfrStore();

let pb: any = null;
let saveTimeout: ReturnType<typeof setTimeout> | null = null;

const router = useRouter();

// List of variables that should be displayed as currency
const currencyVariables = ["hourlyFee", "serviceCallFee"];

const isCurrencyVariable = (key: string): boolean => {
  return currencyVariables.includes(key);
};

// Filter out preApprovedEmails from variables display
const filteredDisplayVariables = computed(() => {
  const filtered: Record<string, any> = {};
  for (const [key, value] of Object.entries(displayVariables.value)) {
    if (key !== 'preApprovedEmails') {
      filtered[key] = value;
    }
  }
  return filtered;
});

const navigateTo = (path: string) => {
  router.push(path);
};

const switchActiveOrg = async (orgId: string) => {
  if (orgId === activeOrg.value?.id) {
    return;
  }

  const selectedOrg = availableOrgs.value.find((org) => org.id === orgId);

  const alert = await alertController.create({
    header: "Switch Organization",
    message: `You are about to switch to "${selectedOrg?.name}". This will log you out and clear all local data. You will need to log back in to continue.`,
    buttons: [
      {
        text: "Cancel",
        role: "cancel",
      },
      {
        text: "Switch Organization",
        handler: async () => {
          await performOrgSwitch(orgId);
        },
      },
    ],
  });

  await alert.present();
};

const performOrgSwitch = async (orgId: string) => {
  try {
    await pb.collection("users").update(user.value?.id, {
      activeOrg: orgId,
    });

    router.push("/auth/logout");
  } catch (error) {
    console.error("Failed to switch organization:", error);
  }
};

const copyOrgLink = async () => {
  try {
    const text = `https://pricebookplatform.com/organization/${activeOrg.value?.id}/join`;
    await navigator.clipboard.writeText(text);

    const toast = await toastController.create({
      message: "Invite link copied to clipboard!",
      duration: 2000,
      color: "success",
    });
    await toast.present();
  } catch (err) {
    console.error("Copy failed", err);
    const toast = await toastController.create({
      message: "Failed to copy link",
      duration: 2000,
      color: "danger",
    });
    await toast.present();
  }
};

const startEditing = (key: string) => {
  editingVariable.value = key;
  if (isCurrencyVariable(key)) {
    const userCurrencyValue = convertFromBaseCurrency(orgVariables.value[key]);
    displayVariables.value[key] = userCurrencyValue;
  } else {
    displayVariables.value[key] = orgVariables.value[key];
  }
};

const stopEditing = async (key: string) => {
  if (editingVariable.value !== key) {
    return;
  }

  if (isCurrencyVariable(key)) {
    const baseCurrencyValue = convertToBaseCurrency(displayVariables.value[key]);
    orgVariables.value[key] = baseCurrencyValue;
  } else {
    const value = displayVariables.value[key];
    if (!isNaN(value) && value !== "" && value !== null) {
      orgVariables.value[key] = parseFloat(value);
    } else {
      orgVariables.value[key] = value;
    }
  }

  editingVariable.value = null;
  await saveVariables();
};

const updateVariable = (key: string, value: any) => {
  displayVariables.value[key] = value;
};

const saveVariables = async () => {
  if (saveTimeout) {
    clearTimeout(saveTimeout);
  }

  saveTimeout = setTimeout(async () => {
    try {
      await pb.collection("organizations").update(activeOrg.value?.id, {
        variables: orgVariables.value,
      });

      const toast = await toastController.create({
        message: "Organization variables updated successfully",
        duration: 2000,
        color: "success",
      });
      await toast.present();
    } catch (error) {
      console.error("Error updating organization variables:", error);
      const toast = await toastController.create({
        message: "Error updating organization variables",
        duration: 3000,
        color: "danger",
      });
      await toast.present();
    }
  }, 300);
};

const parseEmails = async () => {
  if (!emailInput.value.trim()) return;

  // Split by newlines, commas, or semicolons, then clean up
  const newEmails = emailInput.value
    .split(/[\n,;]+/)
    .map(email => email.trim().toLowerCase())
    .filter(email => {
      // Basic email validation
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return email && emailRegex.test(email);
    })
    .filter(email => !preApprovedEmails.value.includes(email)); // Avoid duplicates

  preApprovedEmails.value = [...preApprovedEmails.value, ...newEmails];
  emailInput.value = "";

  // Save to org variables
  orgVariables.value.preApprovedEmails = preApprovedEmails.value;
  await saveVariables();
};

const removeEmail = async (email: string) => {
  preApprovedEmails.value = preApprovedEmails.value.filter(e => e !== email);

  // Save to org variables
  orgVariables.value.preApprovedEmails = preApprovedEmails.value;
  await saveVariables();
};

const updateUserRoles = async (profile: ProfilesResponse, roleIds: string[]) => {
  try {
    await pb.collection("profiles").update(profile.id, {
      roles: roleIds,
    });

    const profileIndex = orgProfiles.value.findIndex((p) => p.id === profile.id);
    if (profileIndex !== -1) {
      orgProfiles.value[profileIndex].roles = roleIds;
    }

    const toast = await toastController.create({
      message: "User roles updated successfully",
      duration: 2000,
      color: "success",
    });
    await toast.present();
  } catch (error) {
    console.error("Error updating user roles:", error);
    const toast = await toastController.create({
      message: "Error updating user roles",
      duration: 3000,
      color: "danger",
    });
    await toast.present();
  }
};

onMounted(async () => {
  try {
    const api = await getApi();
    pb = api.pb;

    // Initialize currency stores
    await preferencesStore.getPreferences();
    await currencyStore.getCurrencies();
    await currencyStore.getRates();

    user.value = pb.authStore.record;
    activeOrg.value = pb.authStore.record.expand.activeOrg;
    const orgProfile = await api.pb
      .collection("profiles")
      .getFirstListItem(`user="${user.value?.id}"`, { expand: "roles" });
    roles.value = (orgProfile?.expand as any).roles;

    const userProfiles = await pb
      .collection("profiles")
      .getFullList(`user="${user.value?.id}"`, { expand: "org" });

    const uniqueOrgs = new Map();
    userProfiles.forEach((profile: any) => {
      if (profile.expand.org) {
        uniqueOrgs.set(profile.expand.org.id, profile.expand.org);
      }
    });
    availableOrgs.value = Array.from(uniqueOrgs.values());

    // Load org management data
    if (activeOrg.value?.id) {
      // Get full organization details with variables
      const fullOrg = await pb.collection("organizations").getOne(activeOrg.value.id);
      orgVariables.value = fullOrg?.variables || {};
      displayVariables.value = { ...orgVariables.value };

      // Load pre-approved emails from org variables
      preApprovedEmails.value = orgVariables.value.preApprovedEmails || [];

      // Get all profiles (users) in this organization
      orgProfiles.value = await pb.collection("profiles").getFullList({
        filter: `org="${activeOrg.value.id}"`,
        expand: "user,roles",
      });

      // Get all roles available in this organization
      availableRoles.value = await pb.collection("roles").getFullList({
        filter: `org="${activeOrg.value.id}"`,
      });
    }
  } catch (error) {
    console.error("Error loading profile data:", error);
  } finally {
    loadingProfiles.value = false;
  }
});
</script>

<style scoped>
:deep(.ion-page) {
  animation: none !important;
  transform: none !important;
  transition: none !important;
}

.profile-container {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
  animation: none !important;
  transform: none !important;
  transition: none !important;
}

.profile-title {
  margin: 0 0 32px 0;
  color: var(--ion-text-color);
  font-size: 32px;
  font-weight: 600;
  text-align: center;
  font-variant: small-caps;
}

.section {
  margin-bottom: 40px;
}

.section-heading {
  margin: 0 0 20px 0;
  color: var(--ion-text-color);
  font-size: 24px;
  font-weight: 700;
  text-align: center;
}

.details-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.detail-item {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.detail-item.full-width {
  flex-direction: column;
  align-items: flex-start;
}

.detail-icon {
  font-size: 24px;
  color: var(--ion-color-primary);
  flex-shrink: 0;
}

.detail-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.detail-label {
  color: var(--ion-color-medium);
  font-size: 14px;
  font-weight: 500;
}

.detail-value {
  color: var(--ion-text-color);
  font-size: 16px;
  font-weight: 600;
}

.roles-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.org-select {
  margin-top: 8px;
  width: 100%;
  max-width: none;
}

.section-message {
  margin: 0 0 24px 0;
  color: var(--ion-color-medium);
  font-size: 16px;
  font-style: italic;
  text-align: center;
}

.add-user-wrapper {
  display: flex;
  justify-content: center;
  margin-bottom: 24px;
}

.add-user-btn {
  --border-radius: 50%;
  width: 48px;
  height: 48px;
}

/* Pre-approved Emails Modal */
.modal-description {
  margin: 0 0 20px 0;
  color: var(--ion-color-medium);
  font-size: 14px;
  line-height: 1.5;
}

.email-input-section {
  margin-bottom: 24px;
}

.email-textarea {
  margin-bottom: 12px;
  background: var(--ion-background-color-step-50);
  border-radius: 8px;
  --padding-start: 12px;
  --padding-end: 12px;
}

.emails-list {
  margin-bottom: 24px;
}

.emails-list-heading {
  margin: 0 0 12px 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ion-text-color);
}

.email-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  background: var(--ion-background-color-step-50);
  border-radius: 8px;
  margin-bottom: 8px;
}

.email-row-icon {
  font-size: 20px;
  color: var(--ion-color-primary);
  flex-shrink: 0;
}

.email-row-text {
  flex: 1;
  font-size: 14px;
  color: var(--ion-text-color);
  word-break: break-all;
}

/* Variables Grid */
.variables-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.variable-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.variable-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.variable-label {
  color: var(--ion-color-medium);
  font-size: 14px;
  font-weight: 600;
  text-transform: capitalize;
}

.variable-display {
  color: var(--ion-text-color);
  font-size: 18px;
  font-weight: 500;
}

.variable-value {
  color: var(--ion-text-color);
  font-size: 18px;
  font-weight: 500;
}

.variable-input {
  font-size: 18px;
  --padding-start: 0;
  --padding-end: 0;
}

.variable-actions {
  display: flex;
  gap: 8px;
}

/* Users Grid */
.users-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.user-item {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.user-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--ion-color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.user-avatar ion-icon {
  font-size: 24px;
  color: white;
}

.user-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.user-email {
  color: var(--ion-text-color);
  font-size: 16px;
  font-weight: 600;
}

.user-name {
  color: var(--ion-color-medium);
  font-size: 14px;
}

.user-roles-select {
  max-width: 200px;
}

/* Pending Users */
.pending-users {
  margin-top: 16px;
}

.user-item.pending {
  border-style: dashed;
  background-color: var(--ion-color-medium);
}

.user-item.pending .user-avatar {
  background: var(--ion-color-medium-shade);
}

.user-item.pending .user-avatar ion-icon {
  color: var(--ion-color-medium-contrast);
}

.user-item.pending .user-email {
  color: var(--ion-color-medium-contrast);
}

.user-item.pending .user-status {
  color: var(--ion-color-medium-contrast);
  opacity: 0.8;
  font-size: 13px;
  font-style: italic;
}

.user-item.pending ion-button {
  --color: var(--ion-color-medium-contrast);
}

/* Roles Grid */
.roles-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.role-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.role-icon {
  font-size: 20px;
  color: var(--ion-color-primary);
}

.role-name {
  color: var(--ion-text-color);
  font-size: 16px;
  font-weight: 500;
}

/* Invite Box */
.invite-box {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.invite-link {
  margin: 0 0 16px 0;
  padding: 12px;
  background: var(--ion-background-color);
  border-radius: 4px;
  color: var(--ion-text-color);
  font-size: 14px;
  word-break: break-all;
  white-space: pre-wrap;
}

.empty-message {
  text-align: center;
  padding: 40px 20px;
  color: var(--ion-color-medium);
  font-style: italic;
}

/* Mobile adjustments */
@media (max-width: 767px) {
  .profile-container {
    padding: 16px;
  }

  .profile-title {
    font-size: 28px;
  }

  .section-heading {
    font-size: 20px;
  }

  .section-message {
    font-size: 14px;
  }

  .detail-item,
  .variable-item,
  .user-item {
    padding: 12px;
  }

  .detail-icon {
    font-size: 20px;
  }

  .user-avatar {
    width: 40px;
    height: 40px;
  }

  .user-avatar ion-icon {
    font-size: 20px;
  }

  .user-roles-select {
    max-width: 150px;
  }
}
</style>