Compare commits

...
Author SHA1 Message Date
Eric Lewis af1137b3c3 chore(deployment): 0.1.8 2024-02-29 00:47:19 -07:00
Eric Lewisandnabarun a7810a34c9 feat: one click deployer (#139)
* feat: one click deployer

This adds a script for deploying whatever is the latest on main, essentially automating the changes specified in the readme.

* fix

* repurpose existing script

* clean up

* fix

* fix

* Update latest record version

---------

Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
2024-02-29 13:11:01 +05:30
Vivian Phung ff9b08e91d Merge pull request #143 from snowball-tools/template-card-redirect-bug
Template card redirect bug
2024-02-29 00:38:12 -07:00
nabarun 94a9bf88a9 Remove check for .env in build script (#144) 2024-02-29 12:51:32 +05:30
Vivian Phung dd5f93050d fix navigate 2024-02-29 00:01:56 -07:00
Vivian Phung 5946755749 fix navigate 2024-02-28 23:52:37 -07:00
Vivian Phung 4532a962d8 fix navigate 2024-02-28 23:44:10 -07:00
Sushan Yadav 6a108c1a1b Merge pull request #137 from snowball-tools/sushan/T-4914-project-overview-layout
Project overview layout
2024-02-29 09:57:12 +05:45
Sushan Yadav 0fb378386a chore: update comment 2024-02-28 20:51:58 +05:45
Sushan Yadav 563059e887 chore: comment fixes - tab issues and hardcoded strings 2024-02-28 20:46:08 +05:45
Sushan Yadav f34eb48d1e feat: add layout 2024-02-28 17:35:02 +05:45
Sushan Yadav acf5aab26f Merge branch 'main' into sushan/T-4914-project-overview-layout 2024-02-28 16:57:32 +05:45
Sushan Yadav 6b6582c287 feat: top section 2024-02-28 16:56:28 +05:45
Sushan Yadav b43ee3b7bb feat: add deplyment infos 2024-02-28 16:39:26 +05:45
Wahyu Kurniawan b1bf47d104 [T-4921: feat] Update project card component (#136)
* 🎨 style: add hover interaction to the card

* ️ feat: make the whole card clickable

* 🎨 style: adjust hovered background for wavy border

* ♻️ refactor: move wavy border class to project card theme

* 🎨 style: add transition when hover

* 📝 docs: add todo to experiment using `Link` componnt
2024-02-28 16:50:55 +07:00
Wahyu KurniawanandZachery Ng 8ee61c0c85 [T-4910: feat] Re-styling dashboard, create project layout, project template card (#135)
* 🎨 style: adjust wavy border and add layout wavy border

* ♻️ refactor: change sidebar to use `nav`

* ♻️ refactor: org slug dashboard layout

* ♻️ refactor: create project layout and restyling it

* ♻️ refactor: remove unused style

* ️ feat: restyling template card

* ️ feat: create template icon component

* ️ feat: use `h2` for layout title

* ️ feat: Add isComingSoon property to templates and handle click event in TemplateCard component

* ♻️ refactor: WavyBorder component and update CreateProjectLayout

* 🎨 style: update button medium size padding

* 🎨 style: update layout shadow and add new shadow for the template card

* ️ feat: add wavy border gradient and line svg assets

* refactor: update wavy border svg

* 🎨 style: adjust template card name and arrow also responsive of the list of template

---------

Co-authored-by: Zachery Ng <zachery.ng@gmail.com>
2024-02-28 16:22:54 +07:00
Andre Hadianto 33aa75e341 Merge pull request #133 from snowball-tools/andrehadianto/T-4906-create-project-connect-github
[T-4906] create project connect GitHub
2024-02-28 15:55:59 +08:00
Sushan Yadav a238610522 Reskin Project Overview - Activity section (#134)
* feat: reskin w/ new design system

* feat: add sepeator calc in comment

* chore: limit commit message to 4 line

* comment fixes

* chore: use Heading component
2024-02-28 13:31:41 +05:45
Wahyu Kurniawan 62734308fc [T-4907: style] Re-styling home page & sidebar component (#132)
* ️ feat: create heading component

* ♻️ refactor: move sidebar inside shared components

* ️ feat: add polymorphic prop type for heading component

* 🎨 style: re-styling project list page

* ♻️ refactor: remove `.env`

* 🎨 style: set default font weight to normal for heading component
2024-02-28 12:50:30 +07:00
59 changed files with 1536 additions and 478 deletions
-8
View File
@@ -9,14 +9,6 @@ if [[ -d "$DEST_DIR" ]]; then
exit 1
fi
if [[ -f "$PKG_DIR/.env" ]]; then
echo "Using existing .env file"
else
mv "$PKG_DIR/.env.example" "$PKG_DIR/.env"
echo "Created .env file. Please populate with the correct values."
exit 1
fi
cat > $PKG_DIR/.env <<EOF
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url'
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
+1 -1
View File
@@ -3,7 +3,7 @@ services:
restEndpoint: http://console.laconic.com:1317
gqlEndpoint: http://console.laconic.com:9473/api
chainId: laconic_9000-1
gas: 1000000
gas: 1200000
fees: 200000aphoton
userKey: 0524fc22ea0a12e6c5cc4cfe08e73c95dffd0ab5ed72a59f459ed33134fa3b16
bondId: 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac
+64 -4
View File
@@ -1,11 +1,71 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
# Increment application-record version
APPLICATION_RECORD_FILE="./records/application-record.yml"
if [ -f "$APPLICATION_RECORD_FILE" ]; then
# Extract current version and increment it
CURRENT_VERSION=$(grep 'version:' $APPLICATION_RECORD_FILE | head -1 | awk '{print $2}')
IFS='.' read -ra ADDR <<< "$CURRENT_VERSION"
VERSION_NUMBER=${ADDR[2]}
NEW_VERSION_NUMBER=$((VERSION_NUMBER + 1))
NEW_APPLICATION_VERSION="${ADDR[0]}.${ADDR[1]}.$NEW_VERSION_NUMBER"
else
# If file does not exist, start from version 0.0.1
NEW_APPLICATION_VERSION="0.0.1"
fi
# Generate application-deployment-request.yml
cat > ./records/application-deployment-request.yml <<EOF
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: snowballtools-base-frontend@$PACKAGE_VERSION
application: crn://snowballtools/applications/snowballtools-base-frontend@$PACKAGE_VERSION
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
EOF
# Generate application-record.yml with incremented version
cat > ./records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: snowballtools-base-frontend
app_version: $PACKAGE_VERSION
EOF
echo "Files generated successfully."
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
RECORD_FILE=records/application-record.yml
CONFIG_FILE=config.yml
RCD_APP_VERSION="0.1.3"
REPO_REF="513ca69d01bee857cf207a0605483205b384e218"
# Publish ApplicationRecord
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
@@ -15,8 +75,8 @@ echo $RECORD_ID
# Set name to record
REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${RCD_APP_VERSION}" "$RECORD_ID"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${REPO_REF}" "$RECORD_ID"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
# Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
@@ -1,21 +1,18 @@
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: snowballtools-base-frontend@0.1.3
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.3
name: snowballtools-base-frontend@0.1.8
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.8
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
# If GitHub client ID is changed, same ID and corresponding secret has to be set in backend config
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
# New config env after changes for image upload PWA
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
# Set CURRENT_DATE_TIME; Use command date -u
note: Added by Snowball @ Tue Feb 27 17:24:06 UTC 2024
note: Added by Snowball @ Thu Feb 29 07:46:48 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
repository_ref: a7810a34c90d5bfe0c6cc0697ad9aaf79b1c41d7
@@ -1,10 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.11
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
version: 0.0.27
repository_ref: a7810a34c90d5bfe0c6cc0697ad9aaf79b1c41d7
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
app_type: webapp
# name is set to repo name
name: snowballtools-base-frontend
# app_version is set from package.json
app_version: 0.1.3
app_version: 0.1.8
-7
View File
@@ -1,7 +0,0 @@
REACT_APP_SERVER_URL = 'http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID =
REACT_APP_GITHUB_PWA_TEMPLATE_REPO =
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO =
REACT_APP_WALLET_CONNECT_ID =
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "0.1.3",
"version": "0.1.8",
"private": true,
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.0.19",
-10
View File
@@ -1,10 +0,0 @@
<svg width="333" height="5" viewBox="0 0 333 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 4L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L25.3955 1.30714C27.3808 0.47125 29.6192 0.47125 31.6045 1.30714L34.8955 2.69286C36.8808 3.52875 39.1192 3.52875 41.1045 2.69286L44.3955 1.30714C46.3808 0.47125 48.6192 0.47125 50.6045 1.30714L53.8955 2.69286C55.8808 3.52875 58.1192 3.52875 60.1045 2.69286L63.3955 1.30714C65.3808 0.47125 67.6192 0.47125 69.6045 1.30714L72.8955 2.69286C74.8808 3.52875 77.1192 3.52875 79.1045 2.69286L82.3955 1.30714C84.3808 0.47125 86.6192 0.47125 88.6045 1.30714L91.8955 2.69286C93.8808 3.52875 96.1192 3.52875 98.1045 2.69286L101.396 1.30714C103.381 0.47125 105.619 0.47125 107.604 1.30714L110.896 2.69286C112.881 3.52875 115.119 3.52875 117.104 2.69286L120.396 1.30714C122.381 0.47125 124.619 0.47125 126.604 1.30714L129.896 2.69286C131.881 3.52875 134.119 3.52875 136.104 2.69286L139.396 1.30714C141.381 0.47125 143.619 0.47125 145.604 1.30714L148.896 2.69286C150.881 3.52875 153.119 3.52875 155.104 2.69286L158.396 1.30714C160.381 0.47125 162.619 0.47125 164.604 1.30714L167.896 2.69286C169.881 3.52875 172.119 3.52875 174.104 2.69286L177.396 1.30714C179.381 0.47125 181.619 0.47125 183.604 1.30714L186.896 2.69286C188.881 3.52875 191.119 3.52875 193.104 2.69286L196.396 1.30714C198.381 0.47125 200.619 0.47125 202.604 1.30714L205.896 2.69286C207.881 3.52875 210.119 3.52875 212.104 2.69286L215.396 1.30714C217.381 0.47125 219.619 0.47125 221.604 1.30714L224.896 2.69286C226.881 3.52875 229.119 3.52875 231.104 2.69286L234.396 1.30714C236.381 0.47125 238.619 0.47125 240.604 1.30714L243.896 2.69286C245.881 3.52875 248.119 3.52875 250.104 2.69286L253.396 1.30714C255.381 0.47125 257.619 0.47125 259.604 1.30714L262.896 2.69286C264.881 3.52875 267.119 3.52875 269.104 2.69286L272.396 1.30714C274.381 0.47125 276.619 0.47125 278.604 1.30714L281.896 2.69286C283.881 3.52875 286.119 3.52875 288.104 2.69286L291.396 1.30714C293.381 0.47125 295.619 0.47125 297.604 1.30714L300.896 2.69286C302.881 3.52875 305.119 3.52875 307.104 2.69286L310.396 1.30714C312.381 0.47125 314.619 0.47125 316.604 1.30714L319.973 2.72566C321.913 3.54216 324.095 3.56183 326.049 2.78039L330.029 1.18845C331.936 0.425535 334.064 0.425535 335.971 1.18845L343 4" stroke="#DBEBF9"/>
<path d="M6.39555 1.30714L0 4H342.5L336.027 1.27434C334.087 0.457837 331.905 0.438174 329.951 1.21961L326.049 2.78039C324.095 3.56183 321.913 3.54216 319.973 2.72566L316.604 1.30714C314.619 0.47125 312.381 0.47125 310.396 1.30714L307.104 2.69286C305.119 3.52875 302.881 3.52875 300.896 2.69286L297.604 1.30714C295.619 0.47125 293.381 0.47125 291.396 1.30714L288.104 2.69286C286.119 3.52875 283.881 3.52875 281.896 2.69286L278.604 1.30714C276.619 0.47125 274.381 0.47125 272.396 1.30714L269.104 2.69286C267.119 3.52875 264.881 3.52875 262.896 2.69286L259.604 1.30714C257.619 0.47125 255.381 0.47125 253.396 1.30714L250.104 2.69286C248.119 3.52875 245.881 3.52875 243.896 2.69286L240.604 1.30714C238.619 0.47125 236.381 0.47125 234.396 1.30714L231.104 2.69286C229.119 3.52875 226.881 3.52875 224.896 2.69286L221.604 1.30714C219.619 0.47125 217.381 0.47125 215.396 1.30714L212.104 2.69286C210.119 3.52875 207.881 3.52875 205.896 2.69286L202.604 1.30714C200.619 0.47125 198.381 0.47125 196.396 1.30714L193.104 2.69286C191.119 3.52875 188.881 3.52875 186.896 2.69286L183.604 1.30714C181.619 0.47125 179.381 0.47125 177.396 1.30714L174.104 2.69286C172.119 3.52875 169.881 3.52875 167.896 2.69286L164.604 1.30714C162.619 0.47125 160.381 0.47125 158.396 1.30714L155.104 2.69286C153.119 3.52875 150.881 3.52875 148.896 2.69286L145.604 1.30714C143.619 0.47125 141.381 0.47125 139.396 1.30714L136.104 2.69286C134.119 3.52875 131.881 3.52875 129.896 2.69286L126.604 1.30714C124.619 0.47125 122.381 0.47125 120.396 1.30714L117.104 2.69286C115.119 3.52875 112.881 3.52875 110.896 2.69286L107.604 1.30714C105.619 0.47125 103.381 0.47125 101.396 1.30714L98.1045 2.69286C96.1192 3.52875 93.8808 3.52875 91.8955 2.69286L88.6045 1.30714C86.6192 0.47125 84.3808 0.47125 82.3955 1.30714L79.1045 2.69286C77.1192 3.52875 74.8808 3.52875 72.8955 2.69286L69.6045 1.30714C67.6192 0.47125 65.3808 0.47125 63.3955 1.30714L60.1045 2.69286C58.1192 3.52875 55.8808 3.52875 53.8955 2.69286L50.6045 1.30714C48.6192 0.47125 46.3808 0.47125 44.3955 1.30714L41.1045 2.69286C39.1192 3.52875 36.8808 3.52875 34.8955 2.69286L31.6045 1.30714C29.6192 0.47125 27.3808 0.47125 25.3955 1.30714L22.1045 2.69286C20.1192 3.52875 17.8808 3.52875 15.8955 2.69286L12.6045 1.30714C10.6192 0.47125 8.38078 0.47125 6.39555 1.30714Z" fill="url(#paint0_linear_1729_11298)"/>
<defs>
<linearGradient id="paint0_linear_1729_11298" x1="171.25" y1="0" x2="171.25" y2="4" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6F4FF"/>
<stop offset="1" stop-color="#F9FCFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

@@ -0,0 +1,4 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M-12.6045 1.27694L-19 3.9698H37.5L34.8955 2.66266L31.6045 1.27694C29.6192 0.441052 27.3808 0.441052 25.3955 1.27694L22.1045 2.66266C20.1192 3.49855 17.8808 3.49855 15.8955 2.66266L12.6045 1.27694C10.6192 0.441052 8.38078 0.441052 6.39555 1.27694L3.10446 2.66266C1.11922 3.49855 -1.11922 3.49855 -3.10445 2.66266L-6.39554 1.27694C-8.38078 0.441052 -10.6192 0.441052 -12.6045 1.27694Z" fill="currentColor"/>
<path d="M25.7836 1.58773L22.4925 2.97346C20.3832 3.86159 18.0049 3.86159 15.8955 2.97346L12.6045 1.58774C10.7433 0.804089 8.64476 0.804088 6.7836 1.58773L3.49251 2.97346C1.3832 3.86159 -0.99514 3.86159 -3.10445 2.97346L-6.39554 1.58774C-8.2567 0.804089 -10.3552 0.804088 -12.2164 1.58773L-18.6119 4.2806L-19 3.35896L-12.6045 0.666099C-10.4951 -0.222033 -8.1168 -0.222033 -6.00749 0.6661L-2.7164 2.05182C-0.855238 2.83547 1.2433 2.83547 3.10446 2.05182L6.39555 0.666099C8.50486 -0.222033 10.8832 -0.222033 12.9925 0.6661L16.2836 2.05182C18.1448 2.83547 20.2433 2.83547 22.1045 2.05182L25.3955 0.666099L25.7836 1.58773Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,15 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1177_19315)">
<path d="M-12.6045 1.27694L-19 3.9698H37.5L34.8955 2.66266L31.6045 1.27694C29.6192 0.441052 27.3808 0.441052 25.3955 1.27694L22.1045 2.66266C20.1192 3.49855 17.8808 3.49855 15.8955 2.66266L12.6045 1.27694C10.6192 0.441052 8.38078 0.441052 6.39555 1.27694L3.10446 2.66266C1.11922 3.49855 -1.11922 3.49855 -3.10445 2.66266L-6.39554 1.27694C-8.38078 0.441052 -10.6192 0.441052 -12.6045 1.27694Z" fill="url(#paint0_linear_1177_19315)"/>
<path d="M25.7836 1.58773L22.4925 2.97346C20.3832 3.86159 18.0049 3.86159 15.8955 2.97346L12.6045 1.58774C10.7433 0.804089 8.64476 0.804088 6.7836 1.58773L3.49251 2.97346C1.3832 3.86159 -0.99514 3.86159 -3.10445 2.97346L-6.39554 1.58774C-8.2567 0.804089 -10.3552 0.804088 -12.2164 1.58773L-18.6119 4.2806L-19 3.35896L-12.6045 0.666099C-10.4951 -0.222033 -8.1168 -0.222033 -6.00749 0.6661L-2.7164 2.05182C-0.855238 2.83547 1.2433 2.83547 3.10446 2.05182L6.39555 0.666099C8.50486 -0.222033 10.8832 -0.222033 12.9925 0.6661L16.2836 2.05182C18.1448 2.83547 20.2433 2.83547 22.1045 2.05182L25.3955 0.666099L25.7836 1.58773Z" fill="#DBEBF9"/>
</g>
<defs>
<linearGradient id="paint0_linear_1177_19315" x1="9.25" y1="0.650024" x2="9.25" y2="3.9698" gradientUnits="userSpaceOnUse">
<stop stop-color="#EBF6FF"/>
<stop offset="1" stop-color="#F7FCFF" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_1177_19315">
<rect width="19" height="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,10 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1175_19292)">
<path d="M-9.5 0L-3.10445 2.69286C-1.11922 3.52875 1.11922 3.52875 3.10446 2.69286L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L28.5 0" stroke="#082F56" stroke-opacity="0.1"/>
</g>
<defs>
<clipPath id="clip0_1175_19292">
<rect width="19" height="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 507 B

+2 -2
View File
@@ -1,7 +1,6 @@
import React from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import OrgSlug from './pages/OrgSlug';
import Projects from './pages/org-slug';
import Settings from './pages/org-slug/Settings';
import {
@@ -11,11 +10,12 @@ import {
import ProjectSearchLayout from './layouts/ProjectSearch';
import Index from './pages';
import Login from './pages/Login';
import { DashboardLayout } from 'pages/org-slug/layout';
const router = createBrowserRouter([
{
path: ':orgSlug',
element: <OrgSlug />,
element: <DashboardLayout />,
children: [
{
element: <ProjectSearchLayout />,
+10 -5
View File
@@ -2,31 +2,36 @@ export default [
{
id: '1',
name: 'Progressive Web App (PWA)',
icon: '^',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '2',
name: 'Image Upload PWA',
icon: '^',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '3',
name: 'Kotlin',
icon: '^',
icon: 'kotlin',
repoFullName: '',
isComingSoon: true,
},
{
id: '4',
name: 'React Native',
icon: '^',
icon: 'react-native',
repoFullName: '',
isComingSoon: true,
},
{
id: '5',
name: 'Swift',
icon: '^',
icon: 'swift',
repoFullName: '',
isComingSoon: true,
},
];
@@ -8,6 +8,7 @@ export const projectCardTheme = tv({
'rounded-2xl',
'flex',
'flex-col',
'group',
],
upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'],
content: ['flex', 'flex-col', 'gap-1', 'flex-1'],
@@ -20,13 +21,15 @@ export const projectCardTheme = tv({
description: ['text-xs', 'text-elements-low-em'],
icons: ['flex', 'items-center', 'gap-1'],
lowerContent: [
'bg-surface-card-hovered',
'transition-colors',
'duration-150',
'px-4',
'py-4',
'flex',
'flex-col',
'gap-2',
'rounded-b-2xl',
'group-hover:bg-surface-card-hovered',
],
latestDeployment: ['flex', 'items-center', 'gap-2'],
deploymentStatusContainer: [
@@ -46,6 +49,12 @@ export const projectCardTheme = tv({
'items-center',
'gap-2',
],
wavyBorder: [
'bg-surface-card',
'transition-colors',
'duration-150',
'group-hover:bg-surface-card-hovered',
],
},
variants: {
status: {
@@ -1,4 +1,8 @@
import React, { ComponentPropsWithoutRef, MouseEvent } from 'react';
import React, {
ComponentPropsWithoutRef,
MouseEvent,
useCallback,
} from 'react';
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
import { Project } from 'gql-client';
import { Button } from 'components/shared/Button';
@@ -11,7 +15,7 @@ import {
WarningDiamondIcon,
} from 'components/shared/CustomIcon';
import { relativeTimeMs } from 'utils/time';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials';
import {
@@ -27,6 +31,8 @@ export interface ProjectCardProps
project: Project;
}
// TODO: Update the whole component to use `Link` from `react-router-dom` and remove the `useNavigate` hook,
// currently it's not possible to use `Link` because the dot menu is not a direct child of the `Link` component
export const ProjectCard = ({
className,
project,
@@ -38,14 +44,24 @@ export const ProjectCard = ({
// TODO: Update this to use the actual status from the API
const hasError = status === 'failure';
const navigate = useNavigate();
const handleOptionsClick = (
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
) => {
e.stopPropagation();
};
const handleClick = useCallback(() => {
navigate(`projects/${project.id}`);
}, [project.id, navigate]);
return (
<div {...props} className={theme.wrapper({ className })}>
<div
{...props}
className={theme.wrapper({ className })}
onClick={handleClick}
>
{/* Upper content */}
<div className={theme.upperContent()}>
{/* Icon container */}
@@ -54,14 +70,13 @@ export const ProjectCard = ({
imageSrc={project.icon}
initials={getInitials(project.name)}
/>
{/* </div> */}
{/* Title and website */}
<Link to={`projects/${project.id}`} className={theme.content()}>
<div className={theme.content()}>
<p className={theme.title()}>{project.name}</p>
<p className={theme.description()}>
{project.deployments[0]?.domain?.name ?? 'No domain'}
</p>
</Link>
</div>
{/* Icons */}
<div className={theme.icons()}>
{hasError && <WarningDiamondIcon className="text-elements-danger" />}
@@ -87,7 +102,7 @@ export const ProjectCard = ({
</div>
</div>
{/* Wave */}
<WavyBorder />
<WavyBorder variant="stroke-and-fill" className={theme.wavyBorder()} />
{/* Lower content */}
<div className={theme.lowerContent()}>
{/* Latest deployment */}
@@ -1,53 +0,0 @@
import React from 'react';
import toast from 'react-hot-toast';
import { IconButton, Typography } from '@material-tailwind/react';
import { Link } from 'react-router-dom';
interface TemplateDetails {
id: string;
name: string;
icon: string;
}
interface TemplateCardProps {
template: TemplateDetails;
isGitAuth: boolean;
}
const CardDetails = ({ template }: { template: TemplateDetails }) => {
return (
<div className="h-14 group bg-gray-200 border-gray-200 rounded-lg shadow p-4 flex items-center justify-between">
<Typography className="grow" placeholder={''}>
{template.icon} {template.name}
</Typography>
<div>
<IconButton
size="sm"
className="rounded-full hidden group-hover:block"
placeholder={''}
>
{'>'}
</IconButton>
</div>
</div>
);
};
const TemplateCard: React.FC<TemplateCardProps> = ({ template, isGitAuth }) => {
return isGitAuth ? (
<Link to={`template?templateId=${template.id}`}>
<CardDetails template={template} />
</Link>
) : (
<a
onClick={() =>
toast.error('Connect Git account to start with a template')
}
>
<CardDetails template={template} />
</a>
);
};
export default TemplateCard;
@@ -0,0 +1,91 @@
import React, { ComponentPropsWithoutRef, useCallback } from 'react';
import { Button } from 'components/shared/Button';
import {
ArrowRightCircleIcon,
ClockOutlineIcon,
TemplateIcon,
TemplateIconType,
} from 'components/shared/CustomIcon';
import { Tag } from 'components/shared/Tag';
import { useNavigate, useParams } from 'react-router-dom';
import { useToast } from 'components/shared/Toast';
import { cn } from 'utils/classnames';
export interface TemplateDetail {
id: string;
name: string;
icon: string;
repoFullName?: string;
isComingSoon?: boolean;
}
export interface TemplateCardProps extends ComponentPropsWithoutRef<'div'> {
template: TemplateDetail;
isGitAuth: boolean;
}
export const TemplateCard: React.FC<TemplateCardProps> = ({
template,
isGitAuth,
}: TemplateCardProps) => {
const { toast, dismiss } = useToast();
const navigate = useNavigate();
const { orgSlug } = useParams();
const handleClick = useCallback(() => {
if (template?.isComingSoon) {
return toast({
id: 'coming-soon',
title: 'This template is coming soon',
variant: 'info',
onDismiss: dismiss,
});
}
if (isGitAuth) {
return navigate(
`/${orgSlug}/projects/create/template?templateId=${template.id}`,
);
}
return toast({
id: 'connect-git-account',
title: 'Connect Git account to start with a template',
variant: 'error',
onDismiss: dismiss,
});
}, [orgSlug, dismiss, isGitAuth, navigate, template, toast]);
return (
<button
className={cn(
'flex items-center gap-3 px-3 py-3 bg-base-bg-alternate hover:bg-base-bg-emphasized rounded-2xl group relative',
{
'cursor-default': template?.isComingSoon,
},
)}
onClick={handleClick}
>
{/* Icon */}
<div className="px-1 py-1 rounded-xl bg-base-bg border border-border-interactive/10 shadow-card-sm">
<TemplateIcon type={template.icon as TemplateIconType} />
</div>
{/* Name */}
<p className="flex-1 text-left text-sm tracking-tighter text-elements-high-em">
{template.name}
</p>
{template?.isComingSoon ? (
<Tag size="xs" type="neutral" leftIcon={<ClockOutlineIcon />}>
Soon
</Tag>
) : (
<Button
variant="tertiary"
size="sm"
iconOnly
className="group-hover:flex hidden absolute right-3"
>
<ArrowRightCircleIcon />
</Button>
)}
</button>
);
};
@@ -0,0 +1 @@
export * from './TemplateCard';
@@ -1,46 +0,0 @@
import React from 'react';
import { Typography, IconButton, Avatar } from '@material-tailwind/react';
import { relativeTimeISO } from '../../../utils/time';
import { GitCommitWithBranch } from '../../../types';
interface ActivityCardProps {
activity: GitCommitWithBranch;
}
const ActivityCard = ({ activity }: ActivityCardProps) => {
return (
<div className="group flex gap-2 hover:bg-gray-200 rounded mt-1">
<div className="w-8">
<Avatar
src={activity.author?.avatar_url}
variant="rounded"
size="sm"
placeholder={''}
/>
</div>
<div className="grow">
<Typography placeholder={''}>{activity.commit.author?.name}</Typography>
<Typography variant="small" color="gray" placeholder={''}>
{relativeTimeISO(activity.commit.author!.date!)} ^{' '}
{activity.branch.name}
</Typography>
<Typography variant="small" color="gray" placeholder={''}>
{activity.commit.message}
</Typography>
</div>
<div className="mr-2 self-center hidden group-hover:block">
<IconButton
size="sm"
className="rounded-full bg-gray-600"
placeholder={''}
>
{'>'}
</IconButton>
</div>
</div>
);
};
export default ActivityCard;
@@ -0,0 +1,28 @@
import React from 'react';
import { GitCommitWithBranch } from 'types';
import { Heading } from 'components/shared/Heading';
import ActivityCard from './ActivityCard';
import { Button } from 'components/shared/Button';
export const Activity = ({
activities,
}: {
activities: GitCommitWithBranch[];
}) => {
return (
<div className="col-span-2 mr-1">
<div className="flex items-center justify-between">
<Heading className="text-lg leading-6 font-medium">Activity</Heading>
<Button variant="tertiary" size="sm">
See all
</Button>
</div>
<div className="mt-5">
{activities.map((activity, index) => {
return <ActivityCard activity={activity} key={`activity-${index}`} />;
})}
</div>
</div>
);
};
@@ -0,0 +1,86 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { GitCommitWithBranch } from '../../../../../types';
import { Avatar } from 'components/shared/Avatar';
import { Button } from 'components/shared/Button';
import {
ArrowRightCircleFilledIcon,
BranchStrokeIcon,
} from 'components/shared/CustomIcon';
import { formatDistance } from 'date-fns';
import { getInitials } from 'utils/geInitials';
interface ActivityCardProps {
activity: GitCommitWithBranch;
}
const ActivityCard = ({ activity }: ActivityCardProps) => {
const formattedDate = formatDistance(
new Date(activity.commit.author!.date!),
new Date(),
{
addSuffix: true,
},
);
return (
<>
<Link
to={activity.html_url}
target="_blank"
className="p-3 gap-2 focus-within:ring-2 focus-within:ring-controls-primary/40 focus:outline-none rounded-xl transition-colors hover:bg-base-bg-alternate flex group"
>
<div>
<Avatar
type="orange"
size={36}
initials={getInitials(activity.commit.author?.name ?? '')}
imageSrc={activity.author?.avatar_url}
/>
</div>
<div className="flex-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
{activity.commit.author?.name}
</span>
<span className="text-elements-low-em text-xs flex items-center gap-2">
<span title={formattedDate} className="whitespace-nowrap">
{formattedDate}
</span>
{/* Dot */}
<span className="w-0.5 h-0.5 rounded-full bg-border-interactive-hovered"></span>
<span className="flex justify-center items-center gap-1.5">
<div>
<BranchStrokeIcon className="w-3 h-3" />
</div>
<span
title={activity.branch.name}
// pseudo to increase hover area
className="before:absolute relative before:h-5 before:-top-4 before:inset-x-0"
>
<span className="line-clamp-1">{activity.branch.name}</span>
</span>
</span>
</span>
<p
title={activity.commit.message}
className="text-sm line-clamp-4 tracking-tight text-elements-mid-em mt-2"
>
{activity.commit.message}
</p>
</div>
<Button
aria-label="Go to commit"
variant="unstyled"
tabIndex={-1}
className="p-0 text-elements-low-em group-focus-within:opacity-100 group-hover:opacity-100 opacity-0 transition-all"
leftIcon={<ArrowRightCircleFilledIcon className="w-5 h-5" />}
/>
</Link>
{/* Separator calc => 100% - 36px (avatar) - 12px (padding-left) - 8px (gap)
*/}
<div className="pt-2 mb-2 ml-auto h-1 w-[calc(100%-36px-12px-8px)] border-b border-border-separator last:border-b-transparent"></div>
</>
);
};
export default ActivityCard;
@@ -0,0 +1 @@
export * from './Activity';
@@ -0,0 +1,29 @@
import React from 'react';
import { PropsWithChildren } from 'react';
import { cloneElement } from 'utils/cloneElement';
interface OverviewInfoProps {
label: string;
icon: React.ReactNode;
}
export const OverviewInfo = ({
label,
icon,
children,
}: PropsWithChildren<OverviewInfoProps>) => {
const styledIcon = cloneElement({
element: icon,
className: 'w-4 h-4',
});
return (
<div className="flex justify-between gap-2 py-3 text-sm items-center">
<div className="flex gap-2 items-center text-elements-high-em">
{styledIcon}
{label}
</div>
<div>{children}</div>
</div>
);
};
@@ -150,7 +150,7 @@ export const buttonTheme = tv(
{
size: 'md',
iconOnly: true,
class: ['py-3.25', 'px-3.25'],
class: ['py-3', 'px-3'],
},
{
size: 'sm',
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ArrowRightCircleFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
{...props}
fill="none"
height="32"
viewBox="0 0 32 32"
width="32"
>
<path
clipRule="evenodd"
d="M29.3334 16C29.3334 8.63619 23.3638 2.66666 16 2.66666C8.63622 2.66666 2.66669 8.63619 2.66669 16C2.66669 23.3638 8.63622 29.3333 16 29.3333C23.3638 29.3333 29.3334 23.3638 29.3334 16ZM18.1144 17.3333L16.3905 19.0572C15.8698 19.5779 15.8698 20.4221 16.3905 20.9428C16.9112 21.4635 17.7555 21.4635 18.2762 20.9428L21.3334 17.8856C22.3748 16.8442 22.3748 15.1558 21.3334 14.1144L18.2762 11.0572C17.7555 10.5365 16.9112 10.5365 16.3905 11.0572C15.8698 11.5779 15.8698 12.4221 16.3905 12.9428L18.1144 14.6667H10.6667C9.93031 14.6667 9.33335 15.2636 9.33335 16C9.33335 16.7364 9.93031 17.3333 10.6667 17.3333H18.1144Z"
fill="currentColor"
fillRule="evenodd"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ArrowRightCircleIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="17"
height="16"
viewBox="0 0 17 16"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.3333 8C15.3333 4.3181 12.3486 1.33333 8.66667 1.33333C4.98477 1.33333 2 4.3181 2 8C2 11.6819 4.98477 14.6667 8.66667 14.6667C12.3486 14.6667 15.3333 11.6819 15.3333 8ZM10.2929 8.5L8.97978 9.81311C8.78452 10.0084 8.78452 10.325 8.97978 10.5202C9.17504 10.7155 9.49162 10.7155 9.68689 10.5202L11.3821 8.82495C11.8378 8.36934 11.8378 7.63065 11.3821 7.17504L9.68689 5.47977C9.49162 5.28451 9.17504 5.28451 8.97978 5.47977C8.78452 5.67504 8.78452 5.99162 8.97978 6.18688L10.2929 7.5H5.83333C5.55719 7.5 5.33333 7.72385 5.33333 8C5.33333 8.27614 5.55719 8.5 5.83333 8.5H10.2929Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,20 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const BranchStrokeIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M4.66667 4.99984C5.58714 4.99984 6.33333 4.25365 6.33333 3.33317C6.33333 2.4127 5.58714 1.6665 4.66667 1.6665C3.74619 1.6665 3 2.4127 3 3.33317C3 4.25365 3.74619 4.99984 4.66667 4.99984ZM4.66667 4.99984V10.9998M4.66667 10.9998C3.74619 10.9998 3 11.746 3 12.6665C3 13.587 3.74619 14.3332 4.66667 14.3332C5.58714 14.3332 6.33333 13.587 6.33333 12.6665C6.33333 11.746 5.58714 10.9998 4.66667 10.9998ZM4.66667 10.9998V9.33317C4.66667 8.59679 5.26362 7.99984 6 7.99984H10C10.7364 7.99984 11.3333 7.40288 11.3333 6.6665V4.99984M11.3333 4.99984C12.2538 4.99984 13 4.25365 13 3.33317C13 2.4127 12.2538 1.6665 11.3333 1.6665C10.4129 1.6665 9.66667 2.4127 9.66667 3.33317C9.66667 4.25365 10.4129 4.99984 11.3333 4.99984Z"
stroke="currentColor"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,41 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CalendarDaysIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M2.3335 5.00016H13.6668M4.33349 13.6668H11.6668C12.7714 13.6668 13.6668 12.7714 13.6668 11.6668V4.3335C13.6668 3.22893 12.7714 2.3335 11.6668 2.3335H4.3335C3.22893 2.3335 2.3335 3.22893 2.3335 4.33349V11.6668C2.3335 12.7714 3.22893 13.6668 4.33349 13.6668Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.6665 7.8335C4.6665 7.55735 4.89036 7.3335 5.1665 7.3335C5.44265 7.3335 5.6665 7.55735 5.6665 7.8335C5.6665 8.10964 5.44265 8.3335 5.1665 8.3335C4.89036 8.3335 4.6665 8.10964 4.6665 7.8335Z"
fill="currentColor"
/>
<path
d="M4.6665 10.8335C4.6665 10.5574 4.89036 10.3335 5.1665 10.3335C5.44265 10.3335 5.6665 10.5574 5.6665 10.8335C5.6665 11.1096 5.44265 11.3335 5.1665 11.3335C4.89036 11.3335 4.6665 11.1096 4.6665 10.8335Z"
fill="currentColor"
/>
<path
d="M7.49984 10.8335C7.49984 10.5574 7.72369 10.3335 7.99984 10.3335C8.27598 10.3335 8.49984 10.5574 8.49984 10.8335C8.49984 11.1096 8.27598 11.3335 7.99984 11.3335C7.72369 11.3335 7.49984 11.1096 7.49984 10.8335Z"
fill="currentColor"
/>
<path
d="M7.49984 7.8335C7.49984 7.55735 7.72369 7.3335 7.99984 7.3335C8.27598 7.3335 8.49984 7.55735 8.49984 7.8335C8.49984 8.10964 8.27598 8.3335 7.99984 8.3335C7.72369 8.3335 7.49984 8.10964 7.49984 7.8335Z"
fill="currentColor"
/>
<path
d="M10.3332 7.8335C10.3332 7.55735 10.557 7.3335 10.8332 7.3335C11.1093 7.3335 11.3332 7.55735 11.3332 7.8335C11.3332 8.10964 11.1093 8.3335 10.8332 8.3335C10.557 8.3335 10.3332 8.10964 10.3332 7.8335Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ClockOutlineIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M7.99984 5V8L9.99984 10M14.3332 8C14.3332 11.4978 11.4976 14.3333 7.99984 14.3333C4.50203 14.3333 1.6665 11.4978 1.6665 8C1.6665 4.5022 4.50203 1.66667 7.99984 1.66667C11.4976 1.66667 14.3332 4.5022 14.3332 8Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,22 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CursorBoxIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M13.5 7V3.83333C13.5 3.09695 12.903 2.5 12.1667 2.5H3.83333C3.09695 2.5 2.5 3.09695 2.5 3.83333V12.1667C2.5 12.903 3.09695 13.5 3.83333 13.5H7M9.43391 14.0852L7.5218 7.9391C7.44202 7.68269 7.68269 7.44202 7.9391 7.5218L14.0852 9.43391C14.3658 9.52122 14.4043 9.90262 14.1468 10.0443L11.5848 11.4534C11.5294 11.4838 11.4838 11.5294 11.4534 11.5848L10.0443 14.1468C9.90262 14.4043 9.52122 14.3658 9.43391 14.0852Z"
stroke="currentColor"
strokeWidth="1.05263"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,19 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const GithubStrokeIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M13.0194 4.73001C13.1722 4.23701 13.2212 3.71768 13.1634 3.20479C13.1055 2.6919 12.942 2.19656 12.6831 1.75001C12.6392 1.67398 12.5761 1.61085 12.5001 1.56697C12.424 1.52308 12.3378 1.49999 12.25 1.50001C11.6676 1.49879 11.093 1.6338 10.5721 1.89425C10.0512 2.1547 9.59845 2.53337 9.25 3.00001H7.75C7.40155 2.53337 6.94878 2.1547 6.42788 1.89425C5.90698 1.6338 5.33238 1.49879 4.75 1.50001C4.66221 1.49999 4.57597 1.52308 4.49994 1.56697C4.4239 1.61085 4.36077 1.67398 4.31687 1.75001C4.05803 2.19656 3.89452 2.6919 3.83664 3.20479C3.77877 3.71768 3.8278 4.23701 3.98062 4.73001C3.6717 5.26921 3.50623 5.87862 3.5 6.50001V7.00001C3.50105 7.84601 3.80817 8.66306 4.36464 9.30029C4.92111 9.93752 5.68935 10.3519 6.5275 10.4669C6.18538 10.9047 5.99968 11.4444 6 12V12.5H4.5C4.10218 12.5 3.72064 12.342 3.43934 12.0607C3.15804 11.7794 3 11.3978 3 11C3 10.6717 2.93534 10.3466 2.8097 10.0433C2.68406 9.73999 2.49991 9.46439 2.26777 9.23224C2.03562 9.00009 1.76002 8.81595 1.45671 8.69031C1.15339 8.56467 0.828305 8.50001 0.5 8.50001C0.367392 8.50001 0.240215 8.55269 0.146447 8.64645C0.0526784 8.74022 0 8.8674 0 9.00001C0 9.13262 0.0526784 9.25979 0.146447 9.35356C0.240215 9.44733 0.367392 9.50001 0.5 9.50001C0.897825 9.50001 1.27936 9.65804 1.56066 9.93935C1.84196 10.2207 2 10.6022 2 11C2 11.663 2.26339 12.2989 2.73223 12.7678C3.20107 13.2366 3.83696 13.5 4.5 13.5H6V14.5C6 14.6326 6.05268 14.7598 6.14645 14.8536C6.24021 14.9473 6.36739 15 6.5 15C6.63261 15 6.75979 14.9473 6.85355 14.8536C6.94732 14.7598 7 14.6326 7 14.5V12C7 11.6022 7.15804 11.2207 7.43934 10.9393C7.72064 10.658 8.10218 10.5 8.5 10.5C8.89782 10.5 9.27936 10.658 9.56066 10.9393C9.84196 11.2207 10 11.6022 10 12V14.5C10 14.6326 10.0527 14.7598 10.1464 14.8536C10.2402 14.9473 10.3674 15 10.5 15C10.6326 15 10.7598 14.9473 10.8536 14.8536C10.9473 14.7598 11 14.6326 11 14.5V12C11.0003 11.4444 10.8146 10.9047 10.4725 10.4669C11.3107 10.3519 12.0789 9.93752 12.6354 9.30029C13.1918 8.66306 13.4989 7.84601 13.5 7.00001V6.50001C13.4938 5.87862 13.3283 5.26921 13.0194 4.73001ZM12.5 7.00001C12.5 7.66305 12.2366 8.29893 11.7678 8.76778C11.2989 9.23662 10.663 9.50001 10 9.50001H7C6.33696 9.50001 5.70107 9.23662 5.23223 8.76778C4.76339 8.29893 4.5 7.66305 4.5 7.00001V6.50001C4.50613 6.00002 4.65582 5.51233 4.93125 5.09501C4.98259 5.02733 5.01585 4.94769 5.02788 4.8636C5.03991 4.77951 5.03031 4.69374 5 4.61438C4.86976 4.27851 4.80709 3.92023 4.81556 3.56009C4.82403 3.19995 4.90349 2.84501 5.04938 2.51563C5.45857 2.55965 5.85262 2.6952 6.2023 2.91224C6.55198 3.12928 6.84832 3.42223 7.06938 3.76938C7.11442 3.83982 7.17641 3.89784 7.24968 3.93813C7.32294 3.97842 7.40514 3.9997 7.48875 4.00001H9.51062C9.59455 4.00001 9.67713 3.97889 9.75075 3.93858C9.82437 3.89828 9.88666 3.84009 9.93188 3.76938C10.1529 3.4222 10.4492 3.12922 10.7989 2.91218C11.1486 2.69514 11.5427 2.55961 11.9519 2.51563C12.0976 2.8451 12.1768 3.20007 12.1851 3.56021C12.1933 3.92036 12.1304 4.27859 12 4.61438C11.9698 4.69298 11.9597 4.77788 11.9706 4.86138C11.9815 4.94487 12.0131 5.02432 12.0625 5.09251C12.3407 5.50984 12.4926 5.99853 12.5 6.50001V7.00001Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const LinkIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.99634 4.3125C5.99634 4.00184 6.24818 3.75 6.55884 3.75H12.1875C13.3266 3.75 14.25 4.67341 14.25 5.8125V12.1844C14.25 12.495 13.9982 12.7469 13.6875 12.7469C13.3769 12.7469 13.125 12.495 13.125 12.1844V5.8125C13.125 5.76745 13.1218 5.72315 13.1157 5.6798L5.27275 13.5227C5.05308 13.7424 4.69692 13.7424 4.47725 13.5227C4.25758 13.3031 4.25758 12.9469 4.47725 12.7273L12.3202 4.88432C12.2768 4.87818 12.2326 4.875 12.1875 4.875H6.55884C6.24818 4.875 5.99634 4.62316 5.99634 4.3125Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const StorageIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M13 3.42576C13 4.39738 10.7614 5.18502 8 5.18502C5.23858 5.18502 3 4.39738 3 3.42576M13 3.42576C13 2.45415 10.7614 1.6665 8 1.6665C5.23858 1.6665 3 2.45415 3 3.42576M13 3.42576V12.5739C13 13.5455 10.7614 14.3332 8 14.3332C5.23858 14.3332 3 13.5455 3 12.5739V3.42576M13 7.92562C13 8.89723 10.7614 9.68488 8 9.68488C5.23858 9.68488 3 8.89723 3 7.92562"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -33,3 +33,14 @@ export * from './GitHubLogo';
export * from './ClockIcon';
export * from './HorizontalDotIcon';
export * from './WarningDiamondIcon';
export * from './ArrowRightCircleIcon';
export * from './ClockOutlineIcon';
export * from './ArrowRightCircleFilledIcon';
export * from './GithubStrokeIcon';
export * from './BranchStrokeIcon';
export * from './StorageIcon';
export * from './LinkIcon';
export * from './CursorBoxIcon';
// Templates
export * from './templates';
@@ -0,0 +1,130 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from '../CustomIcon';
export const KotlinIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="41"
height="40"
viewBox="0 0 41 40"
fill="none"
{...props}
>
<g filter="url(#filter0_ii_417_1111)">
<rect
x="0.333496"
width="40"
height="40"
rx="8"
fill="url(#paint0_linear_417_1111)"
/>
<rect
x="0.74421"
y="0.410714"
width="39.1786"
height="39.1786"
rx="7.58929"
stroke="#082F56"
strokeOpacity="0.1"
strokeWidth="0.821429"
/>
<g clipPath="url(#clip0_417_1111)">
<path
d="M30.1908 29.8591H10.4766V10.1448H30.1908L20.3337 20.0019L30.1908 29.8591Z"
fill="url(#paint1_linear_417_1111)"
/>
<path
d="M30.1908 29.8591H10.4766V10.1448H30.1908L20.3337 20.0019L30.1908 29.8591Z"
fill="white"
/>
</g>
</g>
<defs>
<filter
id="filter0_ii_417_1111"
x="0.333496"
y="0"
width="40"
height="40"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"
/>
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_417_1111"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="effect1_innerShadow_417_1111"
result="effect2_innerShadow_417_1111"
/>
</filter>
<linearGradient
id="paint0_linear_417_1111"
x1="40.3335"
y1="-1.19209e-06"
x2="0.333497"
y2="40"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.00343514" stopColor="#E44857" />
<stop offset="0.4689" stopColor="#C711E1" />
<stop offset="1" stopColor="#7F52FF" />
</linearGradient>
<linearGradient
id="paint1_linear_417_1111"
x1="30.1908"
y1="10.1448"
x2="10.4766"
y2="29.8591"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.00343514" stopColor="#E44857" />
<stop offset="0.4689" stopColor="#C711E1" />
<stop offset="1" stopColor="#7F52FF" />
</linearGradient>
<clipPath id="clip0_417_1111">
<rect
width="19.7143"
height="19.7143"
fill="white"
transform="translate(10.4766 10.1429)"
/>
</clipPath>
</defs>
</CustomIcon>
);
};
@@ -0,0 +1,88 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from '../CustomIcon';
export const PWAIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
{...props}
>
<g filter="url(#filter0_ii_417_1121)">
<rect width="40" height="40" rx="8" fill="#12B785" />
<rect
x="0.410714"
y="0.410714"
width="39.1786"
height="39.1786"
rx="7.58929"
stroke="#082F56"
strokeOpacity="0.1"
strokeWidth="0.821429"
/>
<path
d="M24.5832 26.25H26.2498M12.9165 24.5833V14.5833C12.9165 13.6628 13.6627 12.9167 14.5832 12.9167H25.4165C26.337 12.9167 27.0832 13.6628 27.0832 14.5833V17.0833M12.9165 24.5833H11.6665V25.4167C11.6665 26.3371 12.4127 27.0833 13.3332 27.0833H22.0832M12.9165 24.5833H22.0832M27.0832 17.0833H23.7498C22.8294 17.0833 22.0832 17.8295 22.0832 18.75V26.25C22.0832 27.1705 22.8294 27.9167 23.7498 27.9167H27.0832C28.0036 27.9167 28.7498 27.1705 28.7498 26.25V18.75C28.7498 17.8295 28.0036 17.0833 27.0832 17.0833Z"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<filter
id="filter0_ii_417_1121"
x="0"
y="0"
width="40"
height="40"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"
/>
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_417_1121"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="effect1_innerShadow_417_1121"
result="effect2_innerShadow_417_1121"
/>
</filter>
</defs>
</CustomIcon>
);
};
@@ -0,0 +1,98 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from '../CustomIcon';
export const ReactNativeIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
{...props}
>
<g filter="url(#filter0_ii_417_1095)">
<rect width="40" height="40" rx="8" fill="#00C0F5" />
<rect
x="0.410714"
y="0.410714"
width="39.1786"
height="39.1786"
rx="7.58929"
stroke="#082F56"
strokeOpacity="0.1"
strokeWidth="0.821429"
/>
<path
d="M20.0004 21.7819C20.9849 21.7819 21.783 20.9838 21.783 19.9993C21.783 19.0148 20.9849 18.2167 20.0004 18.2167C19.0159 18.2167 18.2178 19.0148 18.2178 19.9993C18.2178 20.9838 19.0159 21.7819 20.0004 21.7819Z"
fill="white"
/>
<path
d="M19.9998 23.6515C25.2825 23.6515 29.565 22.0164 29.565 19.9993C29.565 17.9823 25.2825 16.3472 19.9998 16.3472C14.7171 16.3472 10.4346 17.9823 10.4346 19.9993C10.4346 22.0164 14.7171 23.6515 19.9998 23.6515Z"
stroke="white"
/>
<path
d="M16.8372 21.8246C19.4786 26.3996 23.0359 29.2908 24.7827 28.2822C26.5295 27.2737 25.8043 22.7474 23.163 18.1724C20.5216 13.5975 16.9643 10.7063 15.2175 11.7148C13.4707 12.7233 14.1959 17.2496 16.8372 21.8246Z"
stroke="white"
/>
<path
d="M16.837 18.173C14.1956 22.7479 13.4704 27.2742 15.2172 28.2828C16.964 29.2913 20.5213 26.4001 23.1627 21.8251C25.8041 17.2502 26.5292 12.7238 24.7824 11.7153C23.0356 10.7068 19.4783 13.598 16.837 18.173Z"
stroke="white"
/>
</g>
<defs>
<filter
id="filter0_ii_417_1095"
x="0"
y="0"
width="40"
height="40"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"
/>
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_417_1095"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="effect1_innerShadow_417_1095"
result="effect2_innerShadow_417_1095"
/>
</filter>
</defs>
</CustomIcon>
);
};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
import React, { useMemo } from 'react';
import { CustomIconProps } from '../CustomIcon';
import { ReactNativeIcon } from './ReactNativeIcon';
import { cloneIcon } from 'utils/cloneIcon';
import { PWAIcon } from './PWAIcon';
import { WebAppIcon } from './WebAppIcon';
import { KotlinIcon } from './KotlinIcon';
import { SwitfIcon } from './SwiftIcon';
const TEMPLATE_ICONS = [
'react-native',
'pwa',
'web',
'kotlin',
'swift',
] as const;
export type TemplateIconType = (typeof TEMPLATE_ICONS)[number];
export interface TemplateIconProps extends CustomIconProps {
type: TemplateIconType;
}
export const TemplateIcon = ({ type, ...props }: TemplateIconProps) => {
const renderIcon = useMemo(() => {
switch (type) {
case 'react-native':
return <ReactNativeIcon />;
case 'pwa':
return <PWAIcon />;
case 'web':
return <WebAppIcon />;
case 'kotlin':
return <KotlinIcon />;
case 'swift':
return <SwitfIcon />;
default:
throw new Error(`Invalid template icon type: ${type}`);
}
}, [type]);
return cloneIcon(renderIcon, props) as JSX.Element;
};
@@ -0,0 +1,87 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from '../CustomIcon';
export const WebAppIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="41"
height="40"
viewBox="0 0 41 40"
fill="none"
{...props}
>
<g filter="url(#filter0_ii_417_1126)">
<rect x="0.666504" width="40" height="40" rx="8" fill="#F63184" />
<rect
x="1.07722"
y="0.410714"
width="39.1786"
height="39.1786"
rx="7.58929"
stroke="#082F56"
strokeOpacity="0.1"
strokeWidth="0.821429"
/>
<path
d="M20.6667 27.9167C25.0389 27.9167 28.5833 24.3723 28.5833 20C28.5833 15.6278 25.0389 12.0833 20.6667 12.0833M20.6667 27.9167C16.2944 27.9167 12.75 24.3723 12.75 20C12.75 15.6278 16.2944 12.0833 20.6667 12.0833M20.6667 27.9167C18.8257 27.9167 17.3333 24.3723 17.3333 20C17.3333 15.6278 18.8257 12.0833 20.6667 12.0833M20.6667 27.9167C22.5076 27.9167 24 24.3723 24 20C24 15.6278 22.5076 12.0833 20.6667 12.0833M28.1667 20H13.1667"
stroke="white"
strokeLinecap="square"
/>
</g>
<defs>
<filter
id="filter0_ii_417_1126"
x="0.666504"
y="0"
width="40"
height="40"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"
/>
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_417_1126"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix
type="matrix"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="effect1_innerShadow_417_1126"
result="effect2_innerShadow_417_1126"
/>
</filter>
</defs>
</CustomIcon>
);
};
@@ -0,0 +1 @@
export * from './TemplateIcon';
@@ -5,8 +5,8 @@ import { Organization } from 'gql-client';
import { Option } from '@material-tailwind/react';
import { useDisconnect } from 'wagmi';
import { useGQLClient } from '../context/GQLClientContext';
import AsyncSelect from './shared/AsyncSelect';
import { useGQLClient } from 'context/GQLClientContext';
import AsyncSelect from 'components/shared/AsyncSelect';
import {
ChevronGrabberHorizontal,
FolderIcon,
@@ -14,10 +14,11 @@ import {
LifeBuoyIcon,
QuestionMarkRoundIcon,
SettingsSlidersIcon,
} from './shared/CustomIcon';
} from 'components/shared/CustomIcon';
import { Tabs } from 'components/shared/Tabs';
import { Heading } from 'components/shared/Heading';
const Sidebar = () => {
export const Sidebar = () => {
const { orgSlug } = useParams();
const navigate = useNavigate();
const client = useGQLClient();
@@ -42,20 +43,20 @@ const Sidebar = () => {
}, [disconnect, navigate]);
return (
<div className="flex flex-col h-full p-4 pt-10">
<div className="grow">
<Link to={`/${orgSlug}`}>
<div className="flex items-center space-x-3 mb-10 ml-2">
<img
src="/logo.svg"
alt="Snowball Logo"
className="h-8 w-8 rounded-lg"
/>
<span className="text-2xl font-bold text-snowball-900">
Snowball
</span>
</div>
</Link>
<nav className="flex flex-col h-full px-6 py-8 gap-9">
{/* Logo */}
<Link to={`/${orgSlug}`}>
<div className="flex items-center gap-3 px-2">
<img
src="/logo.svg"
alt="Snowball Logo"
className="h-10 w-10 rounded-lg"
/>
<Heading className="text-[24px] font-semibold">Snowball</Heading>
</div>
</Link>
{/* Switch organization */}
<div className="flex flex-1 flex-col gap-4">
<AsyncSelect
containerProps={{ className: 'h-14 border-none' }}
labelProps={{ className: 'before:border-none after:border-none' }}
@@ -82,7 +83,7 @@ const Sidebar = () => {
)}
arrow={<ChevronGrabberHorizontal className="h-4 w-4 text-gray-500" />}
>
{/* TODO: Show label organization and manage in option */}
{/* // TODO: Show label organization and manage in option */}
{organizations.map((org) => (
<Option key={org.id} value={org.slug}>
<div className="flex items-center space-x-3">
@@ -99,7 +100,7 @@ const Sidebar = () => {
</Option>
))}
</AsyncSelect>
<Tabs defaultValue="Projects" orientation="vertical" className="mt-10">
<Tabs defaultValue="Projects" orientation="vertical">
<Tabs.List>
{[
{ title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> },
@@ -118,9 +119,10 @@ const Sidebar = () => {
</Tabs.List>
</Tabs>
</div>
<div className="grow flex flex-col justify-end mb-8">
{/* Bottom navigation */}
<div className="flex flex-col justify-end">
<Tabs defaultValue="Projects" orientation="vertical">
{/* TODO: use proper link buttons */}
{/* // TODO: use proper link buttons */}
<Tabs.List>
<Tabs.Trigger icon={<GlobeIcon />} value="">
<a className="cursor-pointer" onClick={handleLogOut}>
@@ -136,8 +138,6 @@ const Sidebar = () => {
</Tabs.List>
</Tabs>
</div>
</div>
</nav>
);
};
export default Sidebar;
@@ -0,0 +1 @@
export * from './Sidebar';
@@ -8,7 +8,6 @@ export const tabsTheme = tv({
triggerWrapper: [
// Horizontal default
'px-1',
'pb-5',
'cursor-default',
'select-none',
'text-elements-low-em',
@@ -55,9 +54,13 @@ export const tabsTheme = tv({
'outline-none',
'leading-none',
'tracking-[-0.006em]',
'text-sm',
'rounded-md',
// Horizontal default
'data-[orientation=horizontal]:focus-ring',
'data-[orientation=horizontal]:h-10',
// select direct child of data-[orientation=horizontal]
'[&[data-orientation=horizontal]_>_*]:h-full',
// Vertical
'data-[orientation=vertical]:gap-2',
'data-[orientation=vertical]:justify-start',
@@ -5,25 +5,25 @@ export const tagTheme = tv(
{
slots: {
wrapper: ['flex', 'gap-1.5', 'rounded-lg', 'border'],
icon: ['h-4', 'w-4'],
icon: [],
label: ['font-inter', 'text-xs'],
},
variants: {
type: {
attention: {
icon: ['text-elements-warning'],
wrapper: ['text-elements-warning'],
},
negative: {
icon: ['text-elements-danger'],
wrapper: ['text-elements-danger'],
},
positive: {
icon: ['text-elements-success'],
wrapper: ['text-elements-success'],
},
emphasized: {
icon: ['text-elements-on-secondary'],
wrapper: ['text-elements-on-secondary'],
},
neutral: {
icon: ['text-elements-mid-em'],
wrapper: ['text-elements-mid-em'],
},
},
style: {
@@ -36,9 +36,11 @@ export const tagTheme = tv(
size: {
sm: {
wrapper: ['px-2', 'py-2'],
icon: ['h-4', 'w-4'],
},
xs: {
wrapper: ['px-2', 'py-1.5'],
wrapper: ['px-2', 'py-1'],
icon: ['h-3', 'w-3'],
},
},
},
@@ -27,6 +27,8 @@ export const Tag = ({
type = 'attention',
style = 'default',
size = 'sm',
className,
...props
}: TagProps) => {
const {
wrapper: wrapperCls,
@@ -51,7 +53,7 @@ export const Tag = ({
}, [cloneIcon, iconCls, rightIcon]);
return (
<div className={wrapperCls()}>
<div className={wrapperCls({ className })} {...props}>
{renderLeftIcon}
<p className={labelCls()}>{children}</p>
{renderRightIcon}
@@ -1,16 +1,44 @@
import React, { ComponentPropsWithoutRef } from 'react';
import React, { ComponentPropsWithoutRef, useMemo } from 'react';
import { cn } from 'utils/classnames';
export interface WavyBorderProps extends ComponentPropsWithoutRef<'div'> {}
type WaveBorderVariant = 'stroke' | 'stroke-and-fill';
export interface WavyBorderProps extends ComponentPropsWithoutRef<'div'> {
variant?: WaveBorderVariant;
}
export const WavyBorder = ({
className,
children,
variant = 'stroke',
...props
}: WavyBorderProps) => {
const imageSrc = useMemo(() => {
switch (variant) {
case 'stroke-and-fill':
return '/wavy-border-line-and-fill.svg';
case 'stroke':
default:
return '/wavy-border-line.svg';
}
}, [variant]);
return (
<div {...props} className={cn('wave', className)}>
{children}
<div
{...props}
className={cn(className)}
style={{
// If adding background beneath the wave, we use mask
mask: `url(/wavy-border-fill.svg) repeat-x top`,
WebkitMask: `url(/wavy-border-fill.svg) repeat-x top`,
}}
>
{/* Wave */}
<div
className="h-1 w-full bg-repeat-x bg-top"
style={{
backgroundImage: `url(${imageSrc})`,
}}
/>
</div>
);
};
-8
View File
@@ -153,12 +153,4 @@
.focus-ring {
@apply focus-visible:ring-[3px] focus-visible:ring-snowball-200 focus-visible:ring-offset-1 focus-visible:ring-offset-snowball-500 focus-visible:outline-none;
}
.wave {
background-image: url('../public/wave-border.svg');
background-repeat: repeat-x;
background-position: top;
background-size: cover;
@apply w-full h-1;
}
}
-26
View File
@@ -1,26 +0,0 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import Sidebar from '../components/Sidebar';
import { OctokitProvider } from '../context/OctokitContext';
const OrgSlug = () => {
return (
<div className="grid grid-cols-5 h-screen bg-snowball-50">
<>
<div className="h-full">
<Sidebar />
</div>
<div className="col-span-4 h-full p-3 overflow-y-hidden">
<div className="bg-white rounded-3xl h-full overflow-y-auto shadow-sm">
<OctokitProvider>
<Outlet />
</OctokitProvider>
</div>
</div>
</>
</div>
);
};
export default OrgSlug;
+19 -21
View File
@@ -3,11 +3,11 @@ import { Link, useParams } from 'react-router-dom';
import { Project } from 'gql-client';
import { Button } from 'components/shared/Button';
import { Typography, Chip } from '@material-tailwind/react';
import { useGQLClient } from '../../context/GQLClientContext';
import { PlusIcon } from 'components/shared/CustomIcon';
import { ProjectCard } from 'components/projects/ProjectCard';
import { Heading } from 'components/shared/Heading';
import { Badge } from 'components/shared/Badge';
import { useGQLClient } from 'context/GQLClientContext';
const Projects = () => {
const client = useGQLClient();
@@ -26,33 +26,31 @@ const Projects = () => {
}, [orgSlug]);
return (
<div>
<div className="flex p-5">
<section className="px-6 py-6 flex flex-col gap-6">
{/* Header */}
<div className="flex items-center">
<div className="grow">
<div className="flex gap-2 items-center">
<Typography variant="h4" placeholder={''}>
<div className="flex gap-4 items-center">
<Heading as="h2" className="text-[24px]">
Projects
</Typography>
<Chip
className="bg-gray-300 rounded-full static"
value={projects.length}
size="sm"
/>
</Heading>
<Badge className="bg-base-bg-alternate text-elements-mid-em h-7 w-7">
{projects.length}
</Badge>
</div>
</div>
<div>
<Link to="projects/create">
<Button leftIcon={<PlusIcon />}>Create project</Button>
</Link>
</div>
<Link to="projects/create">
<Button leftIcon={<PlusIcon />}>Create project</Button>
</Link>
</div>
<div className="grid grid-cols-3 gap-5 p-5">
{projects.length !== 0 &&
{/* List of projects */}
<div className="grid grid-cols-3 gap-4">
{projects.length > 0 &&
projects.map((project, key) => {
return <ProjectCard project={project} key={key} />;
})}
</div>
</div>
</section>
);
};
@@ -0,0 +1,31 @@
import { Sidebar } from 'components/shared/Sidebar';
import { OctokitProvider } from 'context/OctokitContext';
import React, { ComponentPropsWithoutRef } from 'react';
import { Outlet } from 'react-router-dom';
import { cn } from 'utils/classnames';
export interface DashboardLayoutProps
extends ComponentPropsWithoutRef<'section'> {}
export const DashboardLayout = ({
className,
children,
...props
}: DashboardLayoutProps) => {
return (
<section
{...props}
className={cn('grid grid-cols-5 h-screen bg-snowball-50', className)}
>
<Sidebar />
<div className="col-span-4 h-full px-3 py-3 overflow-y-hidden">
<div className="rounded-3xl bg-base-bg h-full shadow-card overflow-y-auto relative">
<OctokitProvider>
<Outlet />
</OctokitProvider>
</div>
</div>
{children}
</section>
);
};
@@ -1,34 +0,0 @@
import React from 'react';
import { Outlet, Link, useParams } from 'react-router-dom';
import { IconButton } from '@material-tailwind/react';
import HorizontalLine from '../../../components/HorizontalLine';
const CreateProject = () => {
const { orgSlug } = useParams();
return (
<div className="h-full">
<div className="flex p-4 items-center">
<div className="grow">
<h3 className="text-gray-750 text-2xl">Create new project</h3>
</div>
<div>
<Link to={`/${orgSlug}`}>
<IconButton
className="rounded-full"
variant="outlined"
placeholder={''}
>
X
</IconButton>
</Link>
</div>
</div>
<HorizontalLine />
<Outlet />
</div>
);
};
export default CreateProject;
@@ -8,18 +8,13 @@ import {
} from 'react-router-dom';
import { Project as ProjectType } from 'gql-client';
import {
Button,
Tab,
Tabs,
TabsBody,
TabsHeader,
Typography,
} from '@material-tailwind/react';
import HorizontalLine from '../../../components/HorizontalLine';
import { useGQLClient } from '../../../context/GQLClientContext';
import { useOctokit } from '../../../context/OctokitContext';
import { Button } from 'components/shared/Button';
import { ChevronLeft } from 'components/shared/CustomIcon';
import { WavyBorder } from 'components/shared/WavyBorder';
import { Heading } from 'components/shared/Heading';
import { Tabs } from 'components/shared/Tabs';
const Id = () => {
const { id } = useParams();
@@ -69,96 +64,61 @@ const Id = () => {
<div className="h-full">
{project ? (
<>
<div className="flex p-4 gap-4 items-center">
<Button
variant="outlined"
className="rounded-full"
onClick={() => navigate(-1)}
placeholder={''}
>
{'<'}
</Button>
<Typography variant="h3" className="grow" placeholder={''}>
{project?.name}
</Typography>
<Link to={repoUrl} target="_blank">
<div className="px-6 py-4 flex justify-between items-center gap-4">
<div className="flex items-center justify-center gap-4">
<Button
className="rounded-full"
variant="outlined"
placeholder={''}
>
Open Repo
</Button>
</Link>
<Button className="rounded-full" color="blue" placeholder={''}>
Go to app
</Button>
variant="tertiary"
className="rounded-full h-11 w-11 p-0"
aria-label="Go back"
leftIcon={<ChevronLeft />}
onClick={() => navigate(-1)}
/>
<Heading className="text-2xl font-medium">
{project?.name}
</Heading>
</div>
<div className="flex items-center justify-center gap-3">
<Link to={repoUrl} target="_blank">
<Button className="h-11 transition-colors" variant="tertiary">
Open repo
</Button>
</Link>
<Button className="h-11 transition-colors">Go to app</Button>
</div>
</div>
<HorizontalLine />
<div className="p-4">
<Tabs value={currentTab}>
<TabsHeader
className="rounded-none border-b border-blue-gray-50 bg-transparent p-0"
indicatorProps={{
className:
'bg-transparent border-b-2 border-gray-900 shadow-none rounded-none',
}}
placeholder={''}
>
<Link to="">
<Tab
value=""
className={'p-2 cursor-pointer'}
placeholder={''}
>
Overview
</Tab>
</Link>
<Link to="deployments">
<Tab
value="deployments"
className={'p-2 cursor-pointer'}
placeholder={''}
>
Deployments
</Tab>
</Link>
<Link to="database">
<Tab
value="database"
className={'p-2 cursor-pointer'}
placeholder={''}
>
Database
</Tab>
</Link>
<Link to="integrations">
<Tab
value="integrations"
className={'p-2 cursor-pointer'}
placeholder={''}
>
Integrations
</Tab>
</Link>
<Link to="settings">
<Tab
value="settings"
className={'p-2 cursor-pointer'}
placeholder={''}
>
Settings
</Tab>
</Link>
</TabsHeader>
<TabsBody placeholder={''}>
<WavyBorder />
<div className="px-6 ">
<Tabs value={currentTab} className="flex-col pt-6">
<Tabs.List>
<Tabs.Trigger value="">
<Link to="">Overview</Link>
</Tabs.Trigger>
<Tabs.Trigger value="deployments">
<Link to="deployments">Deployments</Link>
</Tabs.Trigger>
<Tabs.Trigger value="database">
<Link to="database">Database</Link>
</Tabs.Trigger>
<Tabs.Trigger value="integrations">
<Link to="integrations">Integrations</Link>
</Tabs.Trigger>
<Tabs.Trigger value="settings">
<Link to="settings">Settings</Link>
</Tabs.Trigger>
</Tabs.List>
{/* Not wrapping in Tab.Content because we are using Outlet */}
<div className="py-7">
<Outlet context={{ project, onUpdate }} />
</TabsBody>
</div>
</Tabs>
</div>
</>
) : (
<h4>Project not found</h4>
<div className="grid place-items-center h-[calc(100vh-174px)]">
<Heading as="h4" className="text-2xl font-medium">
Project not found.
</Heading>
</div>
)}
</div>
);
@@ -1,29 +1,36 @@
import React from 'react';
import templates from '../../../../assets/templates';
import TemplateCard from '../../../../components/projects/create/TemplateCard';
import RepositoryList from '../../../../components/projects/create/RepositoryList';
import ConnectAccount from '../../../../components/projects/create/ConnectAccount';
import { useOctokit } from '../../../../context/OctokitContext';
import templates from 'assets/templates';
import RepositoryList from 'components/projects/create/RepositoryList';
import ConnectAccount from 'components/projects/create/ConnectAccount';
import { useOctokit } from 'context/OctokitContext';
import { Heading } from 'components/shared/Heading';
import { TemplateCard } from 'components/projects/create/TemplateCard';
const NewProject = () => {
const { octokit, updateAuth, isAuth } = useOctokit();
return isAuth ? (
<>
<h5 className="mt-4 ml-4">Start with template</h5>
<div className="grid grid-cols-3 p-4 gap-4">
{templates.map((template) => {
return (
<TemplateCard
isGitAuth={Boolean(octokit)}
template={template}
key={template.id}
/>
);
})}
<div className="space-y-3">
<Heading as="h3" className="font-medium text-lg">
Start with template
</Heading>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
{templates.map((template) => {
return (
<TemplateCard
isGitAuth={Boolean(octokit)}
template={template}
key={template.id}
/>
);
})}
</div>
</div>
<h5 className="mt-4 ml-4">Import a repository</h5>
<Heading as="h3" className="font-medium text-lg mt-10">
Import a repository
</Heading>
<RepositoryList octokit={octokit} />
</>
) : (
@@ -0,0 +1,39 @@
import React, { ComponentPropsWithoutRef } from 'react';
import { Link, Outlet, useParams } from 'react-router-dom';
import { Heading } from 'components/shared/Heading';
import { WavyBorder } from 'components/shared/WavyBorder';
import { Button } from 'components/shared/Button';
import { CrossIcon } from 'components/shared/CustomIcon';
import { cn } from 'utils/classnames';
export interface CreateProjectLayoutProps
extends ComponentPropsWithoutRef<'section'> {}
export const CreateProjectLayout = ({
className,
...props
}: CreateProjectLayoutProps) => {
const { orgSlug } = useParams();
return (
<section {...props} className={cn('h-full flex flex-col', className)}>
<div className="sticky top-0">
<div className="flex px-6 py-4 bg-base-bg items-center gap-4">
<Heading as="h2" className="flex-1 text-[24px] font-medium">
Create new project
</Heading>
<Link to={`/${orgSlug}`}>
<Button iconOnly variant="tertiary">
<CrossIcon size={18} />
</Button>
</Link>
</div>
<WavyBorder />
</div>
<section className="px-6 h-full flex-1 py-6 overflow-y-auto">
<Outlet />
</section>
</section>
);
};
@@ -3,20 +3,28 @@ import { Domain, DomainStatus } from 'gql-client';
import { Link, useNavigate, useOutletContext } from 'react-router-dom';
import { RequestError } from 'octokit';
import {
Typography,
Button,
Chip,
Avatar,
Tooltip,
} from '@material-tailwind/react';
import ActivityCard from '../../../../components/projects/project/ActivityCard';
import { relativeTimeMs } from '../../../../utils/time';
import { useOctokit } from '../../../../context/OctokitContext';
import { GitCommitWithBranch, OutletContextType } from '../../../../types';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { formatAddress } from '../../../../utils/format';
import { Button } from 'components/shared/Button';
import { Heading } from 'components/shared/Heading';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials';
import {
BranchStrokeIcon,
CheckRoundFilledIcon,
ClockIcon,
CursorBoxIcon,
GithubStrokeIcon,
GlobeIcon,
LinkIcon,
StorageIcon,
} from 'components/shared/CustomIcon';
import { Tag } from 'components/shared/Tag';
import { Activity } from 'components/projects/project/overview/Activity';
import { OverviewInfo } from 'components/projects/project/overview/OverviewInfo';
import { CalendarDaysIcon } from 'components/shared/CustomIcon/CalendarDaysIcon';
import { relativeTimeMs } from 'utils/time';
const COMMITS_PER_PAGE = 4;
@@ -107,97 +115,109 @@ const OverviewTabPanel = () => {
}, [project]);
return (
<div className="grid grid-cols-5">
<div className="col-span-3 p-2">
<div className="flex items-center gap-2 p-2 ">
<div className="grid grid-cols-5 gap-[72px]">
<div className="col-span-3">
<div className="flex items-center gap-4 mb-6">
<Avatar
src={project.icon || '/gray.png'}
variant="rounded"
placeholder={''}
size={48}
initials={getInitials(project.name)}
imageSrc={project.icon}
type="blue"
/>
<div className="grow">
<Typography placeholder={''}>{project.name}</Typography>
<Typography variant="small" color="gray" placeholder={''}>
<div className="flex-1 space-y-1">
<Heading className="text-lg leading-6 font-medium">
{project.name}
</Heading>
<span className="text-sm text-elements-low-em tracking-tight">
{project.subDomain}
</Typography>
</span>
</div>
</div>
<div className="flex justify-between p-2 text-sm items-center">
<div>^ Domain</div>
<OverviewInfo label="Domain" icon={<GlobeIcon />}>
{liveDomain ? (
<Chip
className="normal-case ml-6 inline font-normal"
size="sm"
value="Connected"
icon="^"
color="green"
/>
<Tag type="positive" size="xs" leftIcon={<CheckRoundFilledIcon />}>
Connected
</Tag>
) : (
<div className="flex items-center">
<Chip
className="normal-case inline font-normal mx-2"
size="sm"
value="Not connected"
icon="^"
color="orange"
/>
<div className="flex items-center gap-2">
<Tag type="attention" size="xs" leftIcon={<ClockIcon />}>
Not connected
</Tag>
<Button
className="normal-case rounded-full"
color="blue"
size="sm"
onClick={() => {
navigate('settings/domains');
}}
placeholder={''}
variant="tertiary"
size="sm"
>
Setup
</Button>
</div>
)}
</div>
</OverviewInfo>
{project.deployments.length !== 0 ? (
<>
<div className="flex justify-between p-2 text-sm">
<p>^ Source</p>
<p>^ {project.deployments[0]?.branch}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Deployment</p>
<p className="text-blue-600">{liveDomain?.name}</p>
</div>
<div className="flex justify-between p-2 text-sm">
<p>^ Created</p>
<p>
{relativeTimeMs(project.deployments[0].createdAt)} by ^{' '}
<Tooltip content={project.deployments[0].createdBy.name}>
{formatAddress(project.deployments[0].createdBy.name ?? '')}
</Tooltip>
</p>
</div>
{/* SOURCE */}
<OverviewInfo label="Source" icon={<GithubStrokeIcon />}>
<div className="flex gap-2 items-center">
<BranchStrokeIcon className="text-elements-low-em w-4 h-5" />
<span className="text-elements-high-em text-sm tracking-tighter">
{project.deployments[0]?.branch}
</span>
</div>
</OverviewInfo>
{/* DATABASE */}
<OverviewInfo label="Database" icon={<StorageIcon />}>
<div className="flex gap-2 items-center">
<Link to="#">
<span className="group text-controls-primary hover:border-controls-primary transition-colors border-b border-b-transparent flex gap-2 items-center text-sm tracking-tight">
{/* // TODO: add db name
dbname
*/}
<LinkIcon className="group-hover:rotate-45 transition-transform" />
</span>
</Link>
</div>
</OverviewInfo>
{/* DEPLOYMENT */}
<OverviewInfo label="Deployment URL" icon={<CursorBoxIcon />}>
<div className="flex gap-2 items-center">
<Link to="#">
<span className="text-controls-primary group hover:border-controls-primary transition-colors border-b border-b-transparent flex gap-2 items-center text-sm tracking-tight">
{liveDomain?.name}{' '}
<LinkIcon className="group-hover:rotate-45 transition-transform" />
</span>
</Link>
</div>
</OverviewInfo>
{/* DEPLOYMENT DATE */}
<OverviewInfo label="Deployment date" icon={<CalendarDaysIcon />}>
<div className="flex gap-2 items-center text-elements-high-em text-sm tracking-tighter">
<span>{relativeTimeMs(project.deployments[0].createdAt)}</span>
by
<Avatar
// TODO: add imageSrc
// imageSrc={project.deployments[0]?.createdBy.avatar}
initials={getInitials(
project.deployments[0]?.createdBy?.name ?? '',
)}
className="rounded-full"
size={24}
/>
<span>{project.deployments[0]?.createdBy?.name}</span>
</div>
</OverviewInfo>
</>
) : (
<div>No current deployment found</div>
<p className="text-elements-low-em text-sm py-3">
No current deployment found.
</p>
)}
</div>
<div className="col-span-2 p-2">
<div className="flex justify-between">
<Typography variant="h6" placeholder={''}>
Activity
</Typography>
<button className="text-xs bg-gray-300 rounded-full p-2">
See all
</button>
</div>
<div className="p-2">
{activities.map((activity, index) => {
return (
<Link to={activity.html_url} target="_blank" key={index}>
<ActivityCard activity={activity} />
</Link>
);
})}
</div>
</div>
<Activity activities={activities} />
</div>
);
};
@@ -1,16 +1,16 @@
import React from 'react';
import CreateProject from './Create';
import Id from './Id';
import AddDomain from './id/settings/domains/add';
import { createProjectRoutes } from './create/routes';
import { projectTabRoutes } from './id/routes';
import { addDomainRoutes } from './id/settings/domains/add/routes';
import { CreateProjectLayout } from './create/layout';
export const projectsRoutesWithoutSearch = [
{
path: 'create',
element: <CreateProject />,
element: <CreateProjectLayout />,
children: createProjectRoutes,
},
{
@@ -0,0 +1,43 @@
import React, {
ReactElement,
isValidElement,
Children,
cloneElement as reactCloneElement,
HTMLProps,
ReactNode,
} from 'react';
import { ClassProp } from 'tailwind-variants';
import { cn } from './classnames';
interface cloneElement extends HTMLProps<ReactNode> {
element: ReactNode;
themeStyle?: (props: ClassProp) => string;
}
export const cloneElement = ({
element,
themeStyle,
className,
...props
}: cloneElement) => {
if (isValidElement(element)) {
return (
<>
{Children.map(element, (child) => {
const originalClassName = (child.props as HTMLProps<ReactNode>)
?.className;
return reactCloneElement(child as ReactElement, {
className: themeStyle
? themeStyle({
className: cn(originalClassName, className), // overriding icon classNames
})
: originalClassName,
...props,
});
})}
</>
);
}
return <></>;
};
+1
View File
@@ -158,6 +158,7 @@ export default withMT({
field: '0px 1px 2px rgba(0, 0, 0, 0.04)',
inset: 'inset 0px 1px 0px rgba(8, 47, 86, 0.06)',
card: '0px 0px 0px 1px #E8F0F7, 0px 2px 4px rgba(8, 47, 86, 0.04)',
'card-sm': '0px 1px 2px -1px rgba(4, 25, 47, 0.08)',
},
spacing: {
2.5: '0.625rem',