A product wholly owned by SoftworksThe whole visit.
Working as one.
From the look a customer saves to the people, schedules and decisions behind the reception desk. We designed and built Salony to keep the whole visit connected.
Inside the story20 min read


- Relationship
- Softworks-owned product
- For
- Customers & salon teams
- Surfaces
- Mobile, web & operations
- Context
- Egypt · Arabic & English
A good visit starts before the chair.
The customer imagines a look. The salon manages a day. One visit brings both together.

An engineering case study by the Softworks team
Salony brings salon discovery, style inspiration, appointment booking and daily salon operations into one product. Our team at Softworks built the customer experience around the decisions people make before a visit, then connected those decisions to the schedules, staff, services and resources a salon actually manages.
The resulting platform spans a React Native customer app, a bilingual Next.js website and operational dashboards. Its complexity sits between those surfaces: the same appointment must make sense to a customer choosing a time, a receptionist managing the day and a professional preparing for a particular look.
Existing website capture, 17 October 2026. The embedded app preview uses demonstration records. Screenshot details.
| Project | Salony |
|---|---|
| Product team | Softworks |
| Market and context | Egypt; Arabic and English; Egyptian-pound presentation; Cairo business time |
| Product model | Customer marketplace connected to salon operations |
| Surfaces | Public website, customer mobile app, salon dashboard, platform administration |
| Engineering scope | Product architecture, interaction design, application development, integrations and verification |
| Case-study basis | Current source, reachable commit history and dated delivery evidence, reviewed 18 October 2026 |
Reading guide. The example customer situations below are illustrative scenarios based on implemented behavior. Documented defects are identified separately. Product screenshots contain demo or provider content; figures in them are not adoption, revenue or performance results. Internal security and operational access details are outside this public case study.
The product brief
A salon visit begins well before someone sits in a chair. A customer may have a reference photo, an occasion, a preferred professional, a limited budget and a small window in the day. A salon has a different view of the same visit: service duration, staff availability, suitable equipment, queue pressure and preparation requirements.
We designed Salony around this shared visit. Discovery helps a person decide where to go. Booking turns that preference into an explicit request. The operational workspace gives the salon enough context to respond and prepare. The product needs continuity across all three stages.
That continuity influenced our scope. Public pages explain the product and help people discover salons or inspiration. The customer app carries the booking and payment journey. Salon teams manage their working data through the web dashboard. Each surface has a clear job, while the underlying business concepts stay consistent.
This is a B2B2C product with substantial operational software behind the customer experience. We treated the salon's calendar, service catalogue and portfolio as part of the marketplace experience, because those records determine what the customer can reasonably expect.
One visit, several perspectives
Consider a customer preparing for an event. They browse hairstyle references, compare nearby salons, inspect services and select a professional. They attach a reference and a note about the desired length. The salon then needs the time, service, professional and preparation context together.
The customer should not have to reconstruct that conversation through disconnected messages. The receptionist should not have to interpret an inspiration image as a booked treatment. The professional should understand that the picture expresses a preference, with the final result agreed during consultation.
We built a sequence that preserves these distinctions:
- Explore: browse salons, services and attributed inspiration.
- Choose: identify a branch, service combination and professional.
- Check: present the relevant availability and booking policy.
- Prepare: attach optional reference looks and visit notes.
- Submit: create the booking request and follow the relevant payment path.
- Follow: display the actual appointment and payment states.
- Operate: give the salon a working view of the visit and its next action.
React Native screen rendered through the existing web export, with fictional salons. This capture is not a physical-device certification.
One product. Several points of view.
Shared product API & domain services
Scheduling and appointment lifecycle, reference snapshots, portfolio publishing, payment and communication workflows.
Cloudflare Workers + OpenNext host the current application; MongoDB remains the database.
The interfaces
Native booking. Web operations.
The shared rules
Typed contracts. Validated input.
The retained context
Visit records. Image processing.
The delivery layer
Cloudflare Workers + OpenNext. Vitest + Playwright.
Media & inspirationAWS S3 PexelsOwned media and attributed imagery, with distinct responsibilities.
Architecture shaped around the product
We use an npm-workspaces monorepo for the mobile and web applications. The customer app uses React Native and Expo with React Navigation. The web application uses Next.js for public pages, dashboards and the server-facing API layer. MongoDB and Mongoose hold the operational records, with AWS S3 supporting media storage.
Domain services sit between interface actions and persisted records. Scheduling, booking policy, portfolio publishing, style discovery and payment status each have dedicated responsibilities. This lets a screen ask a product-level question without reproducing its rules independently.
Diagram source and relationships
flowchart TB
A[Customer mobile app] --> D[Shared product API and domain services]
B[Public website] --> D
C[Salon operations dashboard] --> D
D --> E[Scheduling and appointment lifecycle]
D --> F[Style references and portfolio publishing]
D --> G[Payment and communication workflows]
E --> H[(MongoDB and Mongoose)]
F --> H
G --> H
F --> I[Media storage and inspiration provider]
G --> J[External service adapters]Conceptual architecture from the supplied article.
Conceptual product architecture. Internal access controls, deployment configuration and private interfaces are deliberately omitted.
The current web deployment uses OpenNext on Cloudflare Workers. A Durable Object hosts the Next server to keep its existing Mongoose connection context consistent. MongoDB remains the database. This is a concrete runtime adaptation with trade-offs; it is not evidence of a globally replicated booking engine or unlimited throughput.
The implemented technology stack
| Layer | Technology in the reviewed source | Why it matters to this product |
|---|---|---|
| Mobile framework | Expo ~57.0.30, React Native 0.86.3 | A shared customer application with native integration points |
| UI runtime | React 19.2.3 | A common component model across applications |
| Navigation | React Navigation 6 | Explicit mobile journeys and return paths |
| Web application | Next.js 16.3.8 | Public content, interactive dashboards and application APIs |
| Languages | TypeScript; web 5.9.3, mobile ~6.0.3 | Typed contracts and application-specific checking |
| Operational data | MongoDB, Mongoose ^8.24.1 | Explicit document models for salons, branches and visits |
| Web presentation | Tailwind CSS, CSS modules, existing component foundations | Shared visual rules with surface-specific layouts |
| Forms | React Hook Form and Zod | Form state and structured input contracts |
| Localization | i18next/react-i18next on mobile; bilingual web content | Arabic/English presentation and RTL behavior |
| Media | AWS S3, Sharp, attributed Pexels integration | Salon media and a separate inspiration-photo pipeline |
| Maps | Leaflet on web surfaces; MapLibre-based mobile map document | Platform-specific map presentation and fallbacks |
| Charts and movement | Recharts, Motion, GSAP where used | Operational visualization and deliberate interaction |
| Hosting | Cloudflare Workers and OpenNext 1.20.11 | Deployment of the existing Next application |
| Verification | Vitest, Playwright, TypeScript checks, dated native build evidence | Different levels of confidence for different behaviors |
Versions describe the checked workspace. They are not a recommendation to upgrade another project to the same versions. Likewise, a library in the stack does not imply that every possible feature of that library is used.
An empty slot is a system decision.
A time the salon can actually offer.
The service, professional and chair are available for the whole interval. Submission still checks the current state.
Engineering challenge 01: availability depends on more than opening hours
Illustrative scenario: a branch opens at 10:00. One professional starts at 11:00, a second is occupied, and the service needs a suitable chair. The branch is open, but the requested appointment cannot necessarily begin at 10:00.
An opening-hours check alone cannot answer the booking question. We calculate candidate slots using the selected services, service duration, staff capability and schedule, overrides, blocks, existing appointments and relevant chair capacity. The calculation also considers a near-term buffer rather than treating every minute after the current clock time as bookable.
Staff-specific duration matters. Two professionals may deliver the same service at different speeds. Combining services also changes the time the visit occupies. Those details affect which starting times are sensible and which resources remain available afterward.
The public availability label is derived from slot results. If the calculation cannot provide a trustworthy answer, the interface can say availability is unavailable. A “busy” state and an unavailable answer communicate different things; we preserve that difference.
The current business timezone is centralized around Africa/Cairo. Scheduling helpers format and construct business dates using that zone rather than a fixed offset. This makes the time basis explicit across the product. International per-branch timezone behavior would require additional work and validation.
Engineering outcome: discovery and booking use a richer model of salon capacity. The implementation supports more meaningful availability than an open/closed flag; no claim of eliminating every operational delay follows from that model.
Engineering challenge 02: a visible slot can change before submission
Illustrative scenario: two people view the same appointment time, then submit seconds apart. Both screens were valid when loaded. The system must decide against the current state when each request arrives.
The booking service rechecks availability during creation and uses appointment reservation records for the time being occupied. Reservation acquisition can fail if another operation has already claimed that capacity. The customer receives a meaningful “no longer available” outcome instead of an optimistic success message that the salon cannot honor.
Rescheduling adds another problem. Releasing the old time before obtaining the new one can leave a person without either appointment. The reservation-move flow therefore preserves the old reservation while attempting the target interval, reuses overlapping time where appropriate, and completes the move only after the required steps succeed.
This is a lifecycle concern, not a visual flourish. The design of retries, partial progress and cleanup determines whether the schedule stays understandable after a failed operation. Tests and implementation comments document these cases; a page animation cannot compensate for an inconsistent appointment record.
The product consequence is equally important: confirmation belongs after the authoritative operation. A button press expresses intent. It does not itself establish a reservation.
Every state should mean something.
Submitting a transfer reference supplies evidence for review. It does not establish payment acceptance or branch approval.
Engineering challenge 03: payment progress and appointment progress are different
Illustrative scenario: a customer has submitted a transfer reference, but the payment still needs review. Separately, the branch may still need to approve the visit. Treating both as one “success” state would give the customer the wrong expectation.
We model payment and appointment progress separately. A booking can be awaiting payment verification, awaiting branch approval, confirmed, checked in, in service or completed, with other states for the appropriate exception paths. Payment interfaces explain the financial step relevant to the selected method.
The source includes a card-provider integration and transfer-review workflows. A provider checkout returning control to the app is one event in that journey. The application must still represent the established result and any pending work. This is why a generic celebration cannot replace the status view.
The same discipline applies to pricing. Service prices, platform-added amounts, payment handling, promotional adjustments and loyalty effects are different concepts. They need explicit calculation rules and readable presentation.
At the review date, the current fee helpers calculate zero Salony commission and zero added payment-handling fees. Marketing pages describe launch terms and future commercial plans separately. We do not treat future prices in copy as proof that billing has been activated.
Our case-study screenshots exclude older fixture captures that show superseded fee examples. The code and the published narrative must describe the same product state.
Keep the context. Even when things change.
Inspiration
The image, its source and its attribution.
Retained context
A reference record attached to the visit.
- Image & attribution
- Preserved
- Customer note
- Keep the shape and length.
A note-only edit keeps the saved image and attribution without resolving the provider again. Removal is a separate, explicit choice.
Engineering challenge 04: carrying a reference look into a real appointment
Illustrative scenario: a customer saves a hairstyle, attaches it to a booking and later edits the note. In the meantime, the external photo service becomes unavailable or the catalogue changes.
Saving only a live catalogue pointer would make the old booking depend on the catalogue's future state. We resolve a selected look into a booking snapshot containing its image, title, category, available source credit and the customer's note. The supported flow accepts up to three reference looks, with a bounded note for each.
Existing snapshots are reused when notes change. A later provider outage does not require the customer to choose the same image again just to revise “shorter fringe” to “keep more length.” Conversely, a new reference that is no longer available can be refused with a useful recovery path.
The data contract also distinguishes leaving references unchanged from explicitly removing them. That distinction sounds small, but it prevents an unrelated edit from silently clearing useful preparation information.
Existing October 13 fixture capture. The reference includes a photographer credit. The image expresses a preference; the final treatment is agreed with the professional.
The underlying principle is temporal stability: a record of a past decision should retain the context of that decision. The external catalogue can continue evolving without rewriting what the customer originally communicated.
// Illustrative contract, not extracted production source.
type ReferenceSnapshot = {
imageId: string;
imageUrl: string;
title: string;
credit?: string;
note: string;
};
type ReferenceEdit =
| { kind: "keep" }
| { kind: "remove-all" }
| { kind: "update-note"; imageId: string; note: string };
function applyReferenceEdit(
saved: ReferenceSnapshot[],
edit: ReferenceEdit,
): ReferenceSnapshot[] {
if (edit.kind === "keep") return saved;
if (edit.kind === "remove-all") return [];
// Editing a note preserves the original image and credit.
return saved.map(reference =>
reference.imageId === edit.imageId
? { ...reference, note: edit.note }
: reference,
);
}An illustrative contract distinguishing unchanged, removed and note-only edits; not production source.
The right image. The right context.
Engineering challenge 05: portfolio settings must travel with the photo
Illustrative scenario: a salon uploads a three-photo look, hides the middle photo, assigns another to a professional and then changes the order. If settings are stored by position, the hidden flag and credit can attach to the wrong image.
We keep the photo's settings associated with its image URL through the current portfolio model. Reordering preserves those relationships. A whole look can also be hidden while retaining the individual settings for later editing.
The public representation omits unpublished looks and hidden photos, and excludes a look when all its photos are hidden. The optional credit is resolved for the relevant active professional. The customer gallery changes the displayed credit as the selected photo changes.
Compatibility was part of the work. Existing clients used an image_urls array. The extended model keeps that representation while adding optional photo metadata, descriptions, categories and publication state. Legacy records without the new metadata retain their expected publication behavior.
Actual editor rendered with fictional test content, 17 October 2026. The stock-photo fixture illustrates the workflow; it is not claimed as a real salon's work.
This let us improve editorial control without requiring every mobile installation and existing portfolio record to change at once. It also gave the team a concrete regression target: new controls should improve the model without breaking older readers.
Engineering challenge 06: inspiration search needs honest data semantics
Beauty photography is useful for discovering a direction. It rarely establishes a treatment recipe. A photograph cannot reliably tell a customer which products were used, how long a service takes, what it costs or whether it is suitable for them.
We kept provider photography distinct from salon-owned portfolio content. The Styles experience exposes meaningful photo-search controls, preserves source and creator credits, and uses supplied metadata where available. Editorial content is allowed to have an empty state rather than being filled with invented expertise.
The browsing implementation includes category-specific queries, submitted search, pagination with duplicate removal, shareable URL filters and browser Back restoration. Mobile filter edits live in a draft until applied; dismissing the sheet discards that draft and returns focus.
Provider behavior is bounded. Successful responses use a six-hour, per-instance cache with a finite entry count. Identical requests already in progress can share the same pending result. Failures are not cached as successful content. This reduces repeated work while allowing an unsuccessful request to recover.
We also preserve the provider's actual file format and use appropriately sized images for cards and details. A valid PNG should not become a broken image because a URL was reconstructed with a JPEG assumption.
Existing public capture with attributed provider photography. Search relevance is not professional suitability advice.
Designed to read both ways.
Matched captures from the same fixture workflow. Switch language and appearance to inspect the actual interfaces.
17 October 2026 captures · Demonstration dataEngineering challenge 07: Arabic and appearance changes affect behavior
Arabic support includes direction, reading order, mixed-language content, navigation and the space available to labels. A number or brand name can remain left-to-right inside an otherwise right-to-left interface. A short English label can become a longer Arabic instruction.
We use Manrope and Cairo within the current visual system and review important layouts in both languages. The shared platform identity uses matte black, neutral surfaces and restrained lime accents, with a deliberate light appearance alongside dark mode.
Theme and language controls also interact with state. A person filling a salon application should not lose their draft when switching appearance. A gallery should return keyboard focus to its trigger when dismissed. A mobile menu should open and close without leaving navigation focus stranded.
The delivery reports include these behaviors as specific checks. They do not establish universal accessibility certification. Browser viewport checks, simulated enlarged text and native assistive-technology checks each prove different things.
Matched Home captures from the same dated fixture workflow. Dark counterparts and metadata.
Built around the work behind the look.
What we customized for salon operations
The application reflects the shape of a salon's working day. We did not assume that a service catalogue, a staff list and an appointment calendar were independent screens.
| Customization | Product reason |
|---|---|
| Branch-level services and schedules | A business can offer different work at different locations |
| Professional-specific service duration | A visit's timing can depend on who performs it |
| Chair/resource capacity | Staff availability alone does not establish physical capacity |
| Reception and queue workflows | Walk-ins and appointments share the same operational day |
| Booking policies and visit requests | Preparation and changes need understandable conditions |
| Optional inspiration references | Customers communicate visual preferences inside the visit context |
| Per-photo portfolio publishing | Salons control which work appears and who receives credit |
| Business branding data | A salon's identity remains recognizable within the platform |
| Arabic/English and Cairo time | The interface fits its intended operating context |
| Light/dark presentation | Customer browsing and desk work use the same brand coherently |
Existing demonstration dashboard. Counts, names and appointment examples are sample data, not business outcomes.
This is implemented customization of the product domain. It should not be expanded into claims that hardware POS, NFC loyalty, every custom-domain configuration or a full international franchise system are already deployed. The later Salon OS direction has its own roadmap.
What works. What comes next.
How the system evolved: what the history actually supports
The repository has broad early commits and more detailed payment work in other reachable history. We use those records to explain engineering progression without inventing a sprint-by-sprint story.
| Historical evidence | What it supports |
|---|---|
11 May, 826ac05 | A written billing-phase design before related implementation |
11 May, 1fa77c2 | Payment ledger model work |
11 May, 3defa48 | Reconciliation/invoice implementation in that historical branch |
11 May, 86ed6ae | Customer checkout and pending/failure journey work |
11 May, c18ae4c | Salon payment controls and dashboard finance work |
11 May, 92a38a6 | Recovery-service work for incomplete payment operations |
29 June, c8b24de | Mobile dark UI, card and web feature changes |
| October working tree and dated reports | Current branding, reference looks, map refinement, public pages, portfolio controls and deployment changes |
The May commits demonstrate that payment work was divided into models, customer experience, operator experience and recovery concerns. They do not prove that each historical file remains active today. The October implementation is described from the current workspace and its delivery evidence, not attributed to a nonexistent October commit.
That distinction matters when explaining how a product was built. A case study should help another engineer understand the decisions without manufacturing a more convenient history.
Documented issues and their resolutions
Images that existed but failed in the website. The October image report records provider originals returning successfully while the web image pipeline rejected the source and the landing normalizer removed it. We aligned the source configuration used by those paths and verified that the images decoded in the resulting pages. The practical lesson was to inspect the entire media path, including normalization and optimization, rather than only the original file.
Arabic overflow in a horizontal gallery. Styles validation found overflow associated with hidden credits in the RTL gallery. The team corrected the layout and repeated the relevant checks. A hidden element can still influence layout; translating visible text is only part of RTL work.
A published version appearing later than expected. Deployment evidence records an interval where the platform had the new version while active application behavior still reflected older server code. Verification continued until the expected page and assets were actually served. Upload success and visible product activation are separate observations.
These are documented implementation experiences. The scheduling and reference-look examples earlier in the article are explanatory scenarios, not claims about named customers or recorded production incidents.
Verification with a defined scope
We use several kinds of evidence because each answers a different question. Unit tests examine rules and transformations. Browser journeys exercise interfaces and state changes. Builds check that applications compile and package. Native smoke checks establish a narrower result on a particular build and device environment.
The 17 October portfolio delivery records 59 focused web tests in nine files, all 25 web TypeScript shards, source/live browser checks and mobile validation including 218 tests across 43 files. It also records Android testing build 6 installed as an update and opened on an emulator.
The Styles delivery records 51 focused tests and 19 local plus 19 live browser checks. These are separate scoped runs with overlapping coverage. We do not add them into a larger, misleading “total tests passed” metric.
For this documentation review, we reran seven selected unit-test files and 44 tests passed. They cover references, portfolio logic, availability presentation, pricing, timezones, image handling and photo-search helpers. No application was launched and no screenshot was generated during the case-study task.
The available evidence does not establish full physical-device coverage, all provider delivery outcomes, an iOS store release or performance at a stated production traffic level. Those remain separate milestones. Public store links were still presented as Coming soon in the reviewed material.
Outcomes we can substantiate
Salony has a connected implementation for customer discovery, booking and salon operations. The reviewed source carries visit context across surfaces, models meaningful state transitions and supports a bilingual visual system. Public pages and an Android testing build have dated release evidence.
The strongest outcomes in this case study are functional and architectural: reference context survives later edits; portfolio controls preserve their image association; availability accounts for operational constraints; and public information distinguishes photography, actual salon work and planned features.
We do not have a supported before-and-after measurement of booking conversion, no-show reduction, revenue uplift or staff time saved. Those would require a defined measurement period, baseline and comparable usage data. A responsible next stage would measure booking completion, abandonment by step, appointment-change outcomes and operator time on common tasks, then use those findings to prioritize improvements.
The next product horizon
Future Salon OS planning explores deeper CRM, POS and cash workflows, merchant-funded loyalty, desktop reception hardware and wallet-related experiences. Those ideas extend a working operational foundation, but they introduce new accounting, hardware and rollout requirements.
Our next-step principle is to validate each new operational loop end to end. A new feature should have a clear owner action, a customer consequence, a persistent record and an observable completion state. That approach keeps an expanding product understandable to the people running it every day.
Frequently asked questions
What makes Salony a custom salon platform?
Its workflows connect service duration, professionals, branch schedules, physical capacity, visit preparation and portfolio publishing. Those domain relationships shape the application beyond a generic appointment form.
Why use a mobile app and a website?
They support different contexts. The website introduces the product and supports public discovery and salon operations. The mobile app carries the customer booking journey and ongoing visit/account interactions.
Does the Styles library generate guaranteed treatment recommendations?
No. It provides attributed inspiration and optional reference looks. The professional and customer agree on the actual treatment. Provider photographs do not establish products, price, duration or suitability.
Is every feature in the repository available to customers?
No. The active V1 navigation, configuration and release evidence determine availability. Premium/AI source and future Salon OS proposals are kept distinct from the current experience.
How should the screenshots be interpreted?
They are existing product captures with explicit provenance. Many use fictional fixtures and some are historical. They illustrate interfaces and states, not customer growth or financial performance.
Build a product around the work it needs to do
Our work on Salony connects the visible interface to the operational decisions underneath it: what can be booked, what the customer asked for, what the salon can deliver and what happens next.
If your product involves marketplaces, appointments or several teams acting on the same record, we can help define those relationships and turn them into working software.
Discuss your product with Softworks · Explore Salony
Prepared from the repository and dated evidence on 18 October 2026. Editorial/source notes · Image provenance · Choose another edition.
The work, on screen.
29 documented captures across the customer experience, salon operations and public website. Filter, compare and open the detail.
Existing product captures with demonstration data and attributed provider photography.
29 screens
PhoneCustomer home · English
Current Home with category artwork and restrained navigation. Fictional demonstration data. React Native web export; fictional fixtures. Capture context: 2026-10-17.
PhoneCustomer home · English
Current Home with category artwork and restrained navigation. Fictional demonstration data. React Native web export; fictional fixtures. Capture context: 2026-10-17.
PhoneCustomer home · Arabic
Current Home with category artwork and restrained navigation. Fictional demonstration data. React Native web export; fictional fixtures. Capture context: 2026-10-17.
PhoneCustomer home · Arabic
Current Home with category artwork and restrained navigation. Fictional demonstration data. React Native web export; fictional fixtures. Capture context: 2026-10-17.
WebThe public website · English
Public website hero with an actual app preview; embedded records are demonstration content. Existing public website capture. Capture context: 2026-10-17.
WebThe inspiration library · English
Styles browsing with provider photography, distinct from salon-owned portfolio results. Existing public website capture; attributed provider photography. Capture context: 2026-10-17.









