Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ede1196
Create index.md
maddylewis Jul 3, 2025
3575991
Create index.md
maddylewis Jul 3, 2025
a963e60
Create index.md
maddylewis Jul 3, 2025
1a4e7a5
Create index.md
maddylewis Jul 3, 2025
1939b19
Create index.md
maddylewis Jul 3, 2025
2181a09
Create index.md
maddylewis Jul 3, 2025
4042d87
Create index.md
maddylewis Jul 3, 2025
3e2c6e0
Create index.md
maddylewis Jul 3, 2025
284e2fc
Create index.md
maddylewis Jul 3, 2025
524a5a8
Create index.md
maddylewis Jul 3, 2025
a20f847
Create index.md
maddylewis Jul 3, 2025
a269811
Create index.md
maddylewis Jul 3, 2025
89ab602
Delete help/ref/settings/workspaces/:policyID/expensify-cards directory
maddylewis Jul 3, 2025
5316451
Create index.md
maddylewis Jul 3, 2025
5e78315
Create index.md
maddylewis Jul 3, 2025
6e08a02
Create index.md
maddylewis Jul 3, 2025
330bff4
Create index.md
maddylewis Jul 3, 2025
a1bd038
Update index.md
maddylewis Jul 3, 2025
39fd8e6
Update index.md
maddylewis Jul 3, 2025
8746915
Delete help/ref/settings/index.md
maddylewis Jul 3, 2025
9e50be8
Update index.md
maddylewis Jul 3, 2025
0ccb5e3
Delete help/ref/settings/workspaces/:policyId directory
maddylewis Jul 3, 2025
c1b8316
regenerate content for .md help panel
sumo-slonik Jul 4, 2025
5c5c709
update rb script, regenerate tsx map and add missing folder
sumo-slonik Jul 4, 2025
412d6ca
Merge branch 'main' into maddylewis-patch-1
puneetlath Jul 8, 2025
b3d2065
Merge remote-tracking branch 'upstream/maddylewis-patch-1' into maddy…
sumo-slonik Jul 8, 2025
fe69fb3
refactor workspace directory
sumo-slonik Jul 8, 2025
e1c0472
fix script
sumo-slonik Jul 9, 2025
dc252fb
Merge pull request #65686 from software-mansion-labs/maddylewis-patch…
inimaga Jul 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 57 additions & 56 deletions help/_plugins/SitePostRender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,54 +83,54 @@ def self.generate_help_content(site)

output_dir = File.join(site.source, "_src")
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)

output_file = File.join(output_dir, "helpContentMap.tsx")

help_content_tree = generate_help_content_tree()

help_content_string = to_ts_object(help_content_tree)

components = analyze_used_components(help_content_string)

# Generate the import block
import_block = generate_imports(components)

ts_output = generate_ts_output(import_block, help_content_string)

File.write(output_file, ts_output)

puts "✅ Successfully generated helpContent.tsx"
end

def self.analyze_used_components(content)
components = {
'View' => content.include?('<View'),
'Text' => content.include?('<Text'),
'TextLink' => content.include?('<TextLink'),
'BulletList' => content.include?('<BulletList')
}
components.select { |_, used| used }.keys
def self.analyze_used_components(content)
components = {
'View' => content.include?('<View'),
'Text' => content.include?('<Text'),
'TextLink' => content.include?('<TextLink'),
'BulletList' => content.include?('<BulletList')
}
components.select { |_, used| used }.keys
end

def self.generate_imports(components)
base_imports = [
"import type {ReactNode} from 'react';",
"import React from 'react';",
]
# Always include React Native
base_imports << "import {#{(['View'] & components).join(', ')}} from 'react-native';"
# Add component-specific imports
component_imports = []
component_imports << "import BulletList from '@components/SidePanel/HelpComponents/HelpBulletList';" if components.include?('BulletList')
component_imports << "import Text from '@components/Text';" if components.include?('Text')
component_imports << "import TextLink from '@components/TextLink';" if components.include?('TextLink')
# Add style imports
base_imports << "import type {ThemeStyles} from '@styles/index';"
(base_imports + component_imports).join("\n")
def self.generate_imports(components)
base_imports = [
"import type {ReactNode} from 'react';",
"import React from 'react';",
]

# Always include React Native
base_imports << "import {#{(['View'] & components).join(', ')}} from 'react-native';"

# Add component-specific imports
component_imports = []
component_imports << "import BulletList from '@components/SidePanel/HelpComponents/HelpBulletList';" if components.include?('BulletList')
component_imports << "import Text from '@components/Text';" if components.include?('Text')
component_imports << "import TextLink from '@components/TextLink';" if components.include?('TextLink')

# Add style imports
base_imports << "import type {ThemeStyles} from '@styles/index';"

(base_imports + component_imports).join("\n")
end

def self.generate_ts_output(import_block, help_content_string)
Expand Down Expand Up @@ -158,41 +158,42 @@ def self.generate_ts_output(import_block, help_content_string)
export type {ContentComponent};
TS
end

def self.generate_help_content_tree()
tree = {}

@help_mapping.each do |route, node|
parts = route.sub(/^ref\//, '').sub(/\.md$/, '').split('/')
current = tree

parts.each_with_index do |part, i|
is_dynamic = part.start_with?(':') || part.match?(/^\[.*\]$/)
part_key = is_dynamic ? part : part.to_sym

contains_dash = part.include?('-')
part_key = (is_dynamic || contains_dash) ? part : part.to_sym

current[:children] ||= {}
current[:children][part_key] ||= {}

if i == parts.length - 1
jsx_content = html_node_to_RN(node, 1).rstrip

current[:children][part_key][:content] = <<~TS.chomp
({styles}: {styles: ThemeStyles}) => (
#{jsx_content}
)
TS
end

current = current[:children][part_key]
end
end

tree[:content] = <<~JSX
() => null
JSX
tree
end

def self.html_node_to_RN(node, indent_level = 0)
node_processors = {
'div' => method(:process_div),
Expand Down Expand Up @@ -227,26 +228,26 @@ def self.process_div(node, indent_level)
next if child.text? && child.text.strip.empty?
html_node_to_RN(child, indent_level + 1)
end.compact.join("\n")

"#{' ' * indent_level}<View>\n#{children}\n#{' ' * indent_level}</View>"
end

def self.process_heading(node, indent_level)
return "#{' ' * indent_level}<Text style={[styles.textHeadline#{node.name.upcase}, styles.mv4]}>#{node.text.strip}</Text>"
return "#{' ' * indent_level}<Text style={[styles.textHeadline#{node.name.upcase}, styles.mv4]}>#{CGI.escapeHTML(node.text).strip}</Text>"
end

def self.process_unordered_list(node, indent_level)
items = node.xpath('./li').map do |li|
contains_ul = li.xpath('.//ul').any?

li_parts = li.children.map { |child| html_node_to_RN(child, 0) }

if contains_ul

indented_li_parts = li_parts.map do |part|
part.lines.map { |line| "#{' ' * (indent_level + 3)}#{line.rstrip}" }.join("\n")
end.join("\n")

"#{' ' * (indent_level + 2)}<>\n#{indented_li_parts}\n#{' ' * (indent_level + 2)}</>"
else
"#{' ' * (indent_level + 2)}<Text style={styles.textNormal}>#{li_parts.join}</Text>"
Expand All @@ -269,20 +270,20 @@ def self.process_list_item(node, indent_level)

def self.process_paragraph(node, indent_level)
inner = node.children.map { |c| html_node_to_RN(c, indent_level + 1) }.join

style_classes = ['styles.textNormal']
style_classes << 'styles.mt4' if node.previous_element&.name == 'ul'
style_classes << 'styles.mb4' if node.next_element&.name == 'p'

"#{' ' * indent_level}<Text style={[#{style_classes.join(', ')}]}>#{inner.strip}</Text>"
end

def self.process_bold(node, indent_level)
"<Text style={styles.textBold}>#{node.text}</Text>"
"<Text style={styles.textBold}>#{CGI.escapeHTML(node.text)}</Text>"
end

def self.process_italic(node, indent_level)
"<Text style={styles.textItalic}>#{node.text}</Text>"
"<Text style={styles.textItalic}>#{CGI.escapeHTML(node.text)}</Text>"
end

def self.process_link(node, indent_level)
Expand All @@ -292,7 +293,7 @@ def self.process_link(node, indent_level)
end

def self.process_text(node, indent_level)
node.text
CGI.escapeHTML(node.text)
end

def self.process_default(node, indent_level)
Expand All @@ -311,16 +312,16 @@ def self.to_ts_object(obj, indent = 0)
if obj.is_a?(Array)
items = obj.map { |item| to_ts_object(item, indent + 1) }
return "[]" if items.empty?
return "[\n" +
items.map { |item| "#{spacing} #{item}" }.join(",\n") +

return "[\n" +
items.map { |item| "#{spacing} #{item}" }.join(",\n") +
"\n#{spacing}]"
end

obj.each do |key, value|
key_str = key.is_a?(Symbol) ? key.to_s : key.inspect
key_line_prefix = ' ' * (indent + 1) + "#{key_str}: "

if value.is_a?(Hash) || value.is_a?(Array)
nested = to_ts_object(value, indent + 1)
lines << key_line_prefix + nested + ","
Expand All @@ -333,11 +334,11 @@ def self.to_ts_object(obj, indent = 0)
lines << key_line_prefix + value.inspect + ","
end
end

lines << ' ' * indent + "}"
lines.join("\n")
end

end
end

3 changes: 1 addition & 2 deletions help/ref/search/index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
---
layout: product
title: Expensify Chat
title: Reports
---


# Reports

Virtually all data can be analyzed and reported upon in the Reports page. The major elements of this page include:
Expand Down
18 changes: 0 additions & 18 deletions help/ref/settings/index.md

This file was deleted.

1 change: 1 addition & 0 deletions help/ref/settings/profile/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/settings/security/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/settings/subscription/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/categories/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/company-cards/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/distance-rates/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/expensify-card/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/invoices/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Empty file.
1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/overview/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/per-diem/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Empty file.
1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/rules/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/tags/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/taxes/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions help/ref/workspaces/:policyID/workflows/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

76 changes: 16 additions & 60 deletions help/ref/workspaces/index.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,29 @@
---
layout: product
title: Expensify Chat
title: Workspaces
---

# Workspaces
# Workspaces 101

Workspaces help you manage company expenses, enforce policies, and integrate with accounting software. Each workspace has its own rules, settings, and features.
Think of a workspace as mission control for your company’s expenses. It’s where you set the rules, invite the team, and connect to your accounting tools. Each workspace runs independently, so you can keep things tidy across departments, entities, or clients.

## Creating a Workspace
## Create a new workspace
Hit the **New workspace** button to get started. Add a name, set a default currency, and you’re ready to get started customizing the workspace settings!

**To create a new workspace:**
1. In the left-hand menu, select **Workspaces**
2. Click **New workspace**
3. Click **Name** to give your workspace a name
4. Click **Default Currency** to set your preferred currency
## Invite your team
Add teammates to your workspace to manage expenses and approvals in one central place:
- Members can submit and approve reports they’re assigned to.
- Admins can approve all reports and manage workspace settings.

**Your first workspace includes:**
- Free 30-day trial
- Access to Setup Specialist via #admins chat room
- Help from Concierge in your Inbox
## Automate approvals
Toggle on **Add Approvals** under **Workflows** to set a default first approver. Create custom approval flows for individual team members if needed.

## Managing Members
## Connect your accounting system
Link your workspace with QuickBooks Online, Xero, NetSuite, or Sage Intacct to sync expenses like a pro.

**To invite team members:**
1. Click **Members** in the left-hand menu
2. Click **Invite member**
3. Enter names, emails, or phone numbers
4. Click **Next**, add an optional message, and click **Invite**

**Member vs Admin roles:**
- **Members** can submit their own reports and approve assigned reports
- **Admins** can approve all workspace reports, view all reports, and edit workspace settings

**To assign admin roles:**
1. Select **Members** in the left-hand menu
2. Click a member's name
3. Click **Role** and select **Admin**

## Key Features

**Categories** - Organize and track expenses (imported automatically if connected to accounting software)

**Approval Workflows** - Automate expense report reviews:
- Toggle **Add Approvals** on under **Workflows**
- Set a default first approver for all expenses
- Create custom workflows for specific members

**Accounting Integrations** - Connect to:
- QuickBooks Online
- Xero
- NetSuite
- Sage Intacct

**Additional Features** (enable via **More Features**):
- Expensify Cards for company spending
- Distance tracking for mileage
- Tags for detailed expense coding
- Company card connections

## Workspace Settings

Access all workspace configuration from the **Workspaces** tab:
- **Overview** - Name, currency, description, and sharing options
- **Members** - Invite, remove, and manage member roles
- **Categories** - Add and organize expense categories
- **Workflows** - Set up approval and payment processes
- **More Features** - Enable additional workspace capabilities
## Enhance your workspace with extra features
Under **More Features**, enable extras like the Expensify Card, distance rates, custom categories and tags, and company card connections.

---

**Tip:** Use the **Share** option on your workspace profile to get an invite link or QR code for easy member onboarding.

**Tip:** If you manage multiple departments, clients, or entities, consider creating multiple workspaces. Separate workspaces can help keep settings, approvals, and payments organized and more automated.
Loading
Loading