Compare commits
12 Commits
d907878143
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 148cd7c5c6 | |||
| 7d14914b02 | |||
| c21d7000ef | |||
| b7c6bb79e7 | |||
| 346ef5097b | |||
| f0de02ca94 | |||
| 0bef634eb8 | |||
| f8ec35e72f | |||
| 290c4e427f | |||
| 1b4449ae12 | |||
| 4145a92ca7 | |||
| 08b9ad24a5 |
@@ -12,7 +12,7 @@ See: .planning/PROJECT.md (updated 2026-01-14)
|
||||
Phase: 7 of 7 (Testing & Polish)
|
||||
Plan: 3 of 3 in current phase
|
||||
Status: Phase complete - PROJECT COMPLETE!
|
||||
Last activity: 2026-01-29 — Completed Plan 07-03 (Admin Flow & Deployment Prep)
|
||||
Last activity: 2026-01-29 — Completed Quick Task 002: Fix duplicate mentor notifications on job republish
|
||||
|
||||
Progress: ████████████████████ 100%
|
||||
|
||||
@@ -113,10 +113,17 @@ All 7 phases finished. All identified UX/notification issues resolved. System is
|
||||
|
||||
**Production deployment:** Ready to proceed following `.planning/phases/07-testing-polish/DEPLOYMENT-CHECKLIST.md`
|
||||
|
||||
### Quick Tasks Completed
|
||||
|
||||
| # | Description | Date | Commit | Directory |
|
||||
|---|-------------|------|--------|-----------|
|
||||
| 001 | UX & Notification Polish | 2026-01-29 | N/A | [001-ux-notification-polish](./quick/001-ux-notification-polish/) |
|
||||
| 002 | Fix duplicate mentor notifications on job republish | 2026-01-29 | 4145a92 | [002-fix-duplicate-mentor-notifications-on-jo](./quick/002-fix-duplicate-mentor-notifications-on-jo/) |
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-01-29
|
||||
Stopped at: Completed Quick Task 001 (UX & Notification Polish) - all 4 deferred issues resolved
|
||||
Stopped at: Completed Quick Task 002: Fix duplicate mentor notifications on job republish
|
||||
Resume file: None
|
||||
|
||||
**Project Status:** ✅ COMPLETE - All 7 phases finished, all polish items complete, system ready for production deployment
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
phase: quick-002
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- includes/class-notifications.php
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Mentors receive notification when a job is first published"
|
||||
- "Mentors do NOT receive notification when a previously-published job is edited and republished"
|
||||
- "Each job can only trigger mentor notifications once in its lifetime"
|
||||
artifacts:
|
||||
- path: "includes/class-notifications.php"
|
||||
provides: "Duplicate notification guard using post meta"
|
||||
contains: "_ddhh_mentors_notified"
|
||||
key_links:
|
||||
- from: "notify_mentors_on_job_publish"
|
||||
to: "post_meta _ddhh_mentors_notified"
|
||||
via: "get_post_meta check before scheduling, update_post_meta after scheduling"
|
||||
pattern: "get_post_meta.*_ddhh_mentors_notified"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Fix duplicate mentor notifications when a job is edited and republished.
|
||||
|
||||
Purpose: Mentors currently receive "new job" notifications every time a job transitions to
|
||||
publish status -- including after edits (pending -> publish). The notification should only
|
||||
fire once per job, on the very first publication. Subsequent republications after edits
|
||||
should be silent for mentors (the admin already gets a separate edit notification).
|
||||
|
||||
Output: Updated `notify_mentors_on_job_publish` method with a post meta guard that prevents
|
||||
duplicate notifications.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@includes/class-notifications.php
|
||||
@includes/class-scheduler.php
|
||||
|
||||
The bug is in `notify_mentors_on_job_publish()` (line ~430). The current guard:
|
||||
|
||||
```php
|
||||
if ( 'publish' !== $new_status || 'publish' === $old_status ) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
This correctly prevents re-notification when a published post is updated (publish -> publish),
|
||||
but it does NOT prevent re-notification when a previously-published job is edited and reset
|
||||
to pending (per decision 03-02: "Post status reset to pending after edit"), then republished
|
||||
by admin (pending -> publish). This second publish triggers notifications again.
|
||||
|
||||
The job lifecycle that causes the bug:
|
||||
1. Provider submits -> status: pending
|
||||
2. Admin publishes -> pending to publish -> mentors notified (CORRECT)
|
||||
3. Provider edits -> status reset to pending (per 03-02)
|
||||
4. Admin republishes -> pending to publish -> mentors notified AGAIN (BUG)
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add post meta guard to prevent duplicate mentor notifications</name>
|
||||
<files>includes/class-notifications.php</files>
|
||||
<action>
|
||||
In the `notify_mentors_on_job_publish` method, add a post meta check AFTER the existing
|
||||
status transition guard (line ~437) and BEFORE the mentor query (line ~442):
|
||||
|
||||
1. Check if post meta `_ddhh_mentors_notified` exists and equals `'1'` for this post.
|
||||
If it does, log a message like "DDHH Job Manager: Skipping mentor notification for
|
||||
job #%d - mentors already notified on initial publish" and return early.
|
||||
|
||||
2. After the `DDHH_JM_Scheduler::schedule_mentor_notification_batch()` call succeeds
|
||||
(line ~457), set post meta: `update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' )`.
|
||||
|
||||
Use underscore-prefixed meta key `_ddhh_mentors_notified` so it is hidden from the
|
||||
WordPress custom fields UI.
|
||||
|
||||
Do NOT change any other logic in this method or file. The existing status transition
|
||||
guard should remain as-is (it still serves a purpose for publish->publish transitions).
|
||||
</action>
|
||||
<verify>
|
||||
1. Read the modified file and confirm:
|
||||
- `get_post_meta( $post->ID, '_ddhh_mentors_notified', true )` check exists before mentor query
|
||||
- `update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' )` exists after scheduling
|
||||
- No other methods were modified
|
||||
2. Run `php -l includes/class-notifications.php` to confirm no syntax errors
|
||||
</verify>
|
||||
<done>
|
||||
The `notify_mentors_on_job_publish` method checks for `_ddhh_mentors_notified` post meta
|
||||
before scheduling notifications, and sets that meta after scheduling. This ensures mentors
|
||||
are only notified once per job, regardless of how many times the job transitions through
|
||||
pending -> publish.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `php -l includes/class-notifications.php` passes with no syntax errors
|
||||
2. The `_ddhh_mentors_notified` meta key is checked before notification scheduling
|
||||
3. The `_ddhh_mentors_notified` meta key is set after successful scheduling
|
||||
4. No other notification methods were modified
|
||||
5. The existing `transition_post_status` guard logic is preserved
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- First publish of a job (pending -> publish) triggers mentor notifications and sets meta flag
|
||||
- Subsequent publishes of the same job (pending -> publish after edit) skip mentor notifications
|
||||
- Admin notifications for edits/republications are unaffected
|
||||
- No PHP syntax errors introduced
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/quick/002-fix-duplicate-mentor-notifications-on-jo/002-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
phase: quick-002
|
||||
plan: 01
|
||||
subsystem: notifications
|
||||
tags: [mentor-notifications, duplicate-prevention, post-meta, bug-fix]
|
||||
requires: [05-01, 05-03]
|
||||
provides:
|
||||
- Duplicate notification prevention using post meta
|
||||
- One-time mentor notification guarantee per job
|
||||
affects: []
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Post meta flags for one-time event tracking"
|
||||
- "Idempotent notification scheduling"
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- includes/class-notifications.php
|
||||
decisions:
|
||||
- key: "Use post meta `_ddhh_mentors_notified` for duplicate prevention"
|
||||
context: "Need to track whether mentors have been notified for a specific job"
|
||||
options: ["Post meta flag", "Custom database table", "Transient cache"]
|
||||
chosen: "Post meta flag"
|
||||
rationale: "Simple, reliable, persists forever, underscore-prefix hides from UI"
|
||||
- key: "Set meta flag after scheduling, not after emails sent"
|
||||
context: "When to mark job as notified"
|
||||
options: ["After scheduling batch", "After all emails sent", "Before scheduling"]
|
||||
chosen: "After scheduling batch"
|
||||
rationale: "Scheduling is synchronous and reliable; email sending is async and could fail individually"
|
||||
metrics:
|
||||
duration: 2
|
||||
completed: 2026-01-29
|
||||
---
|
||||
|
||||
# Quick Task 002: Fix Duplicate Mentor Notifications
|
||||
|
||||
**One-liner:** Post meta guard prevents mentor re-notification on job republish after edits
|
||||
|
||||
## What Was Built
|
||||
|
||||
Fixed a bug where mentors received duplicate "new job" notifications when a job was edited and republished.
|
||||
|
||||
**The Problem:**
|
||||
The job lifecycle includes a status reset to `pending` after provider edits (decision 03-02). This means:
|
||||
1. Provider submits → pending
|
||||
2. Admin publishes → pending to publish → mentors notified ✓
|
||||
3. Provider edits → status reset to pending
|
||||
4. Admin republishes → pending to publish → mentors notified AGAIN ✗
|
||||
|
||||
The existing guard `if ( 'publish' === $old_status )` only prevented publish→publish transitions, not pending→publish after edits.
|
||||
|
||||
**The Solution:**
|
||||
Added a post meta flag `_ddhh_mentors_notified` that:
|
||||
- Is checked before scheduling notifications (early return if already set)
|
||||
- Is set after successful batch scheduling
|
||||
- Persists for the lifetime of the job post
|
||||
- Uses underscore prefix to hide from WordPress custom fields UI
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
**Modified:** `includes/class-notifications.php`
|
||||
|
||||
1. **Pre-scheduling guard** (after line 439):
|
||||
- `get_post_meta( $post->ID, '_ddhh_mentors_notified', true )`
|
||||
- If value is `'1'`, log skip message and return early
|
||||
- Prevents duplicate scheduling entirely
|
||||
|
||||
2. **Post-scheduling flag** (after line 469):
|
||||
- `update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' )`
|
||||
- Sets flag after `schedule_mentor_notification_batch()` succeeds
|
||||
- Ensures future republish attempts skip notification
|
||||
|
||||
**Why after scheduling, not after sending?**
|
||||
- Scheduling is synchronous and happens immediately
|
||||
- Email sending is async (Action Scheduler batches)
|
||||
- Individual emails might fail, but we still don't want re-notifications
|
||||
- The intent to notify has been recorded, which is what matters
|
||||
|
||||
## Verification
|
||||
|
||||
**Syntax Check:**
|
||||
```bash
|
||||
php -l includes/class-notifications.php
|
||||
# No syntax errors detected
|
||||
```
|
||||
|
||||
**Code Review:**
|
||||
- ✅ `get_post_meta` check exists before mentor query
|
||||
- ✅ `update_post_meta` exists after scheduling
|
||||
- ✅ No other notification methods modified
|
||||
- ✅ Existing publish→publish guard preserved
|
||||
- ✅ Underscore-prefixed meta key (hidden from UI)
|
||||
|
||||
## Job Lifecycle Flow (After Fix)
|
||||
|
||||
1. **Initial submission** (pending):
|
||||
- No notification (job not published yet)
|
||||
- `_ddhh_mentors_notified` = not set
|
||||
|
||||
2. **First publish** (pending → publish):
|
||||
- ✅ Mentors notified
|
||||
- `_ddhh_mentors_notified` = '1'
|
||||
|
||||
3. **Provider edits** (publish → pending):
|
||||
- No notification (deactivation)
|
||||
- `_ddhh_mentors_notified` = still '1'
|
||||
|
||||
4. **Republish after edit** (pending → publish):
|
||||
- ❌ Mentors NOT notified (meta flag prevents it)
|
||||
- `_ddhh_mentors_notified` = still '1'
|
||||
|
||||
5. **Any future republish**:
|
||||
- ❌ Mentors NOT notified (meta flag persists)
|
||||
|
||||
## Impact
|
||||
|
||||
**Fixed:**
|
||||
- Mentors no longer receive duplicate notifications for the same job
|
||||
- Reduces notification fatigue and confusion
|
||||
- Maintains trust in the notification system
|
||||
|
||||
**Unchanged:**
|
||||
- Admin notifications for edits (still sent, as intended)
|
||||
- First-time publish notifications (still sent)
|
||||
- Publish→publish guard (still prevents updates to published jobs)
|
||||
- Mentor opt-in/opt-out functionality
|
||||
|
||||
**Edge Cases Handled:**
|
||||
- Job edited multiple times: Only first publish notifies
|
||||
- Job unpublished and republished: Only first publish notifies
|
||||
- Job trashed and restored: Meta persists, no re-notification
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Testing Notes
|
||||
|
||||
**To verify in production:**
|
||||
|
||||
1. Create test job, submit as provider → status: pending
|
||||
2. Admin publishes → Check Action Scheduler for mentor notification batches
|
||||
3. Provider edits job → status: pending
|
||||
4. Admin republishes → Check Action Scheduler - should see NO new batches
|
||||
5. Check error_log for: "Skipping mentor notification for job #X - mentors already notified on initial publish"
|
||||
|
||||
**Manual database check:**
|
||||
```sql
|
||||
SELECT post_id, meta_value
|
||||
FROM wp_postmeta
|
||||
WHERE meta_key = '_ddhh_mentors_notified';
|
||||
```
|
||||
|
||||
Should show '1' for all published jobs that have triggered notifications.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Blockers:** None
|
||||
**Dependencies satisfied:** Builds on existing notification system (05-01, 05-03)
|
||||
**Follow-up needed:** None - fix is complete and self-contained
|
||||
|
||||
This was a targeted bug fix with no architectural implications. The notification system is now fully idempotent - each job triggers exactly one mentor notification batch in its lifetime.
|
||||
165
assets/css/auth-forms.css
Normal file
165
assets/css/auth-forms.css
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Auth Forms Styles — Anbieter Login Page
|
||||
*
|
||||
* Matches the Mentor:innen Login styles from general.css.
|
||||
* Loaded only on the anbieter-login page to override baked-in inline CSS.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
/* -----------------------------------------------
|
||||
Layout: two-column container
|
||||
----------------------------------------------- */
|
||||
.ddhh-auth-container {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ddhh-auth-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Sections: remove gray card backgrounds
|
||||
----------------------------------------------- */
|
||||
.ddhh-register-section,
|
||||
.ddhh-login-section {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ddhh-register-section h2,
|
||||
.ddhh-login-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Labels: match Mentor Login (color #333, 18px, bold)
|
||||
High specificity to override Formidable's
|
||||
.with_frm_style .frm_primary_label selectors
|
||||
----------------------------------------------- */
|
||||
.ddhh-auth-container label,
|
||||
.ddhh-auth-container .frm_forms.with_frm_style .frm_primary_label {
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Inputs: match Mentor Login field sizing
|
||||
----------------------------------------------- */
|
||||
.ddhh-auth-container input[type="text"],
|
||||
.ddhh-auth-container input[type="email"],
|
||||
.ddhh-auth-container input[type="password"],
|
||||
.ddhh-auth-container input[type="url"],
|
||||
.ddhh-auth-container input[type="tel"],
|
||||
.ddhh-auth-container select {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
max-width: 350px;
|
||||
border: 0.0625rem solid;
|
||||
border-radius: 3px;
|
||||
padding: 0.1875rem 0.3125rem;
|
||||
margin: 0 6px 16px 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
font-family: "Poppins", Sans, Helvetica, Arial;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Formidable submit wrapper: override flex-row
|
||||
so the button stretches to full width
|
||||
----------------------------------------------- */
|
||||
.ddhh-auth-container .frm_submit.frm_flex {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Buttons: navy pill with red hover
|
||||
High specificity to override Formidable's
|
||||
.frm_style_formidable-style.with_frm_style selectors
|
||||
----------------------------------------------- */
|
||||
.ddhh-auth-container input[type="submit"],
|
||||
.ddhh-auth-container button[type="submit"],
|
||||
.ddhh-auth-container .frm_forms.with_frm_style .frm_submit button.frm_button_submit {
|
||||
background-color: var(--wp--preset--color--primary, #003063);
|
||||
border-width: 0;
|
||||
color: #fff;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
padding: 0.4em 1.333em;
|
||||
border-radius: 100px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
cursor: pointer;
|
||||
transition: all 0.7s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ddhh-auth-container input[type="submit"]:hover,
|
||||
.ddhh-auth-container button[type="submit"]:hover,
|
||||
.ddhh-auth-container .frm_forms.with_frm_style .frm_submit button.frm_button_submit:hover {
|
||||
background-color: var(--wp--preset--color--hhred, #E40613);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
wp_login_form() specific: style the <p> wrappers
|
||||
----------------------------------------------- */
|
||||
.ddhh-login-section .login-username,
|
||||
.ddhh-login-section .login-password {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-remember,
|
||||
.ddhh-login-section .login-submit {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-username br,
|
||||
.ddhh-login-section .login-password br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-username label,
|
||||
.ddhh-login-section .login-password label {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-remember label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------
|
||||
Passwort vergessen link
|
||||
----------------------------------------------- */
|
||||
.ddhh-login-section .login-lost-password {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-lost-password a {
|
||||
color: var(--wp--preset--color--primary, #003063);
|
||||
text-decoration: underline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ddhh-login-section .login-lost-password a:hover {
|
||||
color: var(--wp--preset--color--hhred, #E40613);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Digital Dabei Job Manager
|
||||
* Plugin URI: https://www.hamburg.de/digital-dabei
|
||||
* Plugin URI: https://hamburg-digital-dabei.de
|
||||
* Description: Closed job board for provider self-registration and mentor applications
|
||||
* Version: 1.0.0
|
||||
* Author: digital dabei Hamburg
|
||||
* Author URI: https://www.hamburg.de/digital-dabei
|
||||
* Author: vihais
|
||||
* Author URI: https://hamburg-digital-dabei.de
|
||||
* Text Domain: ddhh-job-manager
|
||||
* Domain Path: /languages
|
||||
* Requires at least: 6.0
|
||||
@@ -45,8 +45,13 @@ require_once DDHH_JM_PLUGIN_DIR . 'includes/class-template.php';
|
||||
require_once DDHH_JM_PLUGIN_DIR . 'includes/class-admin-ui.php';
|
||||
require_once DDHH_JM_PLUGIN_DIR . 'includes/class-user-preferences.php';
|
||||
require_once DDHH_JM_PLUGIN_DIR . 'includes/class-scheduler.php';
|
||||
require_once DDHH_JM_PLUGIN_DIR . 'includes/class-elementor-tags.php';
|
||||
require_once DDHH_JM_PLUGIN_DIR . 'includes/class-ddhh-job-manager.php';
|
||||
|
||||
// Register activation and deactivation hooks (must be at top level).
|
||||
register_activation_hook( __FILE__, array( 'DDHH_JM_Activator', 'activate' ) );
|
||||
register_deactivation_hook( __FILE__, array( 'DDHH_JM_Deactivator', 'deactivate' ) );
|
||||
|
||||
/**
|
||||
* Initialize the plugin.
|
||||
*/
|
||||
@@ -54,3 +59,11 @@ function ddhh_jm_init() {
|
||||
DDHH_JM_Job_Manager::get_instance();
|
||||
}
|
||||
add_action( 'plugins_loaded', 'ddhh_jm_init', 10 );
|
||||
|
||||
/**
|
||||
* Initialize Elementor dynamic tags when Elementor is loaded.
|
||||
*/
|
||||
function ddhh_jm_elementor_init() {
|
||||
DDHH_JM_Elementor_Tags::init();
|
||||
}
|
||||
add_action( 'elementor/init', 'ddhh_jm_elementor_init' );
|
||||
|
||||
@@ -68,17 +68,6 @@ class DDHH_JM_ACF_Fields {
|
||||
'type' => 'email',
|
||||
'required' => 1,
|
||||
),
|
||||
// Job Logo
|
||||
array(
|
||||
'key' => 'field_job_logo',
|
||||
'label' => __( 'Logo', 'ddhh-job-manager' ),
|
||||
'name' => 'job_logo',
|
||||
'type' => 'image',
|
||||
'required' => 0,
|
||||
'return_format' => 'id',
|
||||
'preview_size' => 'thumbnail',
|
||||
'library' => 'all',
|
||||
),
|
||||
// Job Deactivation Reason (internal, admin-only)
|
||||
array(
|
||||
'key' => 'field_job_deactivation_reason',
|
||||
|
||||
@@ -43,10 +43,6 @@ class DDHH_JM_Job_Manager {
|
||||
* Initialize hooks
|
||||
*/
|
||||
private function init_hooks() {
|
||||
// Register activation and deactivation hooks
|
||||
register_activation_hook( DDHH_JM_PLUGIN_FILE, array( 'DDHH_JM_Activator', 'activate' ) );
|
||||
register_deactivation_hook( DDHH_JM_PLUGIN_FILE, array( 'DDHH_JM_Deactivator', 'deactivate' ) );
|
||||
|
||||
// Initialize post types
|
||||
add_action( 'init', array( 'DDHH_JM_Post_Types', 'register' ) );
|
||||
|
||||
|
||||
58
includes/class-elementor-tags.php
Normal file
58
includes/class-elementor-tags.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor Dynamic Tags integration.
|
||||
*
|
||||
* Registers custom dynamic tags for job offer fields so they appear
|
||||
* in the Elementor editor's dynamic tags dropdown under a
|
||||
* "Stellenangebot" group.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class DDHH_JM_Elementor_Tags
|
||||
*/
|
||||
class DDHH_JM_Elementor_Tags {
|
||||
|
||||
/**
|
||||
* Group slug used for all job offer tags.
|
||||
*/
|
||||
const GROUP_SLUG = 'ddhh-job-offer';
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'elementor/dynamic_tags/register', array( __CLASS__, 'register_tags' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the dynamic tag group and individual tags.
|
||||
*
|
||||
* @param \Elementor\Core\DynamicTags\Manager $manager Elementor dynamic tags manager.
|
||||
*/
|
||||
public static function register_tags( $manager ) {
|
||||
$manager->register_group(
|
||||
self::GROUP_SLUG,
|
||||
array(
|
||||
'title' => 'Stellenangebot',
|
||||
)
|
||||
);
|
||||
|
||||
$tag_dir = DDHH_JM_PLUGIN_DIR . 'includes/elementor-tags/';
|
||||
|
||||
require_once $tag_dir . 'class-tag-job-location.php';
|
||||
require_once $tag_dir . 'class-tag-job-type.php';
|
||||
require_once $tag_dir . 'class-tag-job-deadline.php';
|
||||
require_once $tag_dir . 'class-tag-job-contact-email.php';
|
||||
require_once $tag_dir . 'class-tag-job-logo.php';
|
||||
|
||||
$manager->register( new DDHH_JM_Tag_Job_Location() );
|
||||
$manager->register( new DDHH_JM_Tag_Job_Type() );
|
||||
$manager->register( new DDHH_JM_Tag_Job_Deadline() );
|
||||
$manager->register( new DDHH_JM_Tag_Job_Contact_Email() );
|
||||
$manager->register( new DDHH_JM_Tag_Job_Logo() );
|
||||
}
|
||||
}
|
||||
@@ -156,6 +156,7 @@ class DDHH_JM_Formidable {
|
||||
add_action( 'init', array( __CLASS__, 'create_registration_form' ), 11 );
|
||||
add_action( 'init', array( __CLASS__, 'create_job_submission_form' ), 11 );
|
||||
add_action( 'init', array( __CLASS__, 'create_job_edit_form' ), 11 );
|
||||
add_action( 'init', array( __CLASS__, 'repair_job_form_fields' ), 12 );
|
||||
add_action( 'init', array( __CLASS__, 'create_job_deactivation_form' ), 11 );
|
||||
add_action( 'init', array( __CLASS__, 'create_job_application_form' ), 11 );
|
||||
|
||||
@@ -474,7 +475,6 @@ class DDHH_JM_Formidable {
|
||||
$job_type = '';
|
||||
$job_deadline = '';
|
||||
$job_contact_email = '';
|
||||
$job_logo = '';
|
||||
|
||||
foreach ( $entry->metas as $field_id => $value ) {
|
||||
$field = FrmField::getOne( $field_id );
|
||||
@@ -501,9 +501,6 @@ class DDHH_JM_Formidable {
|
||||
case 'job_contact_email':
|
||||
$job_contact_email = sanitize_email( $value );
|
||||
break;
|
||||
case 'job_logo':
|
||||
$job_logo = $value; // File ID
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,12 +538,6 @@ class DDHH_JM_Formidable {
|
||||
update_post_meta( $post_id, 'job_deadline', $job_deadline );
|
||||
}
|
||||
|
||||
// Handle logo upload if present
|
||||
if ( ! empty( $job_logo ) && is_numeric( $job_logo ) ) {
|
||||
set_post_thumbnail( $post_id, absint( $job_logo ) );
|
||||
error_log( 'DDHH Job Submission: Logo set as featured image' );
|
||||
}
|
||||
|
||||
error_log( 'DDHH Job Submission: Job offer created successfully' );
|
||||
|
||||
do_action( 'ddhh_job_submitted', $post_id, $entry_id );
|
||||
@@ -599,7 +590,6 @@ class DDHH_JM_Formidable {
|
||||
$job_type = '';
|
||||
$job_deadline = '';
|
||||
$job_contact_email = '';
|
||||
$job_logo = '';
|
||||
|
||||
foreach ( $entry->metas as $field_id => $value ) {
|
||||
$field = FrmField::getOne( $field_id );
|
||||
@@ -632,10 +622,6 @@ class DDHH_JM_Formidable {
|
||||
case 'job_contact_email2':
|
||||
$job_contact_email = sanitize_email( $value );
|
||||
break;
|
||||
case 'job_logo':
|
||||
case 'job_logo2':
|
||||
$job_logo = $value; // File ID
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,12 +658,6 @@ class DDHH_JM_Formidable {
|
||||
update_post_meta( $post_id, 'job_deadline', $job_deadline );
|
||||
}
|
||||
|
||||
// Handle logo upload if present
|
||||
if ( ! empty( $job_logo ) && is_numeric( $job_logo ) ) {
|
||||
set_post_thumbnail( $post_id, absint( $job_logo ) );
|
||||
error_log( 'DDHH Job Edit: Logo updated' );
|
||||
}
|
||||
|
||||
error_log( 'DDHH Job Edit: Job offer updated successfully' );
|
||||
|
||||
do_action( 'ddhh_job_edited', $post_id, $entry_id );
|
||||
@@ -835,13 +815,7 @@ class DDHH_JM_Formidable {
|
||||
'required' => '1',
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 4,
|
||||
'field_options' => array(
|
||||
'options' => array(
|
||||
'Vollzeit' => 'Vollzeit',
|
||||
'Teilzeit' => 'Teilzeit',
|
||||
'Ehrenamt' => 'Ehrenamt',
|
||||
),
|
||||
),
|
||||
'options' => array( '', 'Vollzeit', 'Teilzeit', 'Ehrenamt' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'Bewerbungsfrist',
|
||||
@@ -862,19 +836,6 @@ class DDHH_JM_Formidable {
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 6,
|
||||
),
|
||||
array(
|
||||
'name' => 'Logo',
|
||||
'field_key' => 'job_logo',
|
||||
'type' => 'file',
|
||||
'required' => '0',
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 7,
|
||||
'field_options' => array(
|
||||
'restrict' => '1',
|
||||
'allowed_types' => 'image/jpeg,image/png',
|
||||
'max_size' => '2',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Store field IDs for form action mapping
|
||||
@@ -888,49 +849,20 @@ class DDHH_JM_Formidable {
|
||||
|
||||
// Create the Create Post action
|
||||
if ( ! empty( $field_ids ) ) {
|
||||
$action_values = array(
|
||||
'menu_order' => 1,
|
||||
'post_status' => 'published',
|
||||
'post_content' => array(
|
||||
'post_type' => 'job_offer',
|
||||
'post_status' => 'pending',
|
||||
'post_title' => $field_ids['job_title'],
|
||||
'post_content' => $field_ids['job_description'],
|
||||
'post_author' => 'current_user',
|
||||
'post_custom_fields' => array(
|
||||
array(
|
||||
'meta_name' => 'job_location',
|
||||
'field_id' => $field_ids['job_location'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_type',
|
||||
'field_id' => $field_ids['job_type'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_deadline',
|
||||
'field_id' => $field_ids['job_deadline'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_contact_email',
|
||||
'field_id' => $field_ids['job_contact_email'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_logo',
|
||||
'field_id' => $field_ids['job_logo'],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Create the form action using the proper API
|
||||
FrmFormActionsController::create_action(
|
||||
$form_id,
|
||||
array(
|
||||
'post_excerpt' => 'wppost',
|
||||
'post_content' => $action_values,
|
||||
'menu_order' => 1,
|
||||
)
|
||||
$action_control = FrmFormActionsController::get_form_actions( 'wppost' );
|
||||
$new_action = $action_control->prepare_new( $form_id );
|
||||
$new_action->post_content['post_type'] = 'job_offer';
|
||||
$new_action->post_content['post_status'] = 'pending';
|
||||
$new_action->post_content['post_title'] = $field_ids['job_title'];
|
||||
$new_action->post_content['post_content'] = $field_ids['job_description'];
|
||||
$new_action->post_content['post_author'] = 'current_user';
|
||||
$new_action->post_content['post_custom_fields'] = array(
|
||||
array( 'meta_name' => 'job_location', 'field_id' => $field_ids['job_location'] ),
|
||||
array( 'meta_name' => 'job_type', 'field_id' => $field_ids['job_type'] ),
|
||||
array( 'meta_name' => 'job_deadline', 'field_id' => $field_ids['job_deadline'] ),
|
||||
array( 'meta_name' => 'job_contact_email', 'field_id' => $field_ids['job_contact_email'] ),
|
||||
);
|
||||
$action_control->save_settings( $new_action );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,13 +937,7 @@ class DDHH_JM_Formidable {
|
||||
'required' => '1',
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 4,
|
||||
'field_options' => array(
|
||||
'options' => array(
|
||||
'Vollzeit' => 'Vollzeit',
|
||||
'Teilzeit' => 'Teilzeit',
|
||||
'Ehrenamt' => 'Ehrenamt',
|
||||
),
|
||||
),
|
||||
'options' => array( '', 'Vollzeit', 'Teilzeit', 'Ehrenamt' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'Bewerbungsfrist',
|
||||
@@ -1032,19 +958,6 @@ class DDHH_JM_Formidable {
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 6,
|
||||
),
|
||||
array(
|
||||
'name' => 'Logo',
|
||||
'field_key' => 'job_logo',
|
||||
'type' => 'file',
|
||||
'required' => '0',
|
||||
'form_id' => $form_id,
|
||||
'field_order' => 7,
|
||||
'field_options' => array(
|
||||
'restrict' => '1',
|
||||
'allowed_types' => 'image/jpeg,image/png',
|
||||
'max_size' => '2',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Store field IDs for form action mapping
|
||||
@@ -1058,52 +971,62 @@ class DDHH_JM_Formidable {
|
||||
|
||||
// Create the Update Post action
|
||||
if ( ! empty( $field_ids ) ) {
|
||||
$action_values = array(
|
||||
'menu_order' => 1,
|
||||
'post_status' => 'published',
|
||||
'post_content' => array(
|
||||
'post_type' => 'job_offer',
|
||||
'post_status' => 'pending',
|
||||
'post_title' => $field_ids['job_title'],
|
||||
'post_content' => $field_ids['job_description'],
|
||||
'post_id' => 'id_param',
|
||||
'post_custom_fields' => array(
|
||||
array(
|
||||
'meta_name' => 'job_location',
|
||||
'field_id' => $field_ids['job_location'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_type',
|
||||
'field_id' => $field_ids['job_type'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_deadline',
|
||||
'field_id' => $field_ids['job_deadline'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_contact_email',
|
||||
'field_id' => $field_ids['job_contact_email'],
|
||||
),
|
||||
array(
|
||||
'meta_name' => 'job_logo',
|
||||
'field_id' => $field_ids['job_logo'],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Create the form action using the proper API
|
||||
FrmFormActionsController::create_action(
|
||||
$form_id,
|
||||
array(
|
||||
'post_excerpt' => 'wppost',
|
||||
'post_content' => $action_values,
|
||||
'menu_order' => 1,
|
||||
)
|
||||
$action_control = FrmFormActionsController::get_form_actions( 'wppost' );
|
||||
$new_action = $action_control->prepare_new( $form_id );
|
||||
$new_action->post_content['post_type'] = 'job_offer';
|
||||
$new_action->post_content['post_status'] = 'pending';
|
||||
$new_action->post_content['post_title'] = $field_ids['job_title'];
|
||||
$new_action->post_content['post_content'] = $field_ids['job_description'];
|
||||
$new_action->post_content['post_id'] = 'id_param';
|
||||
$new_action->post_content['post_custom_fields'] = array(
|
||||
array( 'meta_name' => 'job_location', 'field_id' => $field_ids['job_location'] ),
|
||||
array( 'meta_name' => 'job_type', 'field_id' => $field_ids['job_type'] ),
|
||||
array( 'meta_name' => 'job_deadline', 'field_id' => $field_ids['job_deadline'] ),
|
||||
array( 'meta_name' => 'job_contact_email', 'field_id' => $field_ids['job_contact_email'] ),
|
||||
);
|
||||
$action_control->save_settings( $new_action );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair existing job form fields in the database.
|
||||
*
|
||||
* Fixes the job_type select options and removes the job_logo field
|
||||
* from both the submission and edit forms. Runs once and stores a
|
||||
* version flag in wp_options to avoid re-running.
|
||||
*/
|
||||
public static function repair_job_form_fields() {
|
||||
if ( ! class_exists( 'FrmField' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repair_version = '1';
|
||||
if ( get_option( 'ddhh_jm_form_repair_version' ) === $repair_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix job_type options on both forms.
|
||||
$correct_options = array( '', 'Vollzeit', 'Teilzeit', 'Ehrenamt' );
|
||||
foreach ( array( 'job_type', 'job_type2' ) as $key ) {
|
||||
$field = FrmField::getOne( $key );
|
||||
if ( $field ) {
|
||||
FrmField::update( $field->id, array(
|
||||
'options' => serialize( $correct_options ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove job_logo fields from both forms.
|
||||
foreach ( array( 'job_logo', 'job_logo2' ) as $key ) {
|
||||
$field = FrmField::getOne( $key );
|
||||
if ( $field ) {
|
||||
FrmField::destroy( $field->id );
|
||||
}
|
||||
}
|
||||
|
||||
update_option( 'ddhh_jm_form_repair_version', $repair_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-populate edit form fields with existing post data
|
||||
*
|
||||
@@ -1159,11 +1082,6 @@ class DDHH_JM_Formidable {
|
||||
case 'job_contact_email2':
|
||||
$value = get_post_meta( $post_id, 'job_contact_email', true );
|
||||
return $value ? $value : $default_value;
|
||||
|
||||
case 'job_logo':
|
||||
case 'job_logo2':
|
||||
$value = get_post_thumbnail_id( $post_id );
|
||||
return $value ? $value : $default_value;
|
||||
}
|
||||
|
||||
return $default_value;
|
||||
@@ -1277,31 +1195,15 @@ class DDHH_JM_Formidable {
|
||||
|
||||
// Create the Update Post action
|
||||
if ( ! empty( $field_ids ) ) {
|
||||
$action_values = array(
|
||||
'menu_order' => 1,
|
||||
'post_status' => 'published',
|
||||
'post_content' => array(
|
||||
'post_type' => 'job_offer',
|
||||
'post_status' => 'draft',
|
||||
'post_id' => 'id_param',
|
||||
'post_custom_fields' => array(
|
||||
array(
|
||||
'meta_name' => 'job_deactivation_reason',
|
||||
'field_id' => $field_ids['deactivation_reason'],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Create the form action using the proper API
|
||||
FrmFormActionsController::create_action(
|
||||
$form_id,
|
||||
array(
|
||||
'post_excerpt' => 'wppost',
|
||||
'post_content' => $action_values,
|
||||
'menu_order' => 1,
|
||||
)
|
||||
$action_control = FrmFormActionsController::get_form_actions( 'wppost' );
|
||||
$new_action = $action_control->prepare_new( $form_id );
|
||||
$new_action->post_content['post_type'] = 'job_offer';
|
||||
$new_action->post_content['post_status'] = 'draft';
|
||||
$new_action->post_content['post_id'] = 'id_param';
|
||||
$new_action->post_content['post_custom_fields'] = array(
|
||||
array( 'meta_name' => 'job_deactivation_reason', 'field_id' => $field_ids['deactivation_reason'] ),
|
||||
);
|
||||
$action_control->save_settings( $new_action );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -438,6 +438,18 @@ class DDHH_JM_Notifications {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mentors have already been notified for this job
|
||||
$already_notified = get_post_meta( $post->ID, '_ddhh_mentors_notified', true );
|
||||
if ( '1' === $already_notified ) {
|
||||
error_log(
|
||||
sprintf(
|
||||
'DDHH Job Manager: Skipping mentor notification for job #%d - mentors already notified on initial publish',
|
||||
$post->ID
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get opted-in mentors
|
||||
$mentor_ids = DDHH_JM_User_Preferences::get_opted_in_mentors();
|
||||
|
||||
@@ -456,6 +468,9 @@ class DDHH_JM_Notifications {
|
||||
// Schedule async batch notifications
|
||||
$batch_count = DDHH_JM_Scheduler::schedule_mentor_notification_batch( $mentor_ids, $post->ID );
|
||||
|
||||
// Mark job as having notified mentors to prevent duplicate notifications
|
||||
update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' );
|
||||
|
||||
// Log success
|
||||
error_log(
|
||||
sprintf(
|
||||
|
||||
@@ -21,6 +21,58 @@ class DDHH_JM_Pages {
|
||||
public static function setup_hooks() {
|
||||
// Redirect logged-in providers from login page to dashboard
|
||||
add_action( 'template_redirect', array( __CLASS__, 'maybe_redirect_logged_in_from_login' ) );
|
||||
|
||||
// Enqueue auth form styles on anbieter-login page
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_auth_styles' ) );
|
||||
|
||||
// Clean up legacy inline styles and inject missing elements on login page
|
||||
add_filter( 'the_content', array( __CLASS__, 'filter_login_page_content' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue auth form styles on the anbieter-login page
|
||||
*/
|
||||
public static function enqueue_auth_styles() {
|
||||
$login_page_id = get_option( 'ddhh_jm_login_page_id' );
|
||||
if ( ! $login_page_id || ! is_page( $login_page_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'ddhh-jm-auth-forms',
|
||||
DDHH_JM_PLUGIN_URL . 'assets/css/auth-forms.css',
|
||||
array(),
|
||||
DDHH_JM_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter login page content to remove legacy inline styles and inject missing elements
|
||||
*
|
||||
* @param string $content Page content.
|
||||
* @return string Filtered content.
|
||||
*/
|
||||
public static function filter_login_page_content( $content ) {
|
||||
$login_page_id = get_option( 'ddhh_jm_login_page_id' );
|
||||
if ( ! $login_page_id || ! is_page( $login_page_id ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Strip legacy inline <style> blocks baked into the page content
|
||||
$content = preg_replace( '/<style[^>]*>.*?<\/style>/s', '', $content );
|
||||
|
||||
// Inject "Passwort vergessen?" link after the login form if not already present
|
||||
if ( strpos( $content, 'login-lost-password' ) === false ) {
|
||||
$lost_pw_html = '<p class="login-lost-password"><a href="' . esc_url( wp_lostpassword_url() ) . '">Passwort vergessen?</a></p>';
|
||||
|
||||
// Insert after the closing </form> inside the login section
|
||||
$pos = strrpos( $content, '</form>' );
|
||||
if ( false !== $pos ) {
|
||||
$content = substr_replace( $content, '</form>' . $lost_pw_html, $pos, strlen( '</form>' ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,66 +161,8 @@ class DDHH_JM_Pages {
|
||||
// Get registration form ID
|
||||
$registration_form_id = DDHH_JM_Formidable::get_registration_form_id();
|
||||
|
||||
// Build page content with inline CSS and two sections
|
||||
$content = '<style>
|
||||
.ddhh-auth-container {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.ddhh-register-section,
|
||||
.ddhh-login-section {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.ddhh-register-section h2,
|
||||
.ddhh-login-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Mobile responsive - stacked layout */
|
||||
@media (max-width: 768px) {
|
||||
.ddhh-auth-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form styling for consistency */
|
||||
.ddhh-auth-container input[type="text"],
|
||||
.ddhh-auth-container input[type="email"],
|
||||
.ddhh-auth-container input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ddhh-auth-container input[type="submit"],
|
||||
.ddhh-auth-container button[type="submit"] {
|
||||
background: #0073aa;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ddhh-auth-container input[type="submit"]:hover,
|
||||
.ddhh-auth-container button[type="submit"]:hover {
|
||||
background: #005a87;
|
||||
}
|
||||
</style>';
|
||||
// Build page content — styles are loaded via enqueued auth-forms.css
|
||||
$content = '';
|
||||
|
||||
$content .= '<div class="ddhh-auth-container">';
|
||||
|
||||
@@ -197,6 +191,7 @@ class DDHH_JM_Pages {
|
||||
);
|
||||
|
||||
$content .= wp_login_form( $login_args );
|
||||
$content .= '<p class="login-lost-password"><a href="' . esc_url( wp_lostpassword_url() ) . '">Passwort vergessen?</a></p>';
|
||||
$content .= '</div>';
|
||||
|
||||
$content .= '</div>'; // .ddhh-auth-container
|
||||
|
||||
@@ -40,22 +40,36 @@ class DDHH_JM_Template {
|
||||
$job_type = get_post_meta( $post->ID, 'job_type', true );
|
||||
$job_deadline = get_post_meta( $post->ID, 'job_deadline', true );
|
||||
$job_contact_email = get_post_meta( $post->ID, 'job_contact_email', true );
|
||||
$job_logo = get_the_post_thumbnail( $post->ID, 'job-logo' );
|
||||
$provider_logo_id = get_user_meta( $post->post_author, 'ddhh_provider_logo', true );
|
||||
$job_logo = $provider_logo_id ? wp_get_attachment_image( absint( $provider_logo_id ), 'job-logo' ) : '';
|
||||
|
||||
// Get author/organization info
|
||||
$author = get_userdata( $post->post_author );
|
||||
$author_name = $author ? $author->display_name : '';
|
||||
$author_org = get_user_meta( $post->post_author, 'ddhh_org_name', true );
|
||||
if ( ! $author_org ) {
|
||||
$author_org = $author_name;
|
||||
}
|
||||
|
||||
// Build job details HTML
|
||||
ob_start();
|
||||
?>
|
||||
<div class="ddhh-back-to-archive">
|
||||
<a href="<?php echo esc_url( get_post_type_archive_link( 'job_offer' ) ); ?>">← Alle Jobangebote</a>
|
||||
</div>
|
||||
<div class="ddhh-job-offer-details">
|
||||
<?php if ( $job_logo || $author_org ) : ?>
|
||||
<div class="job-header">
|
||||
<?php if ( $job_logo ) : ?>
|
||||
<div class="job-logo">
|
||||
<?php echo $job_logo; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( $author_org ) : ?>
|
||||
<span class="job-header-org"><?php echo esc_html( $author_org ); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="job-meta">
|
||||
<?php if ( $author_org ) : ?>
|
||||
@@ -118,16 +132,35 @@ class DDHH_JM_Template {
|
||||
<?php endif; ?>
|
||||
|
||||
<style>
|
||||
.ddhh-back-to-archive {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.ddhh-back-to-archive a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ddhh-back-to-archive a:hover {
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ddhh-job-offer-details {
|
||||
margin: 2em 0;
|
||||
}
|
||||
.ddhh-job-offer-details .job-logo {
|
||||
.ddhh-job-offer-details .job-header {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.ddhh-job-offer-details .job-logo img {
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
.ddhh-job-offer-details .job-header-org {
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-size: 1.4em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.ddhh-job-offer-details .job-meta {
|
||||
background: #f5f5f5;
|
||||
padding: 1.5em;
|
||||
|
||||
38
includes/elementor-tags/class-tag-job-contact-email.php
Normal file
38
includes/elementor-tags/class-tag-job-contact-email.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor dynamic tag: Kontakt-E-Mail (job_contact_email).
|
||||
*
|
||||
* Registered in both TEXT and URL categories so it can be used
|
||||
* as link href (mailto:) or plain text display.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class DDHH_JM_Tag_Job_Contact_Email extends \Elementor\Core\DynamicTags\Tag {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'ddhh-job-contact-email';
|
||||
}
|
||||
|
||||
public function get_title(): string {
|
||||
return 'Kontakt-E-Mail';
|
||||
}
|
||||
|
||||
public function get_group(): array {
|
||||
return array( DDHH_JM_Elementor_Tags::GROUP_SLUG );
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return array(
|
||||
\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY,
|
||||
\Elementor\Modules\DynamicTags\Module::URL_CATEGORY,
|
||||
);
|
||||
}
|
||||
|
||||
public function render(): void {
|
||||
$value = get_post_meta( get_the_ID(), 'job_contact_email', true );
|
||||
echo esc_html( $value );
|
||||
}
|
||||
}
|
||||
44
includes/elementor-tags/class-tag-job-deadline.php
Normal file
44
includes/elementor-tags/class-tag-job-deadline.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor dynamic tag: Bewerbungsfrist (job_deadline).
|
||||
*
|
||||
* Formats the stored Y-m-d date as DD.MM.YYYY for display.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class DDHH_JM_Tag_Job_Deadline extends \Elementor\Core\DynamicTags\Tag {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'ddhh-job-deadline';
|
||||
}
|
||||
|
||||
public function get_title(): string {
|
||||
return 'Bewerbungsfrist';
|
||||
}
|
||||
|
||||
public function get_group(): array {
|
||||
return array( DDHH_JM_Elementor_Tags::GROUP_SLUG );
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY );
|
||||
}
|
||||
|
||||
public function render(): void {
|
||||
$raw = get_post_meta( get_the_ID(), 'job_deadline', true );
|
||||
if ( empty( $raw ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = strtotime( $raw );
|
||||
if ( false === $timestamp ) {
|
||||
echo esc_html( $raw );
|
||||
return;
|
||||
}
|
||||
|
||||
echo esc_html( date_i18n( 'd.m.Y', $timestamp ) );
|
||||
}
|
||||
}
|
||||
32
includes/elementor-tags/class-tag-job-location.php
Normal file
32
includes/elementor-tags/class-tag-job-location.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor dynamic tag: Standort (job_location).
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class DDHH_JM_Tag_Job_Location extends \Elementor\Core\DynamicTags\Tag {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'ddhh-job-location';
|
||||
}
|
||||
|
||||
public function get_title(): string {
|
||||
return 'Standort';
|
||||
}
|
||||
|
||||
public function get_group(): array {
|
||||
return array( DDHH_JM_Elementor_Tags::GROUP_SLUG );
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY );
|
||||
}
|
||||
|
||||
public function render(): void {
|
||||
$value = get_post_meta( get_the_ID(), 'job_location', true );
|
||||
echo wp_kses_post( $value );
|
||||
}
|
||||
}
|
||||
68
includes/elementor-tags/class-tag-job-logo.php
Normal file
68
includes/elementor-tags/class-tag-job-logo.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor dynamic tag: Logo.
|
||||
*
|
||||
* Extends Data_Tag to return image data (id + url) for use in
|
||||
* Image widgets and other image-accepting controls.
|
||||
*
|
||||
* The logo is stored on the provider (post author) as user meta
|
||||
* `ddhh_provider_logo`, not on the job post itself.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class DDHH_JM_Tag_Job_Logo extends \Elementor\Core\DynamicTags\Data_Tag {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'ddhh-job-logo';
|
||||
}
|
||||
|
||||
public function get_title(): string {
|
||||
return 'Logo';
|
||||
}
|
||||
|
||||
public function get_group(): array {
|
||||
return array( DDHH_JM_Elementor_Tags::GROUP_SLUG );
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return array( \Elementor\Modules\DynamicTags\Module::IMAGE_CATEGORY );
|
||||
}
|
||||
|
||||
protected function register_controls(): void {
|
||||
$this->add_control(
|
||||
'fallback',
|
||||
array(
|
||||
'label' => 'Fallback',
|
||||
'type' => \Elementor\Controls_Manager::MEDIA,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function get_value( array $options = array() ): array {
|
||||
$post = get_post( get_the_ID() );
|
||||
$image_id = $post ? get_user_meta( $post->post_author, 'ddhh_provider_logo', true ) : '';
|
||||
|
||||
if ( $image_id ) {
|
||||
$url = wp_get_attachment_image_url( $image_id, 'full' );
|
||||
if ( $url ) {
|
||||
return array(
|
||||
'id' => (int) $image_id,
|
||||
'url' => $url,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$fallback = $this->get_settings( 'fallback' );
|
||||
if ( ! empty( $fallback['id'] ) ) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => 0,
|
||||
'url' => '',
|
||||
);
|
||||
}
|
||||
}
|
||||
41
includes/elementor-tags/class-tag-job-type.php
Normal file
41
includes/elementor-tags/class-tag-job-type.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Elementor dynamic tag: Art (job_type).
|
||||
*
|
||||
* Maps raw select values to German display labels.
|
||||
*
|
||||
* @package DDHH_Job_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class DDHH_JM_Tag_Job_Type extends \Elementor\Core\DynamicTags\Tag {
|
||||
|
||||
private const LABELS = array(
|
||||
'vollzeit' => 'Vollzeit',
|
||||
'teilzeit' => 'Teilzeit',
|
||||
'ehrenamt' => 'Ehrenamt',
|
||||
);
|
||||
|
||||
public function get_name(): string {
|
||||
return 'ddhh-job-type';
|
||||
}
|
||||
|
||||
public function get_title(): string {
|
||||
return 'Art';
|
||||
}
|
||||
|
||||
public function get_group(): array {
|
||||
return array( DDHH_JM_Elementor_Tags::GROUP_SLUG );
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY );
|
||||
}
|
||||
|
||||
public function render(): void {
|
||||
$raw = get_post_meta( get_the_ID(), 'job_type', true );
|
||||
$label = self::LABELS[ $raw ] ?? $raw;
|
||||
echo esc_html( $label );
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,58 @@ if ( ! in_array( 'ddhh_provider', $current_user->roles, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle logo upload
|
||||
if ( isset( $_POST['ddhh_upload_logo'] ) && isset( $_POST['ddhh_logo_nonce'] ) && wp_verify_nonce( $_POST['ddhh_logo_nonce'], 'ddhh_logo_upload' ) ) {
|
||||
if ( ! empty( $_FILES['ddhh_logo_file'] ) && $_FILES['ddhh_logo_file']['error'] === UPLOAD_ERR_OK ) {
|
||||
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
||||
require_once( ABSPATH . 'wp-admin/includes/image.php' );
|
||||
require_once( ABSPATH . 'wp-admin/includes/media.php' );
|
||||
|
||||
// Validate file type
|
||||
$allowed_types = array( 'image/jpeg', 'image/png', 'image/jpg' );
|
||||
$file_type = $_FILES['ddhh_logo_file']['type'];
|
||||
|
||||
if ( in_array( $file_type, $allowed_types, true ) ) {
|
||||
$upload = wp_handle_upload( $_FILES['ddhh_logo_file'], array( 'test_form' => false ) );
|
||||
|
||||
if ( isset( $upload['file'] ) && ! isset( $upload['error'] ) ) {
|
||||
// Insert attachment
|
||||
$attachment = array(
|
||||
'post_mime_type' => $upload['type'],
|
||||
'post_title' => sanitize_file_name( $_FILES['ddhh_logo_file']['name'] ),
|
||||
'post_content' => '',
|
||||
'post_status' => 'inherit',
|
||||
);
|
||||
|
||||
$attachment_id = wp_insert_attachment( $attachment, $upload['file'] );
|
||||
|
||||
if ( ! is_wp_error( $attachment_id ) ) {
|
||||
// Generate metadata
|
||||
$attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
|
||||
wp_update_attachment_metadata( $attachment_id, $attachment_data );
|
||||
|
||||
// Save to user meta
|
||||
update_user_meta( get_current_user_id(), 'ddhh_provider_logo', $attachment_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle logo removal
|
||||
if ( isset( $_POST['ddhh_remove_logo'] ) && isset( $_POST['ddhh_logo_nonce'] ) && wp_verify_nonce( $_POST['ddhh_logo_nonce'], 'ddhh_logo_upload' ) ) {
|
||||
delete_user_meta( get_current_user_id(), 'ddhh_provider_logo' );
|
||||
}
|
||||
|
||||
// Check if we're in edit mode
|
||||
$is_edit_mode = isset( $_GET['action'] ) && $_GET['action'] === 'edit_job' && isset( $_GET['job_id'] );
|
||||
|
||||
// Check if we're in deactivate mode
|
||||
$is_deactivate_mode = isset( $_GET['action'] ) && $_GET['action'] === 'deactivate_job' && isset( $_GET['job_id'] );
|
||||
|
||||
// Check if we're in new job mode
|
||||
$is_new_job_mode = isset( $_GET['action'] ) && $_GET['action'] === 'new_job';
|
||||
|
||||
if ( $is_edit_mode ) {
|
||||
$job_id = absint( $_GET['job_id'] );
|
||||
$form_id = DDHH_JM_Formidable::get_job_edit_form_id();
|
||||
@@ -69,9 +115,6 @@ if ( $is_edit_mode ) {
|
||||
case 'job_contact_email2':
|
||||
$field_value = get_post_meta( $job_id, 'job_contact_email', true );
|
||||
break;
|
||||
case 'job_logo2':
|
||||
$field_value = get_post_thumbnail_id( $job_id );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! empty( $field_value ) ) {
|
||||
@@ -214,6 +257,32 @@ if ( $is_deactivate_mode ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( $is_new_job_mode ) {
|
||||
$form_id = DDHH_JM_Formidable::get_job_submission_form_id();
|
||||
|
||||
if ( $form_id ) {
|
||||
?>
|
||||
<div class="ddhh-provider-dashboard">
|
||||
<div class="ddhh-dashboard-header">
|
||||
<div class="ddhh-user-info">
|
||||
<span class="welcome-text">Angemeldet als: <strong><?php echo esc_html( $current_user->display_name ); ?></strong></span>
|
||||
</div>
|
||||
<div class="ddhh-logout-link">
|
||||
<a href="<?php echo esc_url( wp_logout_url( home_url( '/anbieter-login/' ) ) ); ?>" class="logout-button">Abmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ddhh-job-submit-section">
|
||||
<h2>Neues Stellenangebot erstellen</h2>
|
||||
<p><a href="<?php echo esc_url( home_url( '/anbieter-dashboard/' ) ); ?>" class="back-to-dashboard">← Zurück zur Übersicht</a></p>
|
||||
<?php echo do_shortcode( "[formidable id={$form_id}]" ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Query current user's job_offer posts
|
||||
$args = array(
|
||||
'post_type' => 'job_offer',
|
||||
@@ -237,16 +306,33 @@ $job_query = new WP_Query( $args );
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ddhh-job-submit-section">
|
||||
<h2>Neues Stellenangebot erstellen</h2>
|
||||
<div class="ddhh-logo-section">
|
||||
<h3>Ihr Logo</h3>
|
||||
<?php
|
||||
$form_id = DDHH_JM_Formidable::get_job_submission_form_id();
|
||||
if ( $form_id ) {
|
||||
echo do_shortcode( "[formidable id={$form_id}]" );
|
||||
} else {
|
||||
echo '<p>Formular konnte nicht geladen werden.</p>';
|
||||
$provider_logo_id = get_user_meta( get_current_user_id(), 'ddhh_provider_logo', true );
|
||||
if ( $provider_logo_id ) {
|
||||
echo '<div class="ddhh-current-logo">';
|
||||
echo wp_get_attachment_image( absint( $provider_logo_id ), 'medium' );
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
<form method="post" enctype="multipart/form-data" class="ddhh-logo-upload-form">
|
||||
<?php wp_nonce_field( 'ddhh_logo_upload', 'ddhh_logo_nonce' ); ?>
|
||||
<div class="ddhh-logo-controls">
|
||||
<input type="file" name="ddhh_logo_file" accept="image/png,image/jpeg,image/jpg" class="ddhh-logo-input">
|
||||
<button type="submit" name="ddhh_upload_logo" class="ddhh-logo-button ddhh-upload-button">
|
||||
<?php echo $provider_logo_id ? 'Logo ändern' : 'Logo hochladen'; ?>
|
||||
</button>
|
||||
<?php if ( $provider_logo_id ) : ?>
|
||||
<button type="submit" name="ddhh_remove_logo" class="ddhh-logo-button ddhh-remove-button" onclick="return confirm('Möchten Sie das Logo wirklich entfernen?');">Logo entfernen</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="ddhh-logo-hint">Erlaubte Formate: JPG, PNG (max. 2 MB)</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="ddhh-dashboard-actions">
|
||||
<a href="<?php echo esc_url( add_query_arg( 'action', 'new_job', home_url( '/anbieter-dashboard/' ) ) ); ?>" class="ddhh-new-job-button">+ Neues Stellenangebot erstellen</a>
|
||||
</div>
|
||||
|
||||
<div class="ddhh-job-listings-section">
|
||||
@@ -383,16 +469,124 @@ $job_query = new WP_Query( $args );
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ddhh-job-submit-section {
|
||||
margin-bottom: 3rem;
|
||||
padding: 2rem;
|
||||
background: #f5f5f5;
|
||||
.ddhh-logo-section {
|
||||
padding: 1.5rem 2rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.ddhh-logo-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.125rem;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ddhh-current-logo {
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem;
|
||||
background: #f9fafb;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ddhh-current-logo img {
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ddhh-logo-upload-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ddhh-logo-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ddhh-logo-input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.ddhh-logo-button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.ddhh-upload-button {
|
||||
background-color: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ddhh-upload-button:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
.ddhh-remove-button {
|
||||
background-color: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ddhh-remove-button:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.ddhh-logo-hint {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ddhh-dashboard-actions {
|
||||
margin-bottom: 2rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ddhh-provider-dashboard .ddhh-new-job-button {
|
||||
display: inline-block;
|
||||
padding: 0.875rem 2rem;
|
||||
background-color: #10b981;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.ddhh-provider-dashboard .ddhh-new-job-button:hover {
|
||||
background-color: #059669;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ddhh-job-submit-section {
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.ddhh-job-submit-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user